init
Release Please / release-please (push) Failing after 4m2s
Web CI / web typecheck + build (push) Successful in 3m39s
CodeQL Analysis / Analyze (javascript-typescript) (push) Failing after 1h1m49s
CodeQL Analysis / Analyze (go) (push) Failing after 1h1m49s

This commit is contained in:
redlineo committed 2026-07-13 10:14:00 +03:00
commit d295c7f139
1527 files changed
+270726

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
# Batch Processing
Process multiple job offers in parallel via headless workers. Each worker runs the full evaluation pipeline (A-F report + PDF + tracker line) autonomously. See the **Headless / Batch Mode** table in `AGENTS.md` for the correct command per CLI.
## Quick Start
1. **Add offers** to `batch-input.tsv` (tab-separated: `id`, `url`, `source`, `notes`):
```tsv
id url source notes
1 https://jobs.example.com/role-a LinkedIn
2 https://greenhouse.io/company/role-b Greenhouse priority
```
2. **Dry run** to preview what will be processed:
```bash
./batch/batch-runner.sh --dry-run
```
3. **Run the batch**:
```bash
./batch/batch-runner.sh
```
4. **Results** are automatically merged into `data/applications.md`, processed offers are reconciled out of the `data/pipeline.md` inbox, and integrity is verified with `verify-pipeline.mjs` at the end of the run.
## Options
| Flag | Default | Description |
|------|---------|-------------|
| `--parallel N` | `1` | Number of concurrent headless workers |
| `--dry-run` | off | Preview pending offers without processing |
| `--retry-failed` | off | Only retry offers marked as `failed` in state |
| `--resume-paused` | off | Resume offers paused after a Claude session/rate limit |
| `--start-from N` | `0` | Skip offers with ID below N |
| `--limit N` | `0` | Max number of offers to process in this run (0 = no limit) |
| `--max-retries N` | `2` | Max retry attempts per offer before giving up |
| `--rate-limit-sleep N` | `300` | Seconds to wait before retrying a transient rate-limited worker; use `0` to pause the batch immediately |
## Directory Layout
```
batch/
batch-runner.sh # Orchestrator script
batch-prompt.md # Prompt template sent to each worker
batch-input.tsv # Input offers (you create this)
batch-state.tsv # Processing state (auto-managed, resumable)
logs/ # Per-offer worker logs ({report_num}-{id}.log)
tracker-additions/ # TSV lines produced by workers
merged/ # TSVs already merged into applications.md
```
## How It Works
1. **batch-runner.sh** reads `batch-input.tsv` and `batch-state.tsv` to determine which offers need processing.
2. For each pending offer, it assigns a report number and launches a headless worker with `batch-prompt.md` as the system prompt (placeholders like `{{URL}}`, `{{REPORT_NUM}}` are resolved).
3. Each worker evaluates the offer, writes a report to `reports/`, generates a PDF to `output/`, and writes a tracker TSV to `tracker-additions/`.
4. After all workers finish, batch-runner calls `merge-tracker.mjs` to merge TSVs into `data/applications.md`, `reconcile-pipeline.mjs` to move processed offers out of the `data/pipeline.md` inbox, and `verify-pipeline.mjs` to check integrity.
## Tracker Merge
Workers write one TSV per offer to `batch/tracker-additions/`. The merge script (`npm run merge`) handles:
- Deduplication by company + role fuzzy match and report number
- Column order conversion (TSV has status before score; applications.md has score before status)
- In-place updates when a re-evaluation scores higher than the existing entry
- Moving processed TSVs to `tracker-additions/merged/`
Run `npm run merge` manually if you need to merge outside of a batch run.
## Pipeline Reconcile
Batch mode reads offers from `batch-input.tsv`, but the `data/pipeline.md` inbox is a separate list. Without reconciliation, an offer evaluated by a batch run stays in the pipeline "Pendientes" section and gets surfaced again on the next scan or `/career-ops pipeline` run -- producing duplicate reports.
`reconcile-pipeline.mjs` (run as `npm run reconcile`) closes that gap: after the tracker merge, every `completed` or `skipped` offer in `batch-state.tsv` whose URL is still in pipeline "Pendientes" is moved to "Procesadas" with its report link and score (entries without a report file on disk are left in place). It is idempotent -- safe to run after every batch, or manually.
## Resumability
`batch-state.tsv` tracks the status of every offer (`pending`, `processing`, `completed`, `failed`, `skipped`, `rate_limited`, `paused_rate_limit`). If the batch is interrupted, re-running `batch-runner.sh` picks up where it left off -- completed offers are skipped automatically. `rate_limited` is a non-completed state used while the runner waits before retrying, so interrupted rate-limited jobs are eligible on the next normal run.
`paused_rate_limit` is different: it means a worker hit a Claude session/usage limit, so the runner stopped scheduling new offers and preserved the retry count. Resume those rows explicitly after the limit resets:
```bash
./batch/batch-runner.sh --resume-paused
```
A PID-based lock file (`batch-runner.pid`) prevents concurrent batch runs. If a previous run crashed, the stale lock is detected and removed automatically.
## Prerequisites
- Your CLI in PATH (see **Headless / Batch Mode** table in `AGENTS.md`)
- Node.js >= 18, Playwright chromium installed (`npm run doctor` to verify)
- `batch-input.tsv` with at least one offer
+11
View File
@@ -0,0 +1,11 @@
id url source notes
1 https://getmatch.ru/vacancies/34414-starshii-frontend-razrabotchik?s=offers Javascript jobs (Vue/React/Angular) — Telegram Старший frontend-разработчик | note: via Telegram: https://t.me/s/jsdevjob/2410
2 https://hirehi.ru/development/frontend-razrabotchik-55966 Javascript jobs (Vue/React/Angular) — Telegram Middle Frontend | note: via Telegram: https://t.me/s/jsdevjob/2413
3 https://hh.ru/vacancy/133613060?query=frontend&hhtmFrom=vacancy_search_list Javascript jobs (Vue/React/Angular) — Telegram Frontend-разработчик | note: via Telegram: https://t.me/s/jsdevjob/2416
4 https://hh.ru/vacancy/133056126?query=frontend&hhtmFrom=vacancy_search_list Javascript jobs (Vue/React/Angular) — Telegram Frontend developer (React) | note: via Telegram: https://t.me/s/jsdevjob/2419
5 https://getmatch.ru/vacancies/35037-frontend-software-engineer-bootcamp?s=offers Javascript jobs (Vue/React/Angular) — Telegram Frontend Software Engineer (Bootcamp) | note: via Telegram: https://t.me/s/jsdevjob/2423
6 https://career.habr.com/vacancies/1000167021 Javascript jobs (Vue/React/Angular) — Telegram Ведущий фронтенд-разработчик (React) | note: via Telegram: https://t.me/s/jsdevjob/2425
7 https://hh.ru/vacancy/119867578?query=frontend&hhtmFrom=vacancy_search_list Javascript jobs (Vue/React/Angular) — Telegram Frontend-разработчик React (junior) | note: via Telegram: https://t.me/s/jsdevjob/2429
8 https://t.me/s/runello_rus_javascript/1443 SiteAnalyzer Frontend на React
9 https://t.me/s/runello_rus_javascript/1445 JavaScript Job Offers — Telegram Fullstack React Developer
10 https://t.me/s/runello_rus_javascript/1448 ЗАО КонсультантПлюс Веб разработчик Vue/Js
1 id url source notes
2 1 https://getmatch.ru/vacancies/34414-starshii-frontend-razrabotchik?s=offers Javascript jobs (Vue/React/Angular) — Telegram Старший frontend-разработчик | note: via Telegram: https://t.me/s/jsdevjob/2410
3 2 https://hirehi.ru/development/frontend-razrabotchik-55966 Javascript jobs (Vue/React/Angular) — Telegram Middle Frontend | note: via Telegram: https://t.me/s/jsdevjob/2413
4 3 https://hh.ru/vacancy/133613060?query=frontend&hhtmFrom=vacancy_search_list Javascript jobs (Vue/React/Angular) — Telegram Frontend-разработчик | note: via Telegram: https://t.me/s/jsdevjob/2416
5 4 https://hh.ru/vacancy/133056126?query=frontend&hhtmFrom=vacancy_search_list Javascript jobs (Vue/React/Angular) — Telegram Frontend developer (React) | note: via Telegram: https://t.me/s/jsdevjob/2419
6 5 https://getmatch.ru/vacancies/35037-frontend-software-engineer-bootcamp?s=offers Javascript jobs (Vue/React/Angular) — Telegram Frontend Software Engineer (Bootcamp) | note: via Telegram: https://t.me/s/jsdevjob/2423
7 6 https://career.habr.com/vacancies/1000167021 Javascript jobs (Vue/React/Angular) — Telegram Ведущий фронтенд-разработчик (React) | note: via Telegram: https://t.me/s/jsdevjob/2425
8 7 https://hh.ru/vacancy/119867578?query=frontend&hhtmFrom=vacancy_search_list Javascript jobs (Vue/React/Angular) — Telegram Frontend-разработчик React (junior) | note: via Telegram: https://t.me/s/jsdevjob/2429
9 8 https://t.me/s/runello_rus_javascript/1443 SiteAnalyzer Frontend на React
10 9 https://t.me/s/runello_rus_javascript/1445 JavaScript Job Offers — Telegram Fullstack React Developer
11 10 https://t.me/s/runello_rus_javascript/1448 ЗАО КонсультантПлюс Веб разработчик Vue/Js
+458
View File
@@ -0,0 +1,458 @@
# career-ops Batch Worker — Evaluación Completa + PDF + Tracker Line
Eres un worker de evaluación de ofertas de empleo for the candidate (read name from config/profile.yml). Recibes una oferta (URL + JD text) y produces:
1. Evaluación completa A-G (report .md)
2. PDF personalizado ATS-optimizado
3. Línea de tracker para merge posterior
**IMPORTANTE**: Este prompt es self-contained. Tienes TODO lo necesario aquí. No dependes de ningún otro skill ni sistema.
---
## Fuentes de Verdad (LEER antes de evaluar)
| Archivo | Ruta absoluta | Cuándo |
|---------|---------------|--------|
| cv.md | `cv.md (project root)` | SIEMPRE |
| _profile.md | `modes/_profile.md (if exists)` | SIEMPRE (user customizations: archetypes, role_shape, location policy, comp targets) |
| profile.yml | `config/profile.yml (if exists)` | SIEMPRE (candidate identity, comp range, role_shape rules) |
| llms.txt | `llms.txt (if exists)` | SIEMPRE |
| article-digest.md | `article-digest.md (project root)` | SIEMPRE (proof points) |
| i18n.ts | `i18n.ts (if exists, optional)` | Solo entrevistas/deep |
| cv-template.html | `templates/cv-template.html` | Para PDF |
| generate-pdf.mjs | `generate-pdf.mjs` | Para PDF |
**REGLA: NUNCA escribir en cv.md ni i18n.ts.** Son read-only.
**REGLA: NUNCA hardcodear métricas.** Leerlas de cv.md + article-digest.md en el momento.
**REGLA: Para métricas de artículos, article-digest.md prevalece sobre cv.md.** cv.md puede tener números más antiguos — es normal.
**REGLA: Antes de evaluar, cargar `modes/_profile.md` y `config/profile.yml` si existen.** Contienen las preferencias del candidato Y reglas concretas de scoring que **sobrescriben** los defaults del sistema.
Tipos de patrones que estos archivos pueden incluir:
- **Caps de bloque** — ej: "cap Block A at 3.0/5 if title contains 'Lead'/'Head'/'Principal'"
- **Overrides de recomendación** — ej: "force SKIP if comp ceiling below $120K" o "force SKIP if role_shape signals broad ownership"
- **Scoring por dimensión** — ej: "Remote: full credit on remote-first; score 2.0 on full on-site outside [region]"
- **Framing adaptativo por archetype** — mappings entre arquetipos detectados y proof points a priorizar
Aplicación durante la evaluación A-G:
- **Bloque A:** aplicar caps de role-shape ANTES de calcular el score del bloque
- **Bloques B-D:** aplicar adaptive framing por archetype y reglas de dimension scoring (location, comp, etc.)
- **Bloque F:** aplicar recommendation overrides (SKIP forzado, etc.) — `_profile.md` puede convertir un score técnicamente alto en un SKIP por shape o por comp
**En conflicto, las reglas de `_profile.md` ganan sobre los defaults de `_shared.md`.** Esto es intencional: `_profile.md` es la capa de personalización del usuario.
---
## Placeholders (sustituidos por el orquestador)
| Placeholder | Descripción |
|-------------|-------------|
| `{{URL}}` | URL de la oferta |
| `{{JD_FILE}}` | Ruta al archivo con el texto del JD |
| `{{REPORT_NUM}}` | Número de report (3 dígitos, zero-padded: 001, 002...) |
| `{{DATE}}` | Fecha actual YYYY-MM-DD |
| `{{ID}}` | ID único de la oferta en batch-input.tsv |
---
## Pipeline (ejecutar en orden)
### Paso 1 — Obtener JD
1. Lee el archivo JD en `{{JD_FILE}}`
2. Si el archivo está vacío o no existe, intenta obtener el JD desde `{{URL}}` con WebFetch
3. Si ambos fallan, reporta error y termina
### Paso 2 — Evaluación A-G
Read `cv.md`. Ejecuta TODOS los bloques:
#### Paso 0 — Detección de Arquetipo
Clasifica la oferta en uno de los 6 arquetipos. Si es híbrido, indica los 2 más cercanos.
**Los 6 arquetipos (todos igual de válidos):**
| Arquetipo | Ejes temáticos | Qué compran |
|-----------|----------------|-------------|
| **AI Platform / LLMOps Engineer** | Evaluation, observability, reliability, pipelines | Alguien que ponga AI en producción con métricas |
| **Agentic Workflows / Automation** | HITL, tooling, orchestration, multi-agent | Alguien que construya sistemas de agentes fiables |
| **Technical AI Product Manager** | GenAI/Agents, PRDs, discovery, delivery | Alguien que traduzca negocio → producto AI |
| **AI Solutions Architect** | Hyperautomation, enterprise, integrations | Alguien que diseñe arquitecturas AI end-to-end |
| **AI Forward Deployed Engineer** | Client-facing, fast delivery, prototyping | Alguien que entregue soluciones AI a clientes rápido |
| **AI Transformation Lead** | Change management, adoption, org enablement | Alguien que lidere el cambio AI en una organización |
**Framing adaptativo:**
> **Las métricas concretas se leen de `cv.md` + `article-digest.md` en cada evaluación. NUNCA hardcodear números aquí.**
| Si el rol es... | Emphasize about the candidate... | Fuentes de proof points |
|-----------------|--------------------------|--------------------------|
| Platform / LLMOps | Builder de sistemas en producción, observability, evals, closed-loop | article-digest.md + cv.md |
| Agentic / Automation | Orquestación multi-agente, HITL, reliability, cost | article-digest.md + cv.md |
| Technical AI PM | Product discovery, PRDs, métricas, stakeholder mgmt | cv.md + article-digest.md |
| Solutions Architect | Diseño de sistemas, integrations, enterprise-ready | article-digest.md + cv.md |
| Forward Deployed Engineer | Fast delivery, client-facing, prototype → prod | cv.md + article-digest.md |
| AI Transformation Lead | Change management, team enablement, adoption | cv.md + article-digest.md |
**Ventaja transversal**: Enmarcar perfil como **"Technical builder"** que adapta su framing al rol:
- Para PM: "builder que reduce incertidumbre con prototipos y luego productioniza con disciplina"
- Para FDE: "builder que entrega fast con observability y métricas desde día 1"
- Para SA: "builder que diseña sistemas end-to-end con experiencia real en integrations"
- Para LLMOps: "builder que pone AI en producción con closed-loop quality systems — leer métricas de article-digest.md"
Convertir "builder" en señal profesional, no en "hobby maker". El framing cambia, la verdad es la misma.
#### Bloque A — Resumen del Rol
Tabla con: Arquetipo detectado, Domain, Function, Seniority, Remote, Team size, TL;DR.
#### Bloque B — Match con CV
Read `cv.md`. Tabla con cada requisito del JD mapeado a líneas exactas del CV o keys de i18n.ts.
**Adaptado al arquetipo:**
- FDE → priorizar delivery rápida y client-facing
- SA → priorizar diseño de sistemas e integrations
- PM → priorizar product discovery y métricas
- LLMOps → priorizar evals, observability, pipelines
- Agentic → priorizar multi-agent, HITL, orchestration
- Transformation → priorizar change management, adoption, scaling
Sección de **gaps** con estrategia de mitigación para cada uno:
1. ¿Es hard blocker o nice-to-have?
2. Can the candidate demonstrate experiencia adyacente?
3. ¿Hay un proyecto portfolio que cubra este gap?
4. Plan de mitigación concreto
#### Bloque C — Nivel y Estrategia
1. **Nivel detectado** en el JD vs **candidate's natural level**
2. **Plan "vender senior sin mentir"**: frases específicas, logros concretos, founder como ventaja
3. **Plan "si me downlevelan"**: aceptar si comp justa, review a 6 meses, criterios claros
#### Bloque D — Comp y Demanda
Usar WebSearch para salarios actuales (Glassdoor, Levels.fyi, Blind), reputación comp de la empresa, tendencia demanda. Tabla con datos y fuentes citadas. Si no hay datos, decirlo.
Score de comp (1-5): 5=top quartile, 4=above market, 3=median, 2=slightly below, 1=well below.
#### Bloque E — Plan de Personalización
| # | Sección | Estado actual | Cambio propuesto | Por qué |
|---|---------|---------------|------------------|---------|
Top 5 cambios al CV + Top 5 cambios a LinkedIn.
#### Bloque F — Plan de Entrevistas
6-10 historias STAR mapeadas a requisitos del JD:
| # | Requisito del JD | Historia STAR | S | T | A | R |
**Selección adaptada al arquetipo.** Incluir también:
- 1 case study recomendado (cuál proyecto presentar y cómo)
- Preguntas red-flag y cómo responderlas
#### Bloque G — Posting Legitimacy
Analyze posting signals to assess whether this is a real, active opening.
**Batch mode limitations:** Playwright is not available, so posting freshness signals (exact days posted, apply button state) cannot be directly verified. Mark these as "unverified (batch mode)."
**What IS available in batch mode:**
1. **Description quality analysis** -- Full JD text is available. Analyze specificity, requirements realism, salary transparency, boilerplate ratio.
2. **Company hiring signals** -- WebSearch queries for layoff/freeze news (combine with Block D comp research).
3. **Reposting detection** -- Read `data/scan-history.tsv` to check for prior appearances.
4. **Role market context** -- Qualitative assessment from JD content.
**Output format:** Same as interactive mode (Assessment tier + Signals table + Context Notes), but with a note that posting freshness is unverified.
**Assessment:** Apply the same three tiers (High Confidence / Proceed with Caution / Suspicious), weighting available signals more heavily. If insufficient signals are available to make a determination, default to "Proceed with Caution" with a note about limited data.
#### Score Global
| Dimensión | Score |
|-----------|-------|
| Match con CV | X/5 |
| Alineación North Star | X/5 |
| Comp | X/5 |
| Señales culturales | X/5 |
| Red flags | -X (si hay) |
| **Global** | **X/5** |
#### Machine Summary
Create a machine-readable summary from the completed A-G evaluation and global score. This block is for downstream scripts; keep field names exact, use YAML, and do not add prose inside the fence.
```yaml
company: "{empresa}"
role: "{rol}"
score: {X.X}
legitimacy_tier: "{High Confidence | Proceed with Caution | Suspicious}"
archetype: "{detectado}"
final_decision: "{Apply | Consider | Research first | Skip}"
hard_stops:
- "{blocking gap or risk}"
soft_gaps:
- "{non-blocking gap}"
top_strengths:
- "{strength most relevant to this role}"
risk_level: "{Low | Medium | High}"
confidence: "{Low | Medium | High}"
next_action: "{one concrete next step}"
```
Rules:
- Use `[]` for `hard_stops`, `soft_gaps`, or `top_strengths` when empty.
- `score` is numeric only, without `/5`.
- `final_decision` must reflect the full evaluation, not only the CV match.
- Do not invent missing data. If confidence is limited, set `confidence: "Low"` and explain the limitation in the human-readable sections.
### Paso 3 — Guardar Report .md
Guardar evaluación completa en:
```
reports/{{REPORT_NUM}}-{company-slug}-{{DATE}}.md
```
Donde `{company-slug}` es el nombre de empresa en lowercase, sin espacios, con guiones.
**Formato del report:**
```markdown
# Evaluación: {Empresa} — {Rol}
**Fecha:** {{DATE}}
**Arquetipo:** {detectado}
**Score:** {X/5}
**Legitimacy:** {High Confidence | Proceed with Caution | Suspicious}
**URL:** {URL de la oferta original}
**PDF:** {output/cv-candidate-{company-slug}-{{DATE}}.pdf if score ≥ the resolved `auto_pdf_score_threshold` from Paso 4, else `not generated — run /career-ops pdf {company-slug} to create on demand`}
**Batch ID:** {{ID}}
---
## Machine Summary
```yaml
company: "{empresa}"
role: "{rol}"
score: {X.X}
legitimacy_tier: "{High Confidence | Proceed with Caution | Suspicious}"
archetype: "{detectado}"
final_decision: "{Apply | Consider | Research first | Skip}"
hard_stops:
- "{blocking gap or risk}"
soft_gaps:
- "{non-blocking gap}"
top_strengths:
- "{strength most relevant to this role}"
risk_level: "{Low | Medium | High}"
confidence: "{Low | Medium | High}"
next_action: "{one concrete next step}"
```
## A) Resumen del Rol
(contenido completo)
## B) Match con CV
(contenido completo)
## C) Nivel y Estrategia
(contenido completo)
## D) Comp y Demanda
(contenido completo)
## E) Plan de Personalización
(contenido completo)
## F) Plan de Entrevistas
(contenido completo)
## G) Posting Legitimacy
(contenido completo)
---
## Keywords extraídas
(15-20 keywords del JD para ATS)
```
### Paso 4 — Generar PDF (configurable)
**Gate:** Read `config/profile.yml` → `auto_pdf_score_threshold`. If the key is absent, default to **`3.0`** (the original gate of Path A). This step ONLY runs when the score from Paso 2 is **≥ the resolved threshold**. For everything below it, skip this entire step — the user can generate a tailored PDF on demand later via `/career-ops pdf {company-slug}` using the report from Paso 3 as input.
**Rationale:** Generating a tailored PDF costs ~30–60s per offer (Playwright launch + HTML render) and produces files that often go unused — most roles score 2.x/3.x and never reach application. The `3.0` default matches Path A's original behavior; raise `auto_pdf_score_threshold` (e.g. `4.0`) to pre-generate fewer PDFs, or set `0` to generate one for every offer. Both Path A (`/career-ops pipeline`) and Path B (this batch worker) read the same config key for consistency.
**If score < threshold:**
- Skip steps 1–14 below.
- In the report header use: `**PDF:** not generated — run /career-ops pdf {company-slug} to create on demand`.
- In Paso 5 (tracker line) use `pdf_emoji` = `❌`.
- In Paso 6 (output JSON) set `"pdf": null`.
- Done — move to Paso 5.
**If score ≥ threshold**, generate the tailored PDF:
1. Lee `cv.md` + `i18n.ts`
2. Extrae 15-20 keywords del JD
3. Detecta idioma del JD → idioma del CV (EN default)
4. Detecta ubicación empresa → formato papel: US/Canada → `letter`, resto → `a4`
5. Detecta arquetipo → adapta framing
6. Reescribe Professional Summary inyectando keywords
7. Selecciona top 3-4 proyectos más relevantes
8. Reordena bullets de experiencia por relevancia al JD
9. Construye competency grid (6-8 keyword phrases)
10. Inyecta keywords en logros existentes (**NUNCA inventa**)
11. Genera HTML completo desde template (lee `templates/cv-template.html`)
12. Escribe HTML a `output/cv-candidate-{company-slug}.html` (NO en /tmp — el HTML registrado es la fuente de regeneración del dashboard)
13. Ejecuta:
```bash
node generate-pdf.mjs \
output/cv-candidate-{company-slug}.html \
output/cv-candidate-{company-slug}-{{DATE}}.pdf \
--format={letter|a4} \
--report={{REPORT_NUM}}
```
14. Reporta: ruta PDF, nº páginas, % cobertura keywords
On success, in Paso 5 use `pdf_emoji` = `✅` and in Paso 6 set `"pdf"` to the output path.
**Reglas ATS:**
- Single-column (sin sidebars)
- Headers estándar: "Professional Summary", "Work Experience", "Education", "Skills", "Certifications", "Projects"
- Sin texto en imágenes/SVGs
- Sin info crítica en headers/footers
- UTF-8, texto seleccionable
- Keywords distribuidas: Summary (top 5), primer bullet de cada rol, Skills section
**Diseño:**
- Fonts: Space Grotesk (headings, 600-700) + DM Sans (body, 400-500)
- Fonts self-hosted: `fonts/`
- Header: Space Grotesk 24px bold + gradiente cyan→purple 2px + contacto
- Section headers: Space Grotesk 13px uppercase, color cyan `hsl(187,74%,32%)`
- Body: DM Sans 11px, line-height 1.5
- Company names: purple `hsl(270,70%,45%)`
- Márgenes: 0.6in
- Background: blanco
**Estrategia keyword injection (ético):**
- Reformular experiencia real con vocabulario exacto del JD
- NUNCA añadir skills the candidate doesn't have
- Ejemplo: JD dice "RAG pipelines" y CV dice "LLM workflows with retrieval" → "RAG pipeline design and LLM orchestration workflows"
**Template placeholders (en cv-template.html):**
| Placeholder | Contenido |
|-------------|-----------|
| `{{LANG}}` | `en` o `es` |
| `{{PAGE_WIDTH}}` | `8.5in` (letter) o `210mm` (A4) |
| `{{NAME}}` | (from profile.yml) |
| `{{EMAIL}}` | (from profile.yml) |
| `{{LINKEDIN_URL}}` | (from profile.yml) |
| `{{LINKEDIN_DISPLAY}}` | (from profile.yml) |
| `{{PORTFOLIO_URL}}` | (from profile.yml) |
| `{{PORTFOLIO_DISPLAY}}` | (from profile.yml) |
| `{{LOCATION}}` | (from profile.yml) |
| `{{SECTION_SUMMARY}}` | Professional Summary / Resumen Profesional |
| `{{SUMMARY_TEXT}}` | Summary personalizado con keywords |
| `{{SECTION_COMPETENCIES}}` | Core Competencies / Competencias Core |
| `{{COMPETENCIES}}` | `<span class="competency-tag">keyword</span>` × 6-8 |
| `{{SECTION_EXPERIENCE}}` | Work Experience / Experiencia Laboral |
| `{{EXPERIENCE}}` | HTML de cada trabajo con bullets reordenados |
| `{{SECTION_PROJECTS}}` | Projects / Proyectos |
| `{{PROJECTS}}` | HTML de top 3-4 proyectos |
| `{{SECTION_EDUCATION}}` | Education / Formación |
| `{{EDUCATION}}` | HTML de educación |
| `{{SECTION_CERTIFICATIONS}}` | Certifications / Certificaciones |
| `{{CERTIFICATIONS}}` | HTML de certificaciones |
| `{{SECTION_SKILLS}}` | Skills / Competencias |
| `{{SKILLS}}` | HTML de skills |
### Paso 5 — Tracker Line
Escribir una línea TSV a:
```
batch/tracker-additions/{{ID}}.tsv
```
Formato TSV (una sola línea, sin header, 9 columnas tab-separated):
```
{next_num}\t{{DATE}}\t{empresa}\t{rol}\t{status}\t{score}/5\t{pdf_emoji}\t[{{REPORT_NUM}}](reports/{{REPORT_NUM}}-{company-slug}-{{DATE}}.md)\t{nota_1_frase}
```
**Columnas TSV (orden exacto):**
| # | Campo | Tipo | Ejemplo | Validación |
|---|-------|------|---------|------------|
| 1 | num | int | `647` | Secuencial, max existente + 1 |
| 2 | date | YYYY-MM-DD | `2026-03-14` | Fecha de evaluación |
| 3 | company | string | `Datadog` | Nombre corto de empresa |
| 4 | role | string | `Staff AI Engineer` | Título del rol |
| 5 | status | canonical | `Evaluada` | DEBE ser canónico (ver states.yml) |
| 6 | score | X.XX/5 | `4.55/5` | O `N/A` si no evaluable |
| 7 | pdf | emoji | `✅` o `❌` | Si se generó PDF |
| 8 | report | md link | `[647](reports/647-...)` | Link root-relative; merge-tracker.mjs lo normaliza relativo al tracker (ej. `../reports/...`, #760) |
| 9 | notes | string | `APPLY HIGH...` | Resumen 1 frase |
**IMPORTANTE:** El orden TSV tiene status ANTES de score (col 5→status, col 6→score). En applications.md el orden es inverso (col 5→score, col 6→status). merge-tracker.mjs maneja la conversión.
**Estados canónicos válidos:** `Evaluada`, `Aplicado`, `Respondido`, `Entrevista`, `Oferta`, `Rechazado`, `Descartado`, `NO APLICAR`
Donde `{next_num}` se calcula leyendo la última línea de `data/applications.md`.
### Paso 6 — Output final
Al terminar, imprime por stdout un resumen JSON para que el orquestador lo parsee:
```json
{
"status": "completed",
"id": "{{ID}}",
"report_num": "{{REPORT_NUM}}",
"company": "{empresa}",
"role": "{rol}",
"score": {score_num},
"legitimacy": "{High Confidence|Proceed with Caution|Suspicious}",
"pdf": "{ruta_pdf}",
"report": "{ruta_report}",
"error": null
}
```
Si algo falla:
```json
{
"status": "failed",
"id": "{{ID}}",
"report_num": "{{REPORT_NUM}}",
"company": "{empresa_o_unknown}",
"role": "{rol_o_unknown}",
"score": null,
"pdf": null,
"report": "{ruta_report_si_existe}",
"error": "{descripción_del_error}"
}
```
---
## Reglas Globales
### NUNCA
1. Inventar experiencia o métricas
2. Modificar cv.md, i18n.ts ni archivos del portfolio
3. Compartir el teléfono en mensajes generados
4. Recomendar comp por debajo de mercado
5. Generar PDF sin leer primero el JD
6. Usar corporate-speak
### SIEMPRE
1. Leer cv.md, llms.txt y article-digest.md antes de evaluar
2. Detectar el arquetipo del rol y adaptar el framing
3. Citar líneas exactas del CV cuando haga match
4. Usar WebSearch para datos de comp y empresa
5. Generar contenido en el idioma del JD (EN default)
6. Ser directo y accionable — sin fluff
7. Cuando generes texto en inglés (PDF summaries, bullets, STAR stories), usa inglés nativo de tech: frases cortas, verbos de acción, sin passive voice innecesaria, sin "in order to" ni "utilized"
+932
View File
@@ -0,0 +1,932 @@
#!/usr/bin/env bash
set -euo pipefail
# career-ops batch runner — standalone orchestrator for claude -p workers
# Reads batch-input.tsv, delegates each offer to a claude -p worker,
# tracks state in batch-state.tsv for resumability.
#
# NOTE: This script is Claude Code-specific. It uses claude -p with
# --dangerously-skip-permissions and --append-system-prompt-file flags
# that are not available in other CLIs. Multi-CLI support is out of scope
# for now — contributions welcome.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
BATCH_DIR="$SCRIPT_DIR"
INPUT_FILE="$BATCH_DIR/batch-input.tsv"
STATE_FILE="$BATCH_DIR/batch-state.tsv"
PROMPT_FILE="$BATCH_DIR/batch-prompt.md"
LOGS_DIR="$BATCH_DIR/logs"
TRACKER_DIR="$BATCH_DIR/tracker-additions"
REPORTS_DIR="$PROJECT_DIR/reports"
APPLICATIONS_FILE="$PROJECT_DIR/data/applications.md"
LOCK_FILE="$BATCH_DIR/batch-runner.pid"
PAUSE_FILE="$BATCH_DIR/batch-runner.paused"
STATE_LOCK_DIR="$BATCH_DIR/.batch-state.lock"
STATE_LOCK_PID_FILE="$STATE_LOCK_DIR/pid"
STATE_LOCK_TIMEOUT_SECONDS=30
MAIN_PID="${BASHPID:-$$}"
# Defaults
PARALLEL=1
DRY_RUN=false
RETRY_FAILED=false
RESUME_PAUSED=false
START_FROM=0
MAX_RETRIES=2
MIN_SCORE=0
SKIP_PDF=false
MODEL="" # empty = let claude -p use the Claude Max default
RATE_LIMIT_SLEEP=300
BATCH_PAUSED=false
STATUS_ONLY=false
WATCH_MODE=false
LIMIT=0
# Return success for non-negative integer or decimal strings.
is_decimal_number() {
[[ "$1" =~ ^[0-9]+([.][0-9]+)?$ ]]
}
usage() {
cat <<'USAGE'
career-ops batch runner — process job offers in batch via claude -p workers
Uses your default Claude model (Claude Max subscription).
Usage: batch-runner.sh [OPTIONS]
Options:
--parallel N Number of parallel workers (default: 1)
--dry-run Show what would be processed, don't execute
--retry-failed Only retry offers marked as "failed" in state
--resume-paused Resume offers paused by a Claude session/rate limit
--start-from N Start from offer ID N (skip earlier IDs)
--limit N Max number of offers to process in this run
--max-retries N Max retry attempts per offer (default: 2)
--min-score N Skip PDF/tracker for offers scoring below N (default: 0 = off)
--skip-pdf Skip PDF generation entirely (write ❌ in tracker PDF column)
--rate-limit-sleep N Seconds to wait before retrying a rate-limited worker
(default: 300)
--model NAME Claude model passed to `claude -p --model` (default:
unset = Claude Max default). Use a cheaper model for
large batches, e.g. `--model claude-sonnet-4-6`.
--status Show batch progress and a per-job table, then exit
--watch Live-refresh progress until the run completes
-h, --help Show this help
Files:
batch-input.tsv Input offers (id, url, source, notes)
batch-state.tsv Processing state (auto-managed)
batch-prompt.md Prompt template for workers
logs/ Per-offer logs
tracker-additions/ Tracker lines for post-batch merge
Examples:
# Dry run to see pending offers
./batch-runner.sh --dry-run
# Process all pending
./batch-runner.sh
# Retry only failed offers
./batch-runner.sh --retry-failed
# Process 2 at a time starting from ID 10
./batch-runner.sh --parallel 2 --start-from 10
USAGE
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--parallel) PARALLEL="$2"; shift 2 ;;
--dry-run) DRY_RUN=true; shift ;;
--retry-failed) RETRY_FAILED=true; shift ;;
--resume-paused) RESUME_PAUSED=true; shift ;;
--start-from) START_FROM="$2"; shift 2 ;;
--limit) LIMIT="$2"; shift 2 ;;
--max-retries) MAX_RETRIES="$2"; shift 2 ;;
--min-score) MIN_SCORE="$2"; shift 2 ;;
--skip-pdf) SKIP_PDF=true; shift ;;
--rate-limit-sleep)
[[ $# -ge 2 ]] || { echo "ERROR: --rate-limit-sleep requires an argument"; exit 1; }
RATE_LIMIT_SLEEP="$2"
shift 2
;;
--model) MODEL="$2"; shift 2 ;;
--status) STATUS_ONLY=true; shift ;;
--watch) WATCH_MODE=true; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1"; usage; exit 1 ;;
esac
done
if ! [[ "$RATE_LIMIT_SLEEP" =~ ^[0-9]+$ ]]; then
echo "ERROR: --rate-limit-sleep must be a non-negative integer (seconds)."
exit 1
fi
if ! is_decimal_number "$MIN_SCORE"; then
echo "ERROR: --min-score must be a non-negative number."
exit 1
fi
if ! [[ "$LIMIT" =~ ^[0-9]+$ ]]; then
echo "ERROR: --limit must be a non-negative integer."
exit 1
fi
# Lock file to prevent double execution
acquire_lock() {
if [[ -f "$LOCK_FILE" ]]; then
local old_pid
old_pid=$(cat "$LOCK_FILE")
if kill -0 "$old_pid" 2>/dev/null; then
echo "ERROR: Another batch-runner is already running (PID $old_pid)"
echo "If this is stale, remove $LOCK_FILE"
exit 1
else
echo "WARN: Stale lock file found (PID $old_pid not running). Removing."
rm -f "$LOCK_FILE"
fi
fi
echo "$MAIN_PID" > "$LOCK_FILE"
}
release_lock() {
if [[ "${BASHPID:-$$}" != "$MAIN_PID" ]]; then
return
fi
rm -f "$LOCK_FILE"
}
trap release_lock EXIT
# Validate prerequisites
check_prerequisites() {
if [[ ! -f "$INPUT_FILE" ]]; then
echo "ERROR: $INPUT_FILE not found. Add offers first."
exit 1
fi
if [[ ! -f "$PROMPT_FILE" ]]; then
echo "ERROR: $PROMPT_FILE not found."
exit 1
fi
if ! command -v claude &>/dev/null; then
echo "ERROR: 'claude' CLI not found in PATH."
exit 1
fi
mkdir -p "$LOGS_DIR" "$TRACKER_DIR" "$REPORTS_DIR"
}
# Status/watch mode only needs prior batch state, not worker prerequisites.
check_status_prerequisites() {
if [[ ! -f "$STATE_FILE" ]]; then
echo "No state file found at $STATE_FILE"
exit 0
fi
}
# Initialize state file if it doesn't exist
init_state() {
if [[ ! -f "$STATE_FILE" ]]; then
printf 'id\turl\tstatus\tstarted_at\tcompleted_at\treport_num\tscore\terror\tretries\n' > "$STATE_FILE"
fi
}
acquire_state_lock() {
if [[ "${STATE_LOCK_DISABLED:-0}" -eq 1 ]]; then
return 0
fi
local waited=0
local max_waits=$((STATE_LOCK_TIMEOUT_SECONDS * 10))
while true; do
if mkdir "$STATE_LOCK_DIR" 2>/dev/null; then
if printf '%s\n' "${BASHPID:-$$}" > "$STATE_LOCK_PID_FILE"; then
STATE_LOCK_OWNED=1
return 0
fi
rm -f "$STATE_LOCK_PID_FILE" 2>/dev/null || true
rmdir "$STATE_LOCK_DIR" 2>/dev/null || true
echo "ERROR: Failed to initialize state lock metadata at $STATE_LOCK_DIR"
return 1
fi
if [[ ! -d "$STATE_LOCK_DIR" ]]; then
if (( PARALLEL <= 1 )); then
echo "WARN: State lock creation failed. Falling back to lock-free operation (single-worker mode)." >&2
STATE_LOCK_DISABLED=1
STATE_LOCK_OWNED=0
return 0
fi
echo "ERROR: Failed to create state lock directory $STATE_LOCK_DIR"
return 1
fi
if [[ -f "$STATE_LOCK_PID_FILE" ]]; then
local lock_pid
lock_pid=$(cat "$STATE_LOCK_PID_FILE" 2>/dev/null || true)
if [[ -n "$lock_pid" ]] && ! kill -0 "$lock_pid" 2>/dev/null; then
rm -f "$STATE_LOCK_PID_FILE"
if rmdir "$STATE_LOCK_DIR" 2>/dev/null; then
echo "WARN: Recovered stale state lock (PID $lock_pid not running)."
continue
fi
fi
fi
if (( waited >= max_waits )); then
echo "ERROR: Timed out waiting for state lock at $STATE_LOCK_DIR"
echo "If no batch-runner worker is active, remove the stale lock directory."
return 1
fi
sleep 0.1
((waited += 1))
done
}
release_state_lock() {
if [[ "${STATE_LOCK_OWNED:-0}" -ne 1 ]]; then
return
fi
rm -f "$STATE_LOCK_PID_FILE" 2>/dev/null || true
rmdir "$STATE_LOCK_DIR" 2>/dev/null || true
STATE_LOCK_OWNED=0
}
run_with_state_lock() {
acquire_state_lock || return $?
local status=0
if "$@"; then
status=0
else
status=$?
fi
release_state_lock
return "$status"
}
# Get status of an offer from state file
get_status() {
local id="$1"
if [[ ! -f "$STATE_FILE" ]]; then
echo "none"
return
fi
local status
status=$(awk -F'\t' -v id="$id" '$1 == id { print $3 }' "$STATE_FILE")
echo "${status:-none}"
}
# Get retry count for an offer
get_retries() {
local id="$1"
if [[ ! -f "$STATE_FILE" ]]; then
echo "0"
return
fi
local retries
retries=$(awk -F'\t' -v id="$id" '$1 == id { print $9 }' "$STATE_FILE")
echo "${retries:-0}"
}
# Calculate next report number.
# Caller must hold STATE_LOCK_DIR while this runs.
next_report_num_unlocked() {
local max_num=0
if [[ -d "$REPORTS_DIR" ]]; then
for f in "$REPORTS_DIR"/*.md; do
[[ -f "$f" ]] || continue
local basename
basename=$(basename "$f")
local num="${basename%%-*}"
num=$((10#$num)) # Remove leading zeros for arithmetic
if (( num > max_num )); then
max_num=$num
fi
done
fi
# Also check state file for assigned report numbers
if [[ -f "$STATE_FILE" ]]; then
while IFS=$'\t' read -r _ _ _ _ _ rnum _ _ _; do
[[ "$rnum" == "report_num" || "$rnum" == "-" || -z "$rnum" ]] && continue
local n=$((10#$rnum))
if (( n > max_num )); then
max_num=$n
fi
done < "$STATE_FILE"
fi
printf '%03d' $((max_num + 1))
}
# Update or insert state for an offer.
# Caller must hold STATE_LOCK_DIR while this runs.
update_state_unlocked() {
local id="$1" url="$2" status="$3" started="$4" completed="$5" report_num="$6" score="$7" error="$8" retries="$9"
if [[ ! -f "$STATE_FILE" ]]; then
init_state
fi
local tmp="$STATE_FILE.tmp"
local found=false
# Write header
head -1 "$STATE_FILE" > "$tmp"
# Process existing lines
while IFS=$'\t' read -r sid surl sstatus sstarted scompleted sreport sscore serror sretries; do
[[ "$sid" == "id" ]] && continue # skip header
if [[ "$sid" == "$id" ]]; then
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$id" "$url" "$status" "$started" "$completed" "$report_num" "$score" "$error" "$retries" >> "$tmp"
found=true
else
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$sid" "$surl" "$sstatus" "$sstarted" "$scompleted" "$sreport" "$sscore" "$serror" "$sretries" >> "$tmp"
fi
done < "$STATE_FILE"
if [[ "$found" == "false" ]]; then
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$id" "$url" "$status" "$started" "$completed" "$report_num" "$score" "$error" "$retries" >> "$tmp"
fi
mv "$tmp" "$STATE_FILE"
}
update_state() {
run_with_state_lock update_state_unlocked "$@"
}
is_rate_limit_log() {
local log_file="$1"
grep -Eiq '(rate limit|rate_limit|too many requests|429|quota exceeded|try again later|temporarily unavailable)' "$log_file"
}
is_session_limit_log() {
local log_file="$1"
grep -Eiq '(session limit|resets [0-9:]+[ap]m|usage limit|limit[[:space:]]+reached)' "$log_file"
}
mark_paused_rate_limit() {
local id="$1" url="$2" started_at="$3" report_num="$4" retries="$5" log_file="$6"
local completed_at
completed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
local error_msg
error_msg=$(tail -5 "$log_file" 2>/dev/null | tr '\n' ' ' | cut -c1-200 || echo "session/rate limit reached")
update_state "$id" "$url" "paused_rate_limit" "$started_at" "$completed_at" "$report_num" "-" "$error_msg" "$retries"
printf '%s\t%s\t%s\n' "$id" "$report_num" "$error_msg" > "$PAUSE_FILE"
BATCH_PAUSED=true
}
reserve_report_num_unlocked() {
local id="$1" url="$2" started="$3" retries="$4"
local report_num=""
if report_num=$(next_report_num_unlocked); then
update_state_unlocked "$id" "$url" "processing" "$started" "-" "$report_num" "-" "-" "$retries"
fi
printf '%s\n' "$report_num"
}
reserve_report_num() {
run_with_state_lock reserve_report_num_unlocked "$@"
}
# Process a single offer
process_offer() {
local id="$1" url="$2" source="$3" notes="$4"
local started_at
started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
local retries
retries=$(get_retries "$id")
local report_num
report_num=$(reserve_report_num "$id" "$url" "$started_at" "$retries")
local date
date=$(date +%Y-%m-%d)
# Use mktemp instead of a predictable /tmp path: a fixed name like
# /tmp/batch-jd-${id}.txt is guessable, so an attacker on a shared machine
# could pre-create it as a symlink and redirect or clobber the write.
local jd_file
jd_file="$(mktemp "${TMPDIR:-/tmp}/batch-jd-${id}.XXXXXX")"
echo "--- Processing offer #$id: $url (report $report_num, attempt $((retries + 1)))"
# Build the prompt with placeholders replaced
local prompt
if [[ "$SKIP_PDF" == "true" ]]; then
prompt="Procesa esta oferta de empleo. Ejecuta el pipeline: evaluación A-F + report .md + tracker line. NO generes PDF; en el tracker escribe ❌ en la columna PDF y en el JSON final establece \"pdf\": null."
echo " ⏭️ --skip-pdf set — skipping PDF generation for #$id ($url)"
else
prompt="Procesa esta oferta de empleo. Ejecuta el pipeline completo: evaluación A-F + report .md + PDF + tracker line."
fi
prompt="$prompt URL: $url"
prompt="$prompt JD file: $jd_file"
prompt="$prompt Report number: $report_num"
prompt="$prompt Date: $date"
prompt="$prompt Batch ID: $id"
local log_file="$LOGS_DIR/${report_num}-${id}.log"
# Prepare system prompt with placeholders resolved
local resolved_prompt="$BATCH_DIR/.resolved-prompt-${id}.md"
# Escape sed delimiter characters in variables to prevent substitution breakage
local esc_url esc_jd_file esc_report_num esc_date esc_id
esc_url="${url//\\/\\\\}"
esc_url="${esc_url//|/\\|}"
esc_jd_file="${jd_file//\\/\\\\}"
esc_jd_file="${esc_jd_file//|/\\|}"
esc_report_num="${report_num//|/\\|}"
esc_date="${date//|/\\|}"
esc_id="${id//|/\\|}"
sed \
-e "s|{{URL}}|${esc_url}|g" \
-e "s|{{JD_FILE}}|${esc_jd_file}|g" \
-e "s|{{REPORT_NUM}}|${esc_report_num}|g" \
-e "s|{{DATE}}|${esc_date}|g" \
-e "s|{{ID}}|${esc_id}|g" \
"$PROMPT_FILE" > "$resolved_prompt"
# Inject user-layer personalization into the temporary worker prompt.
# The resolved prompt is gitignored runtime state, so user profile data stays
# out of the system layer while batch scoring matches interactive scoring.
for context_file in "$PROJECT_DIR/modes/_profile.md" "$PROJECT_DIR/config/profile.yml"; do
if [[ -f "$context_file" ]]; then
{
printf '\n\n---\n\n'
printf '## Runtime personalization: %s\n\n' "${context_file#$PROJECT_DIR/}"
sed 's/^/ /' "$context_file"
printf '\n'
} >> "$resolved_prompt"
fi
done
# Launch claude -p worker.
# Model defaults to the Claude Max subscription default unless --model was
# passed. Building the command in an array keeps quoting safe regardless.
# --strict-mcp-config (with no --mcp-config) starts workers with no MCP
# servers: they only evaluate offers and need none. Without it each parallel
# worker inherits the parent session's MCP (e.g. Playwright) and they deadlock
# fighting over the single shared browser when --parallel > 1 (issue #506).
local -a claude_args=(-p --dangerously-skip-permissions --strict-mcp-config)
if [[ -n "$MODEL" ]]; then
claude_args+=(--model "$MODEL")
fi
claude_args+=(--append-system-prompt-file "$resolved_prompt" "$prompt")
local exit_code=0
local terminal_failure_recorded=false
local shim_retries=0
local max_shim_retries=4
while true; do
exit_code=0
claude "${claude_args[@]}" > "$log_file" 2>&1 || exit_code=$?
if [[ $exit_code -eq 0 ]]; then
break
fi
# Check for Claude Code npm shim swap (exit code 127 + command not found)
if [[ $exit_code -eq 127 ]] && grep -qE "(claude: command not found|claude:.*not found|cannot find.*claude)" "$log_file" && (( shim_retries < max_shim_retries )); then
shim_retries=$((shim_retries + 1))
echo " ⏳ Claude command not found (shim swap detected). Retrying in 30s (attempt $shim_retries/$max_shim_retries)..."
sleep 30
continue
fi
if is_session_limit_log "$log_file"; then
mark_paused_rate_limit "$id" "$url" "$started_at" "$report_num" "$retries" "$log_file"
echo " ⏸️ Session/rate limit reached; pausing batch without consuming retry budget."
terminal_failure_recorded=true
break
fi
if is_rate_limit_log "$log_file" && (( retries < MAX_RETRIES )); then
if (( RATE_LIMIT_SLEEP <= 0 )); then
mark_paused_rate_limit "$id" "$url" "$started_at" "$report_num" "$retries" "$log_file"
echo " ⏸️ Rate limited and --rate-limit-sleep is 0; pausing batch without consuming retry budget."
terminal_failure_recorded=true
break
fi
retries=$((retries + 1))
local retry_completed_at
retry_completed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
update_state "$id" "$url" "rate_limited" "$started_at" "$retry_completed_at" "$report_num" "-" "rate-limit; retrying after ${RATE_LIMIT_SLEEP}s" "$retries"
echo " ⏳ Rate limited (attempt $retries/$MAX_RETRIES). Waiting ${RATE_LIMIT_SLEEP}s before retry..."
sleep "$RATE_LIMIT_SLEEP"
continue
fi
break
done
# Cleanup resolved prompt
rm -f "$resolved_prompt"
local completed_at
completed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
if [[ $exit_code -eq 0 ]]; then
# Try to extract score from worker output
local score="-"
local score_match
score_match=$(sed -nE 's/.*"score":[[:space:]]*([0-9.]+).*/\1/p' "$log_file" 2>/dev/null | head -1 || true)
if [[ -n "$score_match" ]]; then
score="$score_match"
fi
# Check min-score gate
if is_decimal_number "$score" && awk -v min="$MIN_SCORE" 'BEGIN{exit !(min > 0)}'; then
if awk -v score="$score" -v min="$MIN_SCORE" 'BEGIN{exit !(score < min)}'; then
update_state "$id" "$url" "skipped" "$started_at" "$completed_at" "$report_num" "$score" "below-min-score" "$retries"
echo " ⏭️ Skipped (score: $score < min-score: $MIN_SCORE)"
return 0
fi
fi
update_state "$id" "$url" "completed" "$started_at" "$completed_at" "$report_num" "$score" "-" "$retries"
echo " ✅ Completed (score: $score, report: $report_num)"
elif [[ "$terminal_failure_recorded" == "false" ]]; then
if (( retries < MAX_RETRIES )); then
retries=$((retries + 1))
fi
local error_msg
error_msg=$(tail -5 "$log_file" 2>/dev/null | tr '\n' ' ' | cut -c1-200 || echo "Unknown error (exit code $exit_code)")
update_state "$id" "$url" "failed" "$started_at" "$completed_at" "$report_num" "-" "$error_msg" "$retries"
echo " ❌ Failed (attempt $retries, exit code $exit_code)"
fi
}
# Merge tracker additions into applications.md
merge_tracker() {
echo ""
echo "=== Merging tracker additions ==="
node "$PROJECT_DIR/merge-tracker.mjs"
echo ""
echo "=== Reconciling pipeline.md ==="
node "$PROJECT_DIR/reconcile-pipeline.mjs" || echo "⚠️ Pipeline reconcile had issues (see above)"
echo ""
echo "=== Verifying pipeline integrity ==="
node "$PROJECT_DIR/verify-pipeline.mjs" || echo "⚠️ Verification found issues (see above)"
}
# Print summary
print_summary() {
echo ""
echo "=== Batch Summary ==="
if [[ ! -f "$STATE_FILE" ]]; then
echo "No state file found."
return
fi
local total=0 completed=0 skipped=0 failed=0 pending=0
local score_sum=0 score_count=0
while IFS=$'\t' read -r sid _ sstatus _ _ _ sscore _ _; do
[[ "$sid" == "id" ]] && continue
total=$((total + 1))
case "$sstatus" in
completed) completed=$((completed + 1))
if is_decimal_number "$sscore"; then
score_sum=$(awk -v sum="$score_sum" -v score="$sscore" 'BEGIN{print sum + score}' 2>/dev/null || echo "$score_sum")
score_count=$((score_count + 1))
fi
;;
skipped) skipped=$((skipped + 1)) ;;
failed) failed=$((failed + 1)) ;;
*) pending=$((pending + 1)) ;;
esac
done < "$STATE_FILE"
echo "Total: $total | Completed: $completed | Skipped: $skipped | Failed: $failed | Pending: $pending"
if (( score_count > 0 )); then
local avg
avg=$(awk -v sum="$score_sum" -v count="$score_count" 'BEGIN{printf "%.1f", sum / count}' 2>/dev/null || echo "N/A")
echo "Average score: $avg/5 ($score_count scored)"
fi
}
print_status_table() {
if [[ ! -f "$STATE_FILE" ]]; then
echo "No state file found at $STATE_FILE"
return
fi
local total=0 completed=0 processing=0 failed=0 pending=0 skipped=0 rate_limited=0 paused_rate_limit=0
local score_sum=0 score_count=0
# Read first line to skip header
local header=true
while IFS=$'\t' read -r sid surl sstatus sstarted scompleted sreport sscore serror sretries || [[ -n "$sid" ]]; do
if [[ "$header" == "true" ]]; then
header=false
continue
fi
[[ -z "$sid" ]] && continue
sstatus="${sstatus%$'\r'}"
sscore="${sscore%$'\r'}"
serror="${serror%$'\r'}"
sreport="${sreport%$'\r'}"
total=$((total + 1))
case "$sstatus" in
completed)
completed=$((completed + 1))
if is_decimal_number "$sscore"; then
score_sum=$(awk -v sum="$score_sum" -v score="$sscore" 'BEGIN{print sum + score}' 2>/dev/null || echo "$score_sum")
score_count=$((score_count + 1))
fi
;;
processing) processing=$((processing + 1)) ;;
failed) failed=$((failed + 1)) ;;
skipped) skipped=$((skipped + 1)) ;;
rate_limited) rate_limited=$((rate_limited + 1)) ;;
paused_rate_limit) paused_rate_limit=$((paused_rate_limit + 1)) ;;
*) pending=$((pending + 1)) ;;
esac
done < "$STATE_FILE"
echo "=== Batch Progress ==="
echo "Total: $total | Completed: $completed | Processing: $processing | Failed: $failed | Pending: $pending | Skipped: $skipped | Rate Limited: $rate_limited | Paused: $paused_rate_limit"
if (( score_count > 0 )); then
local avg
avg=$(awk -v sum="$score_sum" -v count="$score_count" 'BEGIN{printf "%.1f", sum / count}' 2>/dev/null || echo "N/A")
echo "Average score: $avg/5 ($score_count scored)"
fi
echo ""
# Format the per-job table:
# Columns: ID, Status, Report, Score, Target (URL or Error Message)
printf "%-4s | %-17s | %-6s | %-5s | %-40s\n" "ID" "Status" "Report" "Score" "URL / Error"
printf "%-4s+%-19s+%-8s+%-7s+%-42s\n" "----" "-------------------" "--------" "-------" "------------------------------------------"
header=true
while IFS=$'\t' read -r sid surl sstatus sstarted scompleted sreport sscore serror sretries || [[ -n "$sid" ]]; do
if [[ "$header" == "true" ]]; then
header=false
continue
fi
[[ -z "$sid" ]] && continue
sstatus="${sstatus%$'\r'}"
sscore="${sscore%$'\r'}"
serror="${serror%$'\r'}"
sreport="${sreport%$'\r'}"
local target="$surl"
if [[ "$sstatus" == "failed" && -n "$serror" && "$serror" != "-" ]]; then
target="Error: $serror"
fi
# Trim target to fit nicely (e.g. 50 chars)
if (( ${#target} > 50 )); then
target="${target:0:47}..."
fi
printf "%-4s | %-17s | %-6s | %-5s | %-50s\n" "$sid" "$sstatus" "$sreport" "$sscore" "$target"
done < "$STATE_FILE"
}
watch_status() {
local active_pid=""
if [[ -f "$LOCK_FILE" ]]; then
active_pid=$(cat "$LOCK_FILE" 2>/dev/null || true)
fi
if [[ -n "$active_pid" ]] && kill -0 "$active_pid" 2>/dev/null; then
echo "Watching batch-runner (PID $active_pid)... Press Ctrl+C to stop."
while kill -0 "$active_pid" 2>/dev/null; do
clear || printf "\033[c"
echo "=== Watching Batch Progress (PID $active_pid) ==="
print_status_table
sleep 2
done
echo ""
echo "=== Batch runner process (PID $active_pid) has finished ==="
else
echo "No active batch-runner detected."
fi
echo "Showing final status:"
print_status_table
# Chain verify-pipeline.mjs
if [[ -f "$PROJECT_DIR/verify-pipeline.mjs" ]]; then
echo ""
echo "=== Running pipeline verification ==="
node "$PROJECT_DIR/verify-pipeline.mjs" || echo "⚠️ Verification found issues"
fi
}
# Main
main() {
if [[ "$STATUS_ONLY" == "true" ]]; then
check_status_prerequisites
print_status_table
exit 0
fi
if [[ "$WATCH_MODE" == "true" ]]; then
check_status_prerequisites
watch_status
exit 0
fi
check_prerequisites
if [[ "$DRY_RUN" == "false" ]]; then
acquire_lock
rm -f "$PAUSE_FILE"
fi
init_state
# Count input offers (skip header, ignore blank lines)
local total_input
total_input=$(tail -n +2 "$INPUT_FILE" | grep -c '[^[:space:]]' 2>/dev/null || true)
total_input="${total_input:-0}"
if (( total_input == 0 )); then
echo "No offers in $INPUT_FILE. Add offers first."
exit 0
fi
echo "=== career-ops batch runner ==="
if (( LIMIT > 0 )); then
echo "Parallel: $PARALLEL | Max retries: $MAX_RETRIES | Limit: $LIMIT"
else
echo "Parallel: $PARALLEL | Max retries: $MAX_RETRIES"
fi
echo "Input: $total_input offers"
echo ""
# Build list of offers to process
local -a pending_ids=()
local -a pending_urls=()
local -a pending_sources=()
local -a pending_notes=()
while IFS=$'\t' read -r id url source notes; do
[[ "$id" == "id" ]] && continue # skip header
[[ -z "$id" || -z "$url" ]] && continue
# Guard against non-numeric id values
[[ "$id" =~ ^[0-9]+$ ]] || continue
# Skip if before start-from
if (( id < START_FROM )); then
continue
fi
local status
status=$(get_status "$id")
if [[ "$RESUME_PAUSED" == "true" ]]; then
if [[ "$status" != "paused_rate_limit" ]]; then
continue
fi
elif [[ "$RETRY_FAILED" == "true" ]]; then
# Only process failed offers
if [[ "$status" != "failed" ]]; then
continue
fi
# Check retry limit
local retries
retries=$(get_retries "$id")
if (( retries >= MAX_RETRIES )); then
echo "SKIP #$id: max retries ($MAX_RETRIES) reached"
continue
fi
else
# Skip terminal offers
if [[ "$status" == "completed" || "$status" == "skipped" ]]; then
continue
fi
# Paused rate-limit offers resume explicitly with --resume-paused.
if [[ "$status" == "paused_rate_limit" ]]; then
continue
fi
# Skip failed offers that hit retry limit (unless --retry-failed)
if [[ "$status" == "failed" ]]; then
local retries
retries=$(get_retries "$id")
if (( retries >= MAX_RETRIES )); then
echo "SKIP #$id: failed and max retries reached (use --retry-failed to force)"
continue
fi
fi
fi
if (( LIMIT > 0 )) && (( ${#pending_ids[@]} >= LIMIT )); then
break
fi
pending_ids+=("$id")
pending_urls+=("$url")
pending_sources+=("$source")
pending_notes+=("$notes")
done < "$INPUT_FILE"
local pending_count=${#pending_ids[@]}
if (( pending_count == 0 )); then
echo "No offers to process."
print_summary
exit 0
fi
echo "Pending: $pending_count offers"
echo ""
# Dry run: just list
if [[ "$DRY_RUN" == "true" ]]; then
echo "=== DRY RUN (no processing) ==="
for i in "${!pending_ids[@]}"; do
local status
status=$(get_status "${pending_ids[$i]}")
echo " #${pending_ids[$i]}: ${pending_urls[$i]} [${pending_sources[$i]}] (status: $status)"
done
echo ""
echo "Would process $pending_count offers"
exit 0
fi
# Process offers
if (( PARALLEL <= 1 )); then
# Sequential processing
for i in "${!pending_ids[@]}"; do
process_offer "${pending_ids[$i]}" "${pending_urls[$i]}" "${pending_sources[$i]}" "${pending_notes[$i]}"
if [[ "$BATCH_PAUSED" == "true" || -f "$PAUSE_FILE" ]]; then
echo "=== Batch paused: session/rate limit reached. Resume later with --resume-paused. ==="
break
fi
done
else
# Parallel processing with job control
local running=0
local -a pids=()
local -a pid_ids=()
for i in "${!pending_ids[@]}"; do
if [[ "$BATCH_PAUSED" == "true" || -f "$PAUSE_FILE" ]]; then
echo "=== Batch paused: session/rate limit reached. Waiting for running workers, not scheduling new offers. ==="
break
fi
# Wait if we're at parallel limit
while (( running >= PARALLEL )); do
# Wait for any child to finish
for j in "${!pids[@]}"; do
if ! kill -0 "${pids[$j]}" 2>/dev/null; then
wait "${pids[$j]}" 2>/dev/null || true
unset 'pids[j]'
unset 'pid_ids[j]'
running=$((running - 1))
fi
done
# Compact arrays
pids=("${pids[@]}")
pid_ids=("${pid_ids[@]}")
if [[ "$BATCH_PAUSED" == "true" || -f "$PAUSE_FILE" ]]; then
echo "=== Batch paused: session/rate limit reached. Waiting for running workers, not scheduling new offers. ==="
break
fi
sleep 1
done
if [[ "$BATCH_PAUSED" == "true" || -f "$PAUSE_FILE" ]]; then
break
fi
# Launch worker in background
process_offer "${pending_ids[$i]}" "${pending_urls[$i]}" "${pending_sources[$i]}" "${pending_notes[$i]}" &
pids+=($!)
pid_ids+=("${pending_ids[$i]}")
running=$((running + 1))
done
# Wait for remaining workers
for pid in "${pids[@]}"; do
wait "$pid" 2>/dev/null || true
done
fi
# Merge tracker additions
merge_tracker
# Print summary
print_summary
exit 0
}
main "$@"
+11
View File
@@ -0,0 +1,11 @@
id url status started_at completed_at report_num score error retries
1 https://getmatch.ru/vacancies/34414-starshii-frontend-razrabotchik?s=offers completed 2026-07-08T04:20:29Z 2026-07-08T04:23:49Z 066 - - 0
3 https://hh.ru/vacancy/133613060?query=frontend&hhtmFrom=vacancy_search_list completed 2026-07-08T04:20:29Z 2026-07-08T04:22:50Z 067 1.0 - 0
2 https://hirehi.ru/development/frontend-razrabotchik-55966 completed 2026-07-08T04:20:29Z 2026-07-08T04:23:38Z 068 1.2 - 0
4 https://hh.ru/vacancy/133056126?query=frontend&hhtmFrom=vacancy_search_list completed 2026-07-08T04:22:52Z 2026-07-08T04:25:32Z 069 1.2 - 0
5 https://getmatch.ru/vacancies/35037-frontend-software-engineer-bootcamp?s=offers completed 2026-07-08T04:23:40Z 2026-07-08T04:26:23Z 070 1.0 - 0
6 https://career.habr.com/vacancies/1000167021 completed 2026-07-08T04:23:51Z 2026-07-08T04:26:50Z 071 1.8 - 0
7 https://hh.ru/vacancy/119867578?query=frontend&hhtmFrom=vacancy_search_list completed 2026-07-08T04:25:34Z 2026-07-08T04:29:23Z 072 1.7 - 0
8 https://t.me/s/runello_rus_javascript/1443 completed 2026-07-08T04:26:24Z 2026-07-08T04:27:23Z 073 - - 0
9 https://t.me/s/runello_rus_javascript/1445 completed 2026-07-08T04:26:51Z 2026-07-08T04:29:25Z 074 - - 0
10 https://t.me/s/runello_rus_javascript/1448 completed 2026-07-08T04:27:24Z 2026-07-08T04:31:35Z 075 3.0 - 0
1 id url status started_at completed_at report_num score error retries
2 1 https://getmatch.ru/vacancies/34414-starshii-frontend-razrabotchik?s=offers completed 2026-07-08T04:20:29Z 2026-07-08T04:23:49Z 066 - - 0
3 3 https://hh.ru/vacancy/133613060?query=frontend&hhtmFrom=vacancy_search_list completed 2026-07-08T04:20:29Z 2026-07-08T04:22:50Z 067 1.0 - 0
4 2 https://hirehi.ru/development/frontend-razrabotchik-55966 completed 2026-07-08T04:20:29Z 2026-07-08T04:23:38Z 068 1.2 - 0
5 4 https://hh.ru/vacancy/133056126?query=frontend&hhtmFrom=vacancy_search_list completed 2026-07-08T04:22:52Z 2026-07-08T04:25:32Z 069 1.2 - 0
6 5 https://getmatch.ru/vacancies/35037-frontend-software-engineer-bootcamp?s=offers completed 2026-07-08T04:23:40Z 2026-07-08T04:26:23Z 070 1.0 - 0
7 6 https://career.habr.com/vacancies/1000167021 completed 2026-07-08T04:23:51Z 2026-07-08T04:26:50Z 071 1.8 - 0
8 7 https://hh.ru/vacancy/119867578?query=frontend&hhtmFrom=vacancy_search_list completed 2026-07-08T04:25:34Z 2026-07-08T04:29:23Z 072 1.7 - 0
9 8 https://t.me/s/runello_rus_javascript/1443 completed 2026-07-08T04:26:24Z 2026-07-08T04:27:23Z 073 - - 0
10 9 https://t.me/s/runello_rus_javascript/1445 completed 2026-07-08T04:26:51Z 2026-07-08T04:29:25Z 074 - - 0
11 10 https://t.me/s/runello_rus_javascript/1448 completed 2026-07-08T04:27:24Z 2026-07-08T04:31:35Z 075 3.0 - 0
View File
Whitespace-only changes.
+7
View File
@@ -0,0 +1,7 @@
**VK — Старший Frontend-разработчик (React): Score 1.0/5, Skip.**
Hallazgo clave: al hacer WebFetch de la URL, la fuente indica explícitamente que **la vacante está archivada** (VK ya no busca candidatos) — el JD file estaba vacío, así que este dato solo salió al intentar el fallback. Además de eso, dos bloqueadores estructurales independientes del estado de la vacante:
- Stack **React** puro (hooks, context, state management) — sin coincidencia con la especialización Vue 3/Flutter del perfil.
- Nivel **Senior 5+ años** vs ~1.5-2 años de experiencia real de la candidata.
Comp era en realidad excelente (300k+₽, por encima del rango objetivo), pero irrelevante dado lo anterior. Report guardado en `reports/066-vk-2026-07-08.md`, línea de tracker en `batch/tracker-additions/066-vk.tsv` (próximo número: 16), PDF no generado (score bajo el umbral de 3.0).
+32
View File
@@ -0,0 +1,32 @@
## Resultado: oferta duplicada, no se generó nueva evaluación
Esta URL (`https://hh.ru/vacancy/133613060?query=frontend&hhtmFrom=vacancy_search_list`) ya fue evaluada ayer como **report 053** — es la vacante exacta de **Студия Перцы** (Ярославль), programa de trainee para principiantes:
- Стек: React + Sass (verstka/animación para juegos/promo-sitios), no Vue
- Nivel: sin experiencia requerida (programa para estudiantes/graduados) — la candidata ya es Middle con experiencia comercial
- Formato: **solo presencial en Ярославль**, sin remoto (bloqueador duro según `modes/_profile.md`: candidata en Тула, sin reubicación)
- Salario: 40 000–50 000 ₽/mes (etapa de stажировка: 20 000 ₽) — muy por debajo del mínimo objetivo (100 000–120 000+ ₽)
- **Score:** 1.0/5 · **Legitimacy:** High Confidence (empresa real, activa desde 2006, reseñas verificadas)
Confirmé que:
- El TSV de tracker (`batch/tracker-additions/053-studiya-percy.tsv`) ya existe para esta misma empresa+rol, pendiente de `merge-tracker.mjs`.
- Report `053-studiya-percy-2026-07-07.md` cubre exactamente el mismo JD/URL.
Por la regla de integridad del pipeline (nunca duplicar entradas de company+role ya evaluadas), **no generé el report 067 ni un nuevo tracker line** — sería una duplicación exacta del mismo posting evaluado ayer.
```json
{
"status": "skipped_duplicate",
"id": "3",
"report_num": "067",
"company": "Студия Перцы",
"role": "Frontend-разработчик",
"score": 1.0,
"legitimacy": "High Confidence",
"pdf": null,
"report": "reports/053-studiya-percy-2026-07-07.md",
"error": "Duplicate of existing report 053 (same hh.ru vacancy URL, already evaluated 2026-07-07). No new report or tracker line created per pipeline integrity rule."
}
```
Cuando ejecutes `node merge-tracker.mjs`, la entrada #053 (score 1.0/5, no aplicar) se fusionará en `data/applications.md` normalmente — no requiere ninguna acción adicional de esta corrida.
+18
View File
@@ -0,0 +1,18 @@
Evaluación completa. Este es un **SKIP** claro: la posting está marcada por la fuente como archivada/cerrada, el formato es híbrido en Moscú (bloqueo duro de ubicación — la candidata no se muda de Тула), el stack es React/Redux (no Vue.js, su arquetipo primario), y pide iOS nativo en Swift (solo tiene Flutter cross-platform).
```json
{
"status": "completed",
"id": "2",
"report_num": "068",
"company": "Сбер (СберТех)",
"role": "Frontend-разработчик (Middle)",
"score": 1.2,
"legitimacy": "Suspicious",
"pdf": null,
"report": "reports/068-sber-frontend-2026-07-08.md",
"error": null
}
```
Resumen: score 1.2/5, PDF no generado (por debajo del umbral 3.0), report guardado, línea de tracker escrita en `batch/tracker-additions/2.tsv` (pendiente de `node merge-tracker.mjs`).
+18
View File
@@ -0,0 +1,18 @@
Tracker line and report are done. Final JSON summary for the orchestrator:
```json
{
"status": "completed",
"id": "4",
"report_num": "069",
"company": "Т-Банк",
"role": "Frontend Developer (React)",
"score": 1.2,
"legitimacy": "High Confidence",
"pdf": null,
"report": "reports/069-t-bank-2026-07-08.md",
"error": null
}
```
**Resumen:** Т-Банк, Frontend Developer (React) en Kazan — score 1.2/5, SKIP claro. Tres bloqueadores duros: stack React (no Vue, el arquetipo principal de la candidata), seniority requerida de 3-6 años (vs. ~1.5-2 años reales), y ubicación en Kazan que exige reubicación (política explícita de no-reubicación). No se generó PDF (por debajo del umbral 3.0). Report guardado en `reports/069-t-bank-2026-07-08.md`, línea de tracker en `batch/tracker-additions/4.tsv` (pendiente de `node merge-tracker.mjs`).
+18
View File
@@ -0,0 +1,18 @@
Now the final JSON summary for the orchestrator:
```json
{
"status": "completed",
"id": "5",
"report_num": "070",
"company": "Exness",
"role": "Frontend Software Engineer (Bootcamp)",
"score": 1.0,
"legitimacy": "High Confidence",
"pdf": null,
"report": "reports/070-exness-2026-07-08.md",
"error": null
}
```
**Resumen:** Score 1.0/5 — SKIP. Esta oferta (Exness, Limassol/Chipre, oficina) es un **duplicado exacto** del reporte 055 (2026-07-07, mismo ID de vacante 35037), confirmado en `data/scan-history.tsv`. Bloqueadores duros sin cambios: puesto presencial en Chipre que exige residencia previa allí (política de no reubicación de la candidata) + stack React (la candidata trabaja en Vue 3). No se generó PDF (score bajo el umbral 3.0). Report y línea de tracker guardados; falta correr `node merge-tracker.mjs` para fusionar el batch completo.
+19
View File
@@ -0,0 +1,19 @@
Report saved. Score is well below the 3.0 PDF auto-generation threshold anyway (and it's a duplicate), so no PDF was generated, and no tracker line was written for batch ID 6 to prevent a duplicate row in `applications.md`.
```json
{
"status": "completed",
"id": "6",
"report_num": "071",
"company": "РТК-ЦОД",
"role": "Ведущий фронтенд-разработчик (React)",
"score": 1.8,
"legitimacy": "High Confidence",
"pdf": null,
"report": "reports/071-rtk-tsod-2026-07-08.md",
"error": null,
"note": "Duplicate of report 056 (same URL/JD, evaluated 2026-07-07). Tracker line intentionally skipped to avoid a duplicate applications.md row — company+role already covered by batch/tracker-additions/056-rtk-tsod.tsv."
}
```
**Resumen:** РТК-ЦОД — Ведущий фронтенд-разработчик (React), 1.8/5, no recomendado. Es un duplicado exacto de la oferta ya evaluada ayer (reporte 056) — mismo URL, misma JD. Bloqueadores: stack React/Next.js/GraphQL sin ningún proyecto React en el CV (todo el perfil es Vue 3 + Flutter), y nivel Senior/Lead vs. Middle del candidato. No se generó PDF ni línea de tracker nueva para evitar duplicar la entrada de РТК-ЦОД que ya está pendiente de merge desde el reporte 056.
+16
View File
@@ -0,0 +1,16 @@
```json
{
"status": "completed",
"id": "7",
"report_num": "072",
"company": "Т-Банк",
"role": "Frontend-разработчик React (Junior)",
"score": 1.7,
"legitimacy": "Proceed with Caution",
"pdf": null,
"report": "reports/072-t-bank-react-junior-perm-2026-07-08.md",
"error": null
}
```
Evaluated Т-Банк's Junior React frontend role (Perm, remote-friendly) — scored 1.7/5, recommendation SKIP. Main blockers: role requires React (candidate's primary stack is Vue.js, no commercial React experience) and the Junior scope/comp sits below her actual level of ownership (~100k+ ₽/mes minimum vs. Junior market rates of ~55-80k). No PDF generated (below 3.0 threshold). Report saved to `reports/072-t-bank-react-junior-perm-2026-07-08.md`, tracker line queued at `batch/tracker-additions/7.tsv` — run `node merge-tracker.mjs` when the batch finishes.
+17
View File
@@ -0,0 +1,17 @@
No report, PDF, or tracker line was created for this ID since there is no actual job content to evaluate.
```json
{
"status": "failed",
"id": "8",
"report_num": "073",
"company": "unknown",
"role": "unknown",
"score": null,
"pdf": null,
"report": null,
"error": "JD file /tmp/batch-jd-8.okc7U0 is empty (0 bytes), and the fallback URL https://t.me/s/runello_rus_javascript/1443 does not contain a job posting — message #1443 in that channel is a Runello bot promotional/service message ('upload your resume to @RunelloBot'), not an actual JD. No evaluable content exists for this offer."
}
```
Report #073 was not generated — this batch ID has no real job description to score. Recommend removing this URL from the batch input or re-scraping the correct message ID for that channel entry.
+1
View File
@@ -0,0 +1 @@
**Resumen:** Esta oferta (`t.me/s/runello_rus_javascript/1445`) es un duplicado exacto del post ya evaluado como reporte 059 el 2026-07-07 — mismo empleador anónimo, mismo stack (React + Python/LLM/LangChain/RAG), mismo bot de Runello como único canal de aplicación. Score 1.0/5, legitimidad **Suspicious** (empleador no revelado + aplicación forzada vía bot de terceros), sin match real con el perfil Vue/Flutter de la candidata. No se generó PDF (por debajo del umbral 3.0). Reporte guardado en `reports/074-unknown-employer-runello-2026-07-08.md` y línea de tracker en `batch/tracker-additions/9.tsv`. Recomendación: **no aplicar**.
+25
View File
@@ -0,0 +1,25 @@
**Duplicate detected — no new report/PDF created.**
URL `https://t.me/s/runello_rus_javascript/1448` (batch ID 10) is identical to a posting already fully evaluated yesterday as **report 060** (`reports/060-konsultantplyus-2026-07-07.md`, ЗАО КонсультантПлюс — Веб разработчик Vue/Js, score 3.0/5, Proceed with Caution). The JD file was empty and WebFetch returned the same thin Telegram/Runello preview (only title + company blurb — no requirements, salary, or location), so there is no new information that would change that evaluation.
Per the pipeline-integrity rule ("never create duplicate entries for the same company+role"), I did not generate report 075 or a new PDF. Instead I found that report 060's tracker line had never been written (a pre-existing gap — its score never made it into `data/applications.md`), so I closed that gap:
- Wrote `batch/tracker-additions/10.tsv` pointing to the existing report 060 (score 3.0/5, ❌ PDF — the PDF was never actually compiled despite the report claiming it hit the 3.0 threshold; only the HTML exists in `output/`).
- Run `node merge-tracker.mjs` when convenient to fold this into `data/applications.md`.
```json
{
"status": "completed",
"id": "10",
"report_num": "075",
"company": "ЗАО КонсультантПлюс",
"role": "Веб разработчик Vue/Js",
"score": 3.0,
"legitimacy": "Proceed with Caution",
"pdf": null,
"report": "reports/060-konsultantplyus-2026-07-07.md",
"error": "duplicate_of_report_060: identical URL/JD already evaluated 2026-07-07; no new report or PDF generated; wrote missing tracker line for existing report 060 instead"
}
```
If the scanner is re-feeding already-processed URLs into the batch inbox, it's worth checking `data/scan-history.tsv` dedup logic for why this one resurfaced.
View File
Whitespace-only changes.
@@ -0,0 +1 @@
001 2026-07-06 Яндекс (Лавка) Разработчик фронтенда (Vue/React) в Лавку Evaluated 1.0/5 ❌ [001](reports/001-yandex-lavka-2026-07-06.md) Сильный стек-мэтч (Vue3+тесты+legacy+ритейл), но офис/гибрид в Москве/СПб без удалёнки — жёсткий локационный блокер
1 001 2026-07-06 Яндекс (Лавка) Разработчик фронтенда (Vue/React) в Лавку Evaluated 1.0/5 ❌ [001](reports/001-yandex-lavka-2026-07-06.md) Сильный стек-мэтч (Vue3+тесты+legacy+ритейл), но офис/гибрид в Москве/СПб без удалёнки — жёсткий локационный блокер
@@ -0,0 +1 @@
002 2026-07-06 VK Frontend-разработчик (MAX) Evaluated 2.3/5 ❌ [002](reports/002-vk-max-2026-07-06.md) Удалёнка доступна и уровень Middle совпадает, но ядро роли — React-экосистема, которой нет в CV (стек кандидата — Vue)
1 002 2026-07-06 VK Frontend-разработчик (MAX) Evaluated 2.3/5 ❌ [002](reports/002-vk-max-2026-07-06.md) Удалёнка доступна и уровень Middle совпадает, но ядро роли — React-экосистема, которой нет в CV (стек кандидата — Vue)
@@ -0,0 +1 @@
003 2026-07-06 VK Старший Frontend-разработчик сообщества (ВКонтакте) Evaluated 1.8/5 ❌ [003](reports/003-vk-vkontakte-senior-2026-07-06.md) Двойной разрыв: требуется Senior/4+ лет и React, у кандидата Middle/~2.5 года и стек Vue; не рекомендуется откликаться
1 003 2026-07-06 VK Старший Frontend-разработчик сообщества (ВКонтакте) Evaluated 1.8/5 ❌ [003](reports/003-vk-vkontakte-senior-2026-07-06.md) Двойной разрыв: требуется Senior/4+ лет и React, у кандидата Middle/~2.5 года и стек Vue; не рекомендуется откликаться
@@ -0,0 +1 @@
004 2026-07-06 Ozon Frontend-разработчик (ERP и учётные системы) Evaluated 4.2/5 ✅ [004](reports/004-ozon-2026-07-06.md) Лучший фит пакета: Middle Vue3/TS, удалённо, formal "3+ года JS" порог выше факта (~2 года)
Can't render this file because it contains an unexpected character in line 1 and column 225.
@@ -0,0 +1 @@
005 2026-07-06 Ozon Стажёр Frontend-разработчик (Маркетинговая платформа) Evaluated 2.5/5 ❌ [005](reports/005-ozon-2026-07-06.md) Уровень "стажёр" ниже Middle-профиля кандидата
Can't render this file because it contains an unexpected character in line 1 and column 187.
@@ -0,0 +1 @@
006 2026-07-06 Wildberries Frontend-разработчик (Платформа) Evaluated 2.8/5 ❌ [006](reports/006-wildberries-2026-07-06.md) Стек React, не Vue — adjacent архетип, не primary
1 006 2026-07-06 Wildberries Frontend-разработчик (Платформа) Evaluated 2.8/5 ❌ [006](reports/006-wildberries-2026-07-06.md) Стек React, не Vue — adjacent архетип, не primary
@@ -0,0 +1 @@
007 2026-07-06 Wildberries (RWB) Senior Frontend developer (Angular) в WB Job Evaluated 1.7/5 ❌ [007](reports/007-wildberries-2026-07-06.md) Удалённо, но нет Angular и стаж вдвое короче требуемых 4 лет — строго не рекомендуется
1 007 2026-07-06 Wildberries (RWB) Senior Frontend developer (Angular) в WB Job Evaluated 1.7/5 ❌ [007](reports/007-wildberries-2026-07-06.md) Удалённо, но нет Angular и стаж вдвое короче требуемых 4 лет — строго не рекомендуется
@@ -0,0 +1 @@
008 2026-07-06 Т-Банк Frontend-разработчик — React Evaluated 2.0/5 ❌ [008](reports/008-tbank-react-2026-07-06.md) Удалённо/гибко, но нет коммерческого React (обязательно от 4 лет) — строго не рекомендуется
1 008 2026-07-06 Т-Банк Frontend-разработчик — React Evaluated 2.0/5 ❌ [008](reports/008-tbank-react-2026-07-06.md) Удалённо/гибко, но нет коммерческого React (обязательно от 4 лет) — строго не рекомендуется
@@ -0,0 +1 @@
009 2026-07-06 Т-Банк Frontend-разработчик — Angular Evaluated 2.3/5 ❌ [009](reports/009-tbank-angular-2026-07-06.md) Удалённо/гибко, Middle/Senior, но нет Angular (обязательно от 2 лет) — строго не рекомендуется
1 009 2026-07-06 Т-Банк Frontend-разработчик — Angular Evaluated 2.3/5 ❌ [009](reports/009-tbank-angular-2026-07-06.md) Удалённо/гибко, Middle/Senior, но нет Angular (обязательно от 2 лет) — строго не рекомендуется
@@ -0,0 +1 @@
010 2026-07-06 МТС Senior Frontend разработчик [NBA.Банк] Evaluated 1.6/5 ❌ [010](reports/010-mts-senior-frontend-nba-bank-2026-07-06.md) Сильное совпадение по Vue3/TS, но гибрид в Москве нарушает жёсткое правило локации (Тула, без переезда) — балл тонет до 1.6
1 010 2026-07-06 МТС Senior Frontend разработчик [NBA.Банк] Evaluated 1.6/5 ❌ [010](reports/010-mts-senior-frontend-nba-bank-2026-07-06.md) Сильное совпадение по Vue3/TS, но гибрид в Москве нарушает жёсткое правило локации (Тула, без переезда) — балл тонет до 1.6
@@ -0,0 +1 @@
011 2026-07-06 МТС Intern Frontend dev [Governance] Evaluated 2.0/5 ❌ [011](reports/011-mts-intern-frontend-governance-2026-07-06.md) Полностью удалённо (ок для локации), но стек React (не Vue) и уровень "стажёр без опыта" не подходят Middle-кандидату
Can't render this file because it contains an unexpected character in line 1 and column 255.
@@ -0,0 +1 @@
012 2026-07-06 МТС Middle Frontend Developer (Vue) [GARNET] Evaluated 1.9/5 ❌ [012](reports/012-mts-middle-frontend-vue-garnet-2026-07-06.md) Лучшее стековое совпадение в пакете (Vue3+Composition API+TS), но тот же блокер: гибрид в Москве, не Тула
1 012 2026-07-06 МТС Middle Frontend Developer (Vue) [GARNET] Evaluated 1.9/5 ❌ [012](reports/012-mts-middle-frontend-vue-garnet-2026-07-06.md) Лучшее стековое совпадение в пакете (Vue3+Composition API+TS), но тот же блокер: гибрид в Москве, не Тула
@@ -0,0 +1 @@
013 2026-07-06 МТС Senior Frontend (ON Медиа) Evaluated 1.7/5 ❌ [013](reports/013-mts-on-media-2026-07-06.md) Стек Angular не совпадает с primary-архетипом (Vue.js)
1 013 2026-07-06 МТС Senior Frontend (ON Медиа) Evaluated 1.7/5 ❌ [013](reports/013-mts-on-media-2026-07-06.md) Стек Angular не совпадает с primary-архетипом (Vue.js)
@@ -0,0 +1 @@
014 2026-07-06 МТС Senior Frontend разработчик Evaluated 1.6/5 ❌ [014](reports/014-mts-frontend-razrabotchik-2026-07-06.md) Стек Vue совпадает, но гибрид/офис в Москве — жёсткий локационный блокер
1 014 2026-07-06 МТС Senior Frontend разработчик Evaluated 1.6/5 ❌ [014](reports/014-mts-frontend-razrabotchik-2026-07-06.md) Стек Vue совпадает, но гибрид/офис в Москве — жёсткий локационный блокер
@@ -0,0 +1 @@
015 2026-07-06 Kaspersky JavaScript Developer (KES) Evaluated 3.2/5 ✅ [015](reports/015-kaspersky-kes-2026-07-06.md) React+Node enterprise-роль, удалённо по всей России, комп сильный, стек — умеренный разрыв
1 015 2026-07-06 Kaspersky JavaScript Developer (KES) Evaluated 3.2/5 ✅ [015](reports/015-kaspersky-kes-2026-07-06.md) React+Node enterprise-роль, удалённо по всей России, комп сильный, стек — умеренный разрыв
@@ -0,0 +1 @@
016 2026-07-07 КСК ЭЛКОМ Разработчик Flutter (Dart) Evaluated 1.0/5 ❌ [016](reports/016-ksk-elkom-2026-07-07.md) Офис в СПб без удалёнки — блокер по location-политике
1 016 2026-07-07 КСК ЭЛКОМ Разработчик Flutter (Dart) Evaluated 1.0/5 ❌ [016](reports/016-ksk-elkom-2026-07-07.md) Офис в СПб без удалёнки — блокер по location-политике
@@ -0,0 +1 @@
017 2026-07-07 АО "ЦФТ" Frontend-разработчик Vue Evaluated 2.1/5 ❌ [017](reports/017-tsft-2026-07-07.md) Сильное совпадение по Vue3/TS/Pinia, но стажевый пробел (3-6 лет vs ~1.7), формат работы не подтверждён (Новосибирск), зарплата не раскрыта
Can't render this file because it contains an unexpected character in line 1 and column 21.
@@ -0,0 +1 @@
018 2026-07-07 Яндекс Fullstack-разработчик в команду frontend-инфраструктуры Evaluated 1.0/5 ❌ [018](reports/018-yandex-frontend-infra-2026-07-07.md) Екатеринбург/Москва, гибрид/офис без удалёнки + роль platform-tooling, не UI
1 018 2026-07-07 Яндекс Fullstack-разработчик в команду frontend-инфраструктуры Evaluated 1.0/5 ❌ [018](reports/018-yandex-frontend-infra-2026-07-07.md) Екатеринбург/Москва, гибрид/офис без удалёнки + роль platform-tooling, не UI
@@ -0,0 +1 @@
019 2026-07-07 Яндекс Разработчик фронтенда в команду Яндекс Кит Evaluated 1.0/5 ❌ [019](reports/019-yandex-kit-2026-07-07.md) СПб/Москва без удалёнки + стек React вместо Vue
1 019 2026-07-07 Яндекс Разработчик фронтенда в команду Яндекс Кит Evaluated 1.0/5 ❌ [019](reports/019-yandex-kit-2026-07-07.md) СПб/Москва без удалёнки + стек React вместо Vue
@@ -0,0 +1 @@
020 2026-07-07 Яндекс Мобильный разработчик (Android/iOS) Payment SDK в Финтех Evaluated 1.0/5 ❌ [020](reports/020-yandex-payment-sdk-2026-07-07.md) СПб/Москва без удалёнки + нативный Kotlin/Swift вместо Flutter
1 020 2026-07-07 Яндекс Мобильный разработчик (Android/iOS) Payment SDK в Финтех Evaluated 1.0/5 ❌ [020](reports/020-yandex-payment-sdk-2026-07-07.md) СПб/Москва без удалёнки + нативный Kotlin/Swift вместо Flutter
@@ -0,0 +1 @@
021 2026-07-07 Яндекс Стажёр в команду разработки AI-инфраструктуры фронтенда Яндекс 360 Evaluated 1.0/5 ❌ [021](reports/021-yandex-360-intern-2026-07-07.md) Москва, только офис, стажировка на React — не подходит по уровню и локации
1 021 2026-07-07 Яндекс Стажёр в команду разработки AI-инфраструктуры фронтенда Яндекс 360 Evaluated 1.0/5 ❌ [021](reports/021-yandex-360-intern-2026-07-07.md) Москва, только офис, стажировка на React — не подходит по уровню и локации
@@ -0,0 +1 @@
022 2026-07-07 Яндекс Фронтенд-разработчик в еком-сценарии Поиска Evaluated 1.0/5 ❌ [022](reports/022-yandex-ecom-search-2026-07-07.md) Хороший содержательный фит, но СПб/Москва без удалёнки — блокер
1 022 2026-07-07 Яндекс Фронтенд-разработчик в еком-сценарии Поиска Evaluated 1.0/5 ❌ [022](reports/022-yandex-ecom-search-2026-07-07.md) Хороший содержательный фит, но СПб/Москва без удалёнки — блокер
@@ -0,0 +1 @@
023 2026-07-07 Яндекс Разработчик фронтенда в Яндекс 360 Evaluated 1.0/5 ❌ [023](reports/023-yandex-360-frontend-2026-07-07.md) СПб/Москва без удалёнки + React вместо Vue
1 023 2026-07-07 Яндекс Разработчик фронтенда в Яндекс 360 Evaluated 1.0/5 ❌ [023](reports/023-yandex-360-frontend-2026-07-07.md) СПб/Москва без удалёнки + React вместо Vue
@@ -0,0 +1 @@
024 2026-07-07 Бизнес Технологии Senior/Tech Lead frontend-разработчик Evaluated 1.5/5 ❌ [024](reports/024-biznes-tehnologii-2026-07-07.md) Отличная компенсация (300-350к на руки), но требование 10 лет опыта vs 1.7 года у кандидата - критический разрыв
1 024 2026-07-07 Бизнес Технологии Senior/Tech Lead frontend-разработчик Evaluated 1.5/5 ❌ [024](reports/024-biznes-tehnologii-2026-07-07.md) Отличная компенсация (300-350к на руки), но требование 10 лет опыта vs 1.7 года у кандидата - критический разрыв
@@ -0,0 +1 @@
025 2026-07-07 ИП Каржева М.К. Frontend-разработчик Evaluated 2.6/5 ❌ [025](reports/025-karzheva-2026-07-07.md) WebFetch дважды не смог извлечь JD; компенсация (~113-130к на руки расч.) в целевом диапазоне, но легитимность/детали не проверены
1 025 2026-07-07 ИП Каржева М.К. Frontend-разработчик Evaluated 2.6/5 ❌ [025](reports/025-karzheva-2026-07-07.md) WebFetch дважды не смог извлечь JD; компенсация (~113-130к на руки расч.) в целевом диапазоне, но легитимность/детали не проверены
@@ -0,0 +1 @@
026 2026-07-07 Dev Company 23 Frontend разработчик Senior Evaluated 2.4/5 ❌ [026](reports/026-dev-company-23-2026-07-07.md) Сильнейшее совпадение стека (Vue3/TS/Pinia) и отличная компенсация (от 270к gross), но заявлено 4-6 лет опыта vs 1.7 года у кандидата
1 026 2026-07-07 Dev Company 23 Frontend разработчик Senior Evaluated 2.4/5 ❌ [026](reports/026-dev-company-23-2026-07-07.md) Сильнейшее совпадение стека (Vue3/TS/Pinia) и отличная компенсация (от 270к gross), но заявлено 4-6 лет опыта vs 1.7 года у кандидата
@@ -0,0 +1 @@
027 2026-07-07 Sakura Games Fullstack Developer (Node.js/TS + React/Next.js) Evaluated 1.9/5 ❌ [027](reports/027-sakura-games-2026-07-07.md) Стек React/Next.js (не Vue) - нет production опыта; заявлено 6+ лет vs 1.7 года; зарплата не раскрыта; контракт проектный 3-6 мес
1 027 2026-07-07 Sakura Games Fullstack Developer (Node.js/TS + React/Next.js) Evaluated 1.9/5 ❌ [027](reports/027-sakura-games-2026-07-07.md) Стек React/Next.js (не Vue) - нет production опыта; заявлено 6+ лет vs 1.7 года; зарплата не раскрыта; контракт проектный 3-6 мес
@@ -0,0 +1 @@
028 2026-07-07 Selecty Senior Frontend Developer (Vue.js/Nuxt.js) Evaluated 2.0/5 ❌ [028](reports/028-selecty-2026-07-07.md) Удалёнка ок, но 5 лет опыта + английский B2+ mandatory — два hard-блокера
1 028 2026-07-07 Selecty Senior Frontend Developer (Vue.js/Nuxt.js) Evaluated 2.0/5 ❌ [028](reports/028-selecty-2026-07-07.md) Удалёнка ок, но 5 лет опыта + английский B2+ mandatory — два hard-блокера
@@ -0,0 +1 @@
029 2026-07-07 Bnovo Senior Frontend разработчик Evaluated 2.5/5 ❌ [029](reports/029-bnovo-2026-07-07.md) Удалёнка подтверждена, отличная репутация, но 4+ года опыта + tech-lead трек выше уровня
1 029 2026-07-07 Bnovo Senior Frontend разработчик Evaluated 2.5/5 ❌ [029](reports/029-bnovo-2026-07-07.md) Удалёнка подтверждена, отличная репутация, но 4+ года опыта + tech-lead трек выше уровня
@@ -0,0 +1 @@
030 2026-07-07 ООО Бизнес Технологии Middle frontend-разработчик Evaluated 4.2/5 ✅ [030](reports/030-biznes-tehnologii-middle-2026-07-07.md) Гибкий формат (вкл. удалёнку), 150-200к на руки, стек совпадает — рекомендуется откликнуться
1 030 2026-07-07 ООО Бизнес Технологии Middle frontend-разработчик Evaluated 4.2/5 ✅ [030](reports/030-biznes-tehnologii-middle-2026-07-07.md) Гибкий формат (вкл. удалёнку), 150-200к на руки, стек совпадает — рекомендуется откликнуться
@@ -0,0 +1 @@
032 2026-07-07 ИП Шевченко М.Ю. Flutter-разработчик Evaluated 2.7/5 ❌ [032](reports/032-ip-shevchenko-2026-07-07.md) Проектный ГПХ-контракт, заявлен стаж 3-6 лет vs фактический ~1.5-2 года, агрессивный дедлайн (10% готовности за 8 недель)
1 032 2026-07-07 ИП Шевченко М.Ю. Flutter-разработчик Evaluated 2.7/5 ❌ [032](reports/032-ip-shevchenko-2026-07-07.md) Проектный ГПХ-контракт, заявлен стаж 3-6 лет vs фактический ~1.5-2 года, агрессивный дедлайн (10% готовности за 8 недель)
@@ -0,0 +1 @@
033 2026-07-07 МЕДИА ПОТОК Fullstack-разработчик мобильных приложений Evaluated 4.1/5 ✅ [033](reports/033-media-potok-2026-07-07.md) Стаж 1-3 года и стек (Flutter+Node.js) совпадают почти идеально, полностью гибкий формат, вилка 100-200к на руки
1 033 2026-07-07 МЕДИА ПОТОК Fullstack-разработчик мобильных приложений Evaluated 4.1/5 ✅ [033](reports/033-media-potok-2026-07-07.md) Стаж 1-3 года и стек (Flutter+Node.js) совпадают почти идеально, полностью гибкий формат, вилка 100-200к на руки
@@ -0,0 +1 @@
034 2026-07-07 Playneta Senior Flutter Developer Evaluated 1.8/5 ❌ [034](reports/034-playneta-2026-07-07.md) Senior-геймдев роль (5+ лет, рендеринг/анимация 60-120fps) vs фактический опыт ~1.5-2 года без игрового бэкграунда; 2 дублирующих объявления замечены
1 034 2026-07-07 Playneta Senior Flutter Developer Evaluated 1.8/5 ❌ [034](reports/034-playneta-2026-07-07.md) Senior-геймдев роль (5+ лет, рендеринг/анимация 60-120fps) vs фактический опыт ~1.5-2 года без игрового бэкграунда; 2 дублирующих объявления замечены
@@ -0,0 +1 @@
035 2026-07-07 Noroots Fullstack-разработчик Evaluated N/A ❌ [035](reports/035-noroots-2026-07-07.md) Вакансия недоступна для просмотра (403/ограничен доступ) — оценка невозможна
1 035 2026-07-07 Noroots Fullstack-разработчик Evaluated N/A ❌ [035](reports/035-noroots-2026-07-07.md) Вакансия недоступна для просмотра (403/ограничен доступ) — оценка невозможна
@@ -0,0 +1 @@
036 2026-07-07 МТС Senior Frontend developer [МТС Маркетолог] Evaluated 1.8/5 ❌ [036](reports/036-mts-marketolog-2026-07-07.md) React vs Vue stack mismatch + Senior 6+ лет vs ~2 года опыта — не рекомендуется
1 036 2026-07-07 МТС Senior Frontend developer [МТС Маркетолог] Evaluated 1.8/5 ❌ [036](reports/036-mts-marketolog-2026-07-07.md) React vs Vue stack mismatch + Senior 6+ лет vs ~2 года опыта — не рекомендуется
@@ -0,0 +1 @@
037 2026-07-07 МТС Middle Flutter Developer [GOODOK] Evaluated 3.6/5 ✅ [037](reports/037-mts-goodok-2026-07-07.md) Лучший фит партии: BLoC/iOS-портирование совпадают, remote-опция, но ниже порога 4.0
1 037 2026-07-07 МТС Middle Flutter Developer [GOODOK] Evaluated 3.6/5 ✅ [037](reports/037-mts-goodok-2026-07-07.md) Лучший фит партии: BLoC/iOS-портирование совпадают, remote-опция, но ниже порога 4.0
@@ -0,0 +1 @@
038 2026-07-07 МТС Fullstack developer React/Node.JS [MWS Tables] Evaluated 1.9/5 ❌ [038](reports/038-mts-mwstables-2026-07-07.md) React + тяжёлый DevOps (K8s/Helm) далеко от Vue + ознакомительного Node — не рекомендуется
1 038 2026-07-07 МТС Fullstack developer React/Node.JS [MWS Tables] Evaluated 1.9/5 ❌ [038](reports/038-mts-mwstables-2026-07-07.md) React + тяжёлый DevOps (K8s/Helm) далеко от Vue + ознакомительного Node — не рекомендуется
@@ -0,0 +1 @@
039 2026-07-07 МТС Стажер Fullstack QA [MWS Octapi] Evaluated 1.0/5 ❌ [039](reports/039-mts-octapi-2026-07-07.md) Hard location blocker (Москва гибрид, без remote) + программа для студентов, не для выпускников
1 039 2026-07-07 МТС Стажер Fullstack QA [MWS Octapi] Evaluated 1.0/5 ❌ [039](reports/039-mts-octapi-2026-07-07.md) Hard location blocker (Москва гибрид, без remote) + программа для студентов, не для выпускников
@@ -0,0 +1 @@
040 2026-07-07 МТС Junior Fullstack-аналитик [CICADA8] Evaluated 1.0/5 ❌ [040](reports/040-mts-cicada8-2026-07-07.md) Hard location blocker (офис Москва, без remote) + роль аналитика не входит в архетипы кандидата
1 040 2026-07-07 МТС Junior Fullstack-аналитик [CICADA8] Evaluated 1.0/5 ❌ [040](reports/040-mts-cicada8-2026-07-07.md) Hard location blocker (офис Москва, без remote) + роль аналитика не входит в архетипы кандидата
@@ -0,0 +1 @@
051 2026-07-07 VK Старший Frontend-разработчик (React, мессенджер) Evaluated 1.5/5 ❌ [051](reports/051-vk-messenger-frontend-2026-07-07.md) Архивная вакансия, стек React (не Vue) и требование 5+ лет — тройной hard blocker, не рекомендуется откликаться
1 051 2026-07-07 VK Старший Frontend-разработчик (React, мессенджер) Evaluated 1.5/5 ❌ [051](reports/051-vk-messenger-frontend-2026-07-07.md) Архивная вакансия, стек React (не Vue) и требование 5+ лет — тройной hard blocker, не рекомендуется откликаться
@@ -0,0 +1 @@
052 2026-07-07 Сбер Frontend Developer Evaluated 1.0/5 ❌ [052](reports/052-sber-2026-07-07.md) Стек React/Redux/Swift/AI-моделей не совпадает с Vue/Flutter кандидата; роль в Москве требует переезда (hard blocker); листинг архивирован по данным hirehi.ru
1 052 2026-07-07 Сбер Frontend Developer Evaluated 1.0/5 ❌ [052](reports/052-sber-2026-07-07.md) Стек React/Redux/Swift/AI-моделей не совпадает с Vue/Flutter кандидата; роль в Москве требует переезда (hard blocker); листинг архивирован по данным hirehi.ru
@@ -0,0 +1 @@
053 2026-07-07 Студия Перцы Frontend-разработчик Evaluated 1.0/5 ❌ [053](reports/053-studiya-percy-2026-07-07.md) Стажёрская программа для новичков, офис только в Ярославле без удалёнки, ЗП 40-50к — не совпадает по формату работы и уровню
1 053 2026-07-07 Студия Перцы Frontend-разработчик Evaluated 1.0/5 ❌ [053](reports/053-studiya-percy-2026-07-07.md) Стажёрская программа для новичков, офис только в Ярославле без удалёнки, ЗП 40-50к — не совпадает по формату работы и уровню
@@ -0,0 +1 @@
054 2026-07-07 Т-Банк Frontend Developer (React) Evaluated 1.0/5 ❌ [054](reports/054-t-bank-2026-07-07.md) Hard location blocker — гибрид офис Казань, без переезда; стек React vs Vue у кандидата
1 054 2026-07-07 Т-Банк Frontend Developer (React) Evaluated 1.0/5 ❌ [054](reports/054-t-bank-2026-07-07.md) Hard location blocker — гибрид офис Казань, без переезда; стек React vs Vue у кандидата
@@ -0,0 +1 @@
055 2026-07-07 Exness Frontend Software Engineer (Bootcamp) Evaluated 1.0/5 ❌ [055](reports/055-exness-2026-07-07.md) Офис в Лимасоле (Кипр), требует уже жить на Кипре, стек React не Vue — hard location blocker, не рекомендуется
1 055 2026-07-07 Exness Frontend Software Engineer (Bootcamp) Evaluated 1.0/5 ❌ [055](reports/055-exness-2026-07-07.md) Офис в Лимасоле (Кипр), требует уже жить на Кипре, стек React не Vue — hard location blocker, не рекомендуется
@@ -0,0 +1 @@
056 2026-07-07 РТК-ЦОД Ведущий фронтенд-разработчик (React) Evaluated 1.8/5 ❌ [056](reports/056-rtk-tsod-2026-07-07.md) Стек React/Next.js/GraphQL и уровень Lead не совпадают с профилем (Vue 3, Middle) — не рекомендуется откликаться
1 056 2026-07-07 РТК-ЦОД Ведущий фронтенд-разработчик (React) Evaluated 1.8/5 ❌ [056](reports/056-rtk-tsod-2026-07-07.md) Стек React/Next.js/GraphQL и уровень Lead не совпадают с профилем (Vue 3, Middle) — не рекомендуется откликаться
@@ -0,0 +1 @@
057 2026-07-07 Т-Банк Frontend-разработчик React (junior) Evaluated 2.4/5 ❌ [057](reports/057-t-bank-2026-07-07.md) Junior-грейд vs Middle кандидата, стек React не совпадает с Vue, зарплата не раскрыта — не рекомендуется
1 057 2026-07-07 Т-Банк Frontend-разработчик React (junior) Evaluated 2.4/5 ❌ [057](reports/057-t-bank-2026-07-07.md) Junior-грейд vs Middle кандидата, стек React не совпадает с Vue, зарплата не раскрыта — не рекомендуется
@@ -0,0 +1 @@
059 2026-07-07 Unknown (Runello aggregator) Fullstack React Developer Evaluated 1.0/5 ❌ [059](reports/059-unknown-employer-runello-2026-07-07.md) Работодатель не назван, отклик только через сторонний бот; стек React+Python/LLM/RAG не совпадает с профилем (Vue+Flutter) — Suspicious legitimacy, не рекомендуется
1 059 2026-07-07 Unknown (Runello aggregator) Fullstack React Developer Evaluated 1.0/5 ❌ [059](reports/059-unknown-employer-runello-2026-07-07.md) Работодатель не назван, отклик только через сторонний бот; стек React+Python/LLM/RAG не совпадает с профилем (Vue+Flutter) — Suspicious legitimacy, не рекомендуется
@@ -0,0 +1 @@
061 2026-07-07 TOOLUM Fullstack разработчик Next.js Evaluated 2.3/5 ❌ [061](reports/061-toolum-2026-07-07.md) Next.js/React hard req vs Vue-only CV; JD terse (no salary/location/format); TOOLUM no public footprint
1 061 2026-07-07 TOOLUM Fullstack разработчик Next.js Evaluated 2.3/5 ❌ [061](reports/061-toolum-2026-07-07.md) Next.js/React hard req vs Vue-only CV; JD terse (no salary/location/format); TOOLUM no public footprint
@@ -0,0 +1 @@
16 2026-07-08 VK Старший Frontend-разработчик (React) Evaluated 1.0/5 ❌ [066](reports/066-vk-2026-07-08.md) Вакансия архивирована (компания не ищет кандидатов) + стек React вместо Vue + Senior 5+ лет vs ~1.5 года опыта — SKIP
1 16 2026-07-08 VK Старший Frontend-разработчик (React) Evaluated 1.0/5 ❌ [066](reports/066-vk-2026-07-08.md) Вакансия архивирована (компания не ищет кандидатов) + стек React вместо Vue + Senior 5+ лет vs ~1.5 года опыта — SKIP
@@ -0,0 +1 @@
075 2026-07-08 СПБ ИАЦ Программист Front-end Evaluated 1.0/5 ❌ [075](reports/075-spb-iac-2026-07-08.md) Vue 3/TS отлично совпадает, но офис только в Санкт-Петербурге без удалёнки — блокер по локационной политике
1 075 2026-07-08 СПБ ИАЦ Программист Front-end Evaluated 1.0/5 ❌ [075](reports/075-spb-iac-2026-07-08.md) Vue 3/TS отлично совпадает, но офис только в Санкт-Петербурге без удалёнки — блокер по локационной политике
@@ -0,0 +1 @@
076 2026-07-08 Bellerage Стажер Frontend-разработчик Evaluated 1.0/5 ❌ [076](reports/076-bellerage-2026-07-08.md) React (не Vue) + стажёрский уровень + офис в Москве без удалёнки — блокер по локации
1 076 2026-07-08 Bellerage Стажер Frontend-разработчик Evaluated 1.0/5 ❌ [076](reports/076-bellerage-2026-07-08.md) React (не Vue) + стажёрский уровень + офис в Москве без удалёнки — блокер по локации
@@ -0,0 +1 @@
077 2026-07-08 Bioniq Младший фронтенд-разработчик Evaluated 1.0/5 ❌ [077](reports/077-bioniq-2026-07-08.md) Angular (не Vue) + вёрстка лендингов + офис в Москве — блокер по стеку и локации
1 077 2026-07-08 Bioniq Младший фронтенд-разработчик Evaluated 1.0/5 ❌ [077](reports/077-bioniq-2026-07-08.md) Angular (не Vue) + вёрстка лендингов + офис в Москве — блокер по стеку и локации
@@ -0,0 +1 @@
078 2026-07-08 А7-Технологии Flutter-разработчик Evaluated 1.0/5 ❌ [078](reports/078-a7-tehnologii-2026-07-08.md) Хорошее совпадение по Flutter/BLoC, но офис/гибрид в Москве без полной удалёнки — блокер по локации
1 078 2026-07-08 А7-Технологии Flutter-разработчик Evaluated 1.0/5 ❌ [078](reports/078-a7-tehnologii-2026-07-08.md) Хорошее совпадение по Flutter/BLoC, но офис/гибрид в Москве без полной удалёнки — блокер по локации
@@ -0,0 +1 @@
079 2026-07-08 DataLouna Backend Engineer (Node.js, TypeScript, Data, Real-time) Evaluated 1.0/5 ❌ [079](reports/079-datalouna-2026-07-08.md) Remote ок, но senior data/backend роль (ClickHouse, K8s, парсеры) без frontend — глубокое несовпадение по функции и опыту (5+ лет)
1 079 2026-07-08 DataLouna Backend Engineer (Node.js, TypeScript, Data, Real-time) Evaluated 1.0/5 ❌ [079](reports/079-datalouna-2026-07-08.md) Remote ок, но senior data/backend роль (ClickHouse, K8s, парсеры) без frontend — глубокое несовпадение по функции и опыту (5+ лет)
@@ -0,0 +1 @@
080 2026-07-08 Selfwork Full-stack engineer Evaluated 1.5/5 ❌ [080](reports/080-selfwork-2026-07-08.md) Remote, TS/Node пересекаются, но React (не Vue) + жёсткое требование 5+ лет senior fullstack опыта не в аутсорсе
1 080 2026-07-08 Selfwork Full-stack engineer Evaluated 1.5/5 ❌ [080](reports/080-selfwork-2026-07-08.md) Remote, TS/Node пересекаются, но React (не Vue) + жёсткое требование 5+ лет senior fullstack опыта не в аутсорсе
@@ -0,0 +1 @@
081 2026-07-08 Positive Technologies Fullstack QA Engineer (RA VPN) Evaluated 1.0/5 ❌ [081](reports/081-positive-technologies-2026-07-08.md) Remote ок, но это QA-роль (тестирование VPN/NGFW), а не разработка — функциональное несовпадение
1 081 2026-07-08 Positive Technologies Fullstack QA Engineer (RA VPN) Evaluated 1.0/5 ❌ [081](reports/081-positive-technologies-2026-07-08.md) Remote ок, но это QA-роль (тестирование VPN/NGFW), а не разработка — функциональное несовпадение
@@ -0,0 +1 @@
082 2026-07-08 Air Production Frontend-разработчик (Vue.js) Evaluated 4.0/5 ✅ [082](reports/082-air-production-2026-07-08.md) Лучшее совпадение сессии: Vue.js, удалённо, опыт 1-3 года и ЗП 90-130к точно в цель; toolchain-разрыв (Vuex/Webpack/Gulp vs Pinia/Vite) закрываем
1 082 2026-07-08 Air Production Frontend-разработчик (Vue.js) Evaluated 4.0/5 ✅ [082](reports/082-air-production-2026-07-08.md) Лучшее совпадение сессии: Vue.js, удалённо, опыт 1-3 года и ЗП 90-130к точно в цель; toolchain-разрыв (Vuex/Webpack/Gulp vs Pinia/Vite) закрываем
@@ -0,0 +1 @@
083 2026-07-08 Nexters Senior Frontend Developer (Hero Wars) Evaluated 1.0/5 ❌ [083](reports/083-nexters-2026-07-08.md) Remote ок, но gamedev-стек (игровые движки Cocos/Phaser/Pixi) + Senior 3+ года — не веб-Vue разработка
1 083 2026-07-08 Nexters Senior Frontend Developer (Hero Wars) Evaluated 1.0/5 ❌ [083](reports/083-nexters-2026-07-08.md) Remote ок, но gamedev-стек (игровые движки Cocos/Phaser/Pixi) + Senior 3+ года — не веб-Vue разработка
@@ -0,0 +1 @@
084 2026-07-08 МТС (Маркетолог) Senior Frontend developer Evaluated 1.0/5 ❌ [084](reports/084-mts-marketolog-2026-07-08.md) Remote ок, но React/Next.js (не Vue) + Senior 6+ лет — стек и уровень не совпадают
1 084 2026-07-08 МТС (Маркетолог) Senior Frontend developer Evaluated 1.0/5 ❌ [084](reports/084-mts-marketolog-2026-07-08.md) Remote ок, но React/Next.js (не Vue) + Senior 6+ лет — стек и уровень не совпадают
@@ -0,0 +1 @@
085 2026-07-08 МТС (GOODOK) Middle Flutter Developer Evaluated 2.0/5 ❌ [085](reports/085-mts-goodok-2026-07-08.md) Гибрид/удалённо ок, BLoC/Dart совпадают, но глубина (Isolates, локальные БД, тесты) и стаж 3-6 лет vs ~1 год — разрыв
1 085 2026-07-08 МТС (GOODOK) Middle Flutter Developer Evaluated 2.0/5 ❌ [085](reports/085-mts-goodok-2026-07-08.md) Гибрид/удалённо ок, BLoC/Dart совпадают, но глубина (Isolates, локальные БД, тесты) и стаж 3-6 лет vs ~1 год — разрыв
@@ -0,0 +1 @@
086 2026-07-08 МТС (MWS Tables) Fullstack developer React/Node.JS Evaluated 1.0/5 ❌ [086](reports/086-mts-tables-2026-07-08.md) Гибрид в Москве — локационный блокер (Тула, без релокации); плюс стек/стаж выше профиля
1 086 2026-07-08 МТС (MWS Tables) Fullstack developer React/Node.JS Evaluated 1.0/5 ❌ [086](reports/086-mts-tables-2026-07-08.md) Гибрид в Москве — локационный блокер (Тула, без релокации); плюс стек/стаж выше профиля
@@ -0,0 +1 @@
087 2026-07-08 МТС (MWS Octapi) Стажер Fullstack QA Evaluated 1.0/5 ❌ [087](reports/087-mts-octapi-2026-07-08.md) Гибрид в Москве — локационный блокер; плюс QA-функция и стажёрский уровень
1 087 2026-07-08 МТС (MWS Octapi) Стажер Fullstack QA Evaluated 1.0/5 ❌ [087](reports/087-mts-octapi-2026-07-08.md) Гибрид в Москве — локационный блокер; плюс QA-функция и стажёрский уровень
@@ -0,0 +1 @@
088 2026-07-08 МТС (CICADA8) Junior Fullstack-аналитик Evaluated 1.0/5 ❌ [088](reports/088-mts-cicada8-2026-07-08.md) Офис в Москве — локационный блокер; плюс роль аналитика, не разработчика
1 088 2026-07-08 МТС (CICADA8) Junior Fullstack-аналитик Evaluated 1.0/5 ❌ [088](reports/088-mts-cicada8-2026-07-08.md) Офис в Москве — локационный блокер; плюс роль аналитика, не разработчика
@@ -0,0 +1 @@
089 2026-07-08 МТС (Телеком платформа) Middle QA fullstack Evaluated 1.0/5 ❌ [089](reports/089-mts-telecom-platforma-2026-07-08.md) Remote ок, но это QA-роль (тестирование), не разработка
1 089 2026-07-08 МТС (Телеком платформа) Middle QA fullstack Evaluated 1.0/5 ❌ [089](reports/089-mts-telecom-platforma-2026-07-08.md) Remote ок, но это QA-роль (тестирование), не разработка
@@ -0,0 +1 @@
090 2026-07-08 МТС Middle/Senior Fullstack разработчик Evaluated 1.0/5 ❌ [090](reports/090-mts-fullstack-2026-07-08.md) Единственная из 7 без явного remote-тега (только Москва+гибкий график) — консервативно офис, блокер
1 090 2026-07-08 МТС Middle/Senior Fullstack разработчик Evaluated 1.0/5 ❌ [090](reports/090-mts-fullstack-2026-07-08.md) Единственная из 7 без явного remote-тега (только Москва+гибкий график) — консервативно офис, блокер
@@ -0,0 +1 @@
091 2026-07-09 Неизвестный работодатель (TG-брокер) WebView-разработчик (Unity/Flutter/Native) Evaluated 1.0/5 ❌ [091](reports/091-webview-broker-2026-07-09.md) Сдельная оплата, нет компании, не соответствует профилю — не рекомендуется
1 091 2026-07-09 Неизвестный работодатель (TG-брокер) WebView-разработчик (Unity/Flutter/Native) Evaluated 1.0/5 ❌ [091](reports/091-webview-broker-2026-07-09.md) Сдельная оплата, нет компании, не соответствует профилю — не рекомендуется
@@ -0,0 +1 @@
092 2026-07-09 Swiftly / STA TECH Junior Flutter-разработчик Evaluated 1.2/5 ❌ [092](reports/092-swiftly-sta-tech-2026-07-09.md) Стек совпадает (Flutter), но без оплаты до релиза и монетизации — не рекомендуется
1 092 2026-07-09 Swiftly / STA TECH Junior Flutter-разработчик Evaluated 1.2/5 ❌ [092](reports/092-swiftly-sta-tech-2026-07-09.md) Стек совпадает (Flutter), но без оплаты до релиза и монетизации — не рекомендуется
@@ -0,0 +1 @@
093 2026-07-09 АО ГК Орбита Fullstack C# Senior Evaluated 1.0/5 ❌ [093](reports/093-gk-orbita-2026-07-09.md) C# стек, не пересекается ни с одним архетипом кандидата
1 093 2026-07-09 АО ГК Орбита Fullstack C# Senior Evaluated 1.0/5 ❌ [093](reports/093-gk-orbita-2026-07-09.md) C# стек, не пересекается ни с одним архетипом кандидата
@@ -0,0 +1 @@
094 2026-07-09 EvaTeam Frontend разработчик Angular Evaluated 1.3/5 ❌ [094](reports/094-evateam-2026-07-09.md) Angular, не Vue.js — стековое несовпадение
1 094 2026-07-09 EvaTeam Frontend разработчик Angular Evaluated 1.3/5 ❌ [094](reports/094-evateam-2026-07-09.md) Angular, не Vue.js — стековое несовпадение
@@ -0,0 +1 @@
095 2026-07-09 TOOLUM Fullstack разработчик Next.js Evaluated 1.3/5 ❌ [095](reports/095-toolum-2026-07-09.md) Next.js/React, не Vue.js — стековое несовпадение
1 095 2026-07-09 TOOLUM Fullstack разработчик Next.js Evaluated 1.3/5 ❌ [095](reports/095-toolum-2026-07-09.md) Next.js/React, не Vue.js — стековое несовпадение
@@ -0,0 +1 @@
096 2026-07-09 AppDepo Flutter Developer (Junior) Evaluated 3.2/5 ✅ [096](reports/096-appdepo-2026-07-09.md) Сильное совпадение по Flutter/Bloc/Firebase, но зарплата не раскрыта (почасовая) — уточнить ставку до отклика
1 096 2026-07-09 AppDepo Flutter Developer (Junior) Evaluated 3.2/5 ✅ [096](reports/096-appdepo-2026-07-09.md) Сильное совпадение по Flutter/Bloc/Firebase, но зарплата не раскрыта (почасовая) — уточнить ставку до отклика
@@ -0,0 +1 @@
097 2026-07-09 Awelite Fullstack Web разработчик Evaluated 1.3/5 ❌ [097](reports/097-awelite-2026-07-09.md) PHP-компания, бэкенд-стек не совпадает с профилем (Node.js)
1 097 2026-07-09 Awelite Fullstack Web разработчик Evaluated 1.3/5 ❌ [097](reports/097-awelite-2026-07-09.md) PHP-компания, бэкенд-стек не совпадает с профилем (Node.js)
@@ -0,0 +1 @@
098 2026-07-09 Young&Junior / СОФИ И ПАРТНЕРЫ Junior Frontend Разработчик Evaluated 1.6/5 ❌ [098](reports/098-young-junior-2026-07-09.md) React (не Vue), ЗП от 90к ниже минимума кандидата, несовпадение брендинга работодателя
1 098 2026-07-09 Young&Junior / СОФИ И ПАРТНЕРЫ Junior Frontend Разработчик Evaluated 1.6/5 ❌ [098](reports/098-young-junior-2026-07-09.md) React (не Vue), ЗП от 90к ниже минимума кандидата, несовпадение брендинга работодателя
@@ -0,0 +1 @@
099 2026-07-09 Constructor TECH Frontend Engineer (Intern) Evaluated 1.0/5 ❌ [099](reports/099-constructor-tech-2026-07-09.md) Стажировка только для студентов Constructor University/Nexademy/Nexford — формальный барьер входа, вероятно неоплачиваемая
1 099 2026-07-09 Constructor TECH Frontend Engineer (Intern) Evaluated 1.0/5 ❌ [099](reports/099-constructor-tech-2026-07-09.md) Стажировка только для студентов Constructor University/Nexademy/Nexford — формальный барьер входа, вероятно неоплачиваемая
+1
View File
@@ -0,0 +1 @@
60 2026-07-07 ЗАО КонсультантПлюс Веб разработчик Vue/Js Evaluated 3.0/5 ❌ [060](reports/060-konsultantplyus-2026-07-07.md) Дубликат URL из сегодняшнего batch-инпута (тот же t.me/s/runello_rus_javascript/1448); отчёт 060 от 2026-07-07 уже покрывает эту вакансию — новый отчёт не создан. PDF не был фактически сгенерирован несмотря на балл=порог 3.0 (только HTML), требует ручной генерации при необходимости.
1 60 2026-07-07 ЗАО КонсультантПлюс Веб разработчик Vue/Js Evaluated 3.0/5 ❌ [060](reports/060-konsultantplyus-2026-07-07.md) Дубликат URL из сегодняшнего batch-инпута (тот же t.me/s/runello_rus_javascript/1448); отчёт 060 от 2026-07-07 уже покрывает эту вакансию — новый отчёт не создан. PDF не был фактически сгенерирован несмотря на балл=порог 3.0 (только HTML), требует ручной генерации при необходимости.
@@ -0,0 +1 @@
100 2026-07-09 CHOICY (агентство, B2C SaaS партнёр) Senior Full-stack React Developer Evaluated 1.0/5 ❌ [100](reports/100-choicy-react-b2c-saas-2026-07-09.md) 8+ лет опыта требуется + React, не Vue — двойное несовпадение
1 100 2026-07-09 CHOICY (агентство, B2C SaaS партнёр) Senior Full-stack React Developer Evaluated 1.0/5 ❌ [100](reports/100-choicy-react-b2c-saas-2026-07-09.md) 8+ лет опыта требуется + React, не Vue — двойное несовпадение
@@ -0,0 +1 @@
101 2026-07-09 Tilda Publishing Head of Frontend-разработки Evaluated 1.0/5 ❌ [101](reports/101-tilda-publishing-2026-07-09.md) Управленческая роль (15+ подчинённых, 3+ года лидерства) + гибрид Москва/СПб — двойной блокер
1 101 2026-07-09 Tilda Publishing Head of Frontend-разработки Evaluated 1.0/5 ❌ [101](reports/101-tilda-publishing-2026-07-09.md) Управленческая роль (15+ подчинённых, 3+ года лидерства) + гибрид Москва/СПб — двойной блокер
@@ -0,0 +1 @@
102 2026-07-09 Data World (аутстафф Сбер) Frontend разработчик (опыт работы в банке) Evaluated 1.4/5 ❌ [102](reports/102-data-world-2026-07-09.md) React + мин. 3 года опыта — двойное несовпадение, несмотря на хорошую ЗП и удалёнку
1 102 2026-07-09 Data World (аутстафф Сбер) Frontend разработчик (опыт работы в банке) Evaluated 1.4/5 ❌ [102](reports/102-data-world-2026-07-09.md) React + мин. 3 года опыта — двойное несовпадение, несмотря на хорошую ЗП и удалёнку
@@ -0,0 +1 @@
103 2026-07-09 CORE TECHNOLOGY Frontend Developer (Middle) Discarded 1.0/5 ❌ [103](reports/103-core-technology-2026-07-09.md) Hard blocker: only on-site Tashkent, no remote, relocation required
1 103 2026-07-09 CORE TECHNOLOGY Frontend Developer (Middle) Discarded 1.0/5 ❌ [103](reports/103-core-technology-2026-07-09.md) Hard blocker: only on-site Tashkent, no remote, relocation required
@@ -0,0 +1 @@
104 2026-07-09 Lunas.pro Lead Graphics Engineer - Web 3D Discarded 1.0/5 ❌ [104](reports/104-lunas-pro-2026-07-09.md) Skill mismatch: WebGPU/Three.js/shaders, no overlap with Vue/Flutter background; also Lead vs Middle level
1 104 2026-07-09 Lunas.pro Lead Graphics Engineer - Web 3D Discarded 1.0/5 ❌ [104](reports/104-lunas-pro-2026-07-09.md) Skill mismatch: WebGPU/Three.js/shaders, no overlap with Vue/Flutter background; also Lead vs Middle level
@@ -0,0 +1 @@
105 2026-07-09 Podscribe Senior Full-stack Engineer (Node.js) Discarded 1.5/5 ❌ [105](reports/105-podscribe-2026-07-09.md) Senior level, React not Vue, Python/ClickHouse gap, B1 English risk on intl team
1 105 2026-07-09 Podscribe Senior Full-stack Engineer (Node.js) Discarded 1.5/5 ❌ [105](reports/105-podscribe-2026-07-09.md) Senior level, React not Vue, Python/ClickHouse gap, B1 English risk on intl team
@@ -0,0 +1 @@
106 2026-07-09 IDT Senior Frontend Software Engineer (React) Discarded 1.0/5 ❌ [106](reports/106-idt-2026-07-09.md) Employment tied to Georgia/Moldova/Belarus, not RU; also React/Electron/AWS CDK stack mismatch
1 106 2026-07-09 IDT Senior Frontend Software Engineer (React) Discarded 1.0/5 ❌ [106](reports/106-idt-2026-07-09.md) Employment tied to Georgia/Moldova/Belarus, not RU; also React/Electron/AWS CDK stack mismatch
@@ -0,0 +1 @@
107 2026-07-09 Acronis Senior Full-Stack Developer Discarded 1.0/5 ❌ [107](reports/107-acronis-2026-07-09.md) Bulgaria hybrid relocation + Senior 6+yrs gap; strong RBAC/Vue content match though
1 107 2026-07-09 Acronis Senior Full-Stack Developer Discarded 1.0/5 ❌ [107](reports/107-acronis-2026-07-09.md) Bulgaria hybrid relocation + Senior 6+yrs gap; strong RBAC/Vue content match though
Loaded 100 of 1527 files, more files were not shown because too many files have changed in this diff. Show more