-
-
Notifications
You must be signed in to change notification settings - Fork 7.1k
feat(visa-phase-2): add OPT timeline calculator and cap season tracking #200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -154,6 +154,27 @@ Analyze posting signals to assess whether this is a real, active opening. | |
|
|
||
| **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. | ||
|
|
||
| #### OPT Timeline Status (solo si config/visa.yml tiene seccion opt:) | ||
|
|
||
| Si `config/visa.yml` tiene seccion `opt:` configurada, ejecutar: | ||
| `echo '{"jdText":"<full JD text>"}' | node opt-timeline.mjs --json` | ||
|
|
||
| Con el resultado JSON, mostrar este banner despues de la tabla de legitimacy: | ||
|
|
||
| > **OPT STATUS:** {remainingDays} days remaining (expires {endDate}) | ||
| > Unemployment: {unemployment.used}/{unemployment.limit} days used ({unemployment.remaining} remaining) | ||
| > **Cap Season:** {capSeason.phase}. {capSeason.advice} | ||
| > **Time-to-Hire:** {tthEstimate.type} company, est. {tthEstimate.minDays}-{tthEstimate.maxDays} days. Your OPT window: {remainingDays} days. {tthEstimate.warning || "Within range."} | ||
|
Comment on lines
+162
to
+167
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Render the Time-to-Hire line only when The JSON contract makes 🤖 Prompt for AI Agents |
||
|
|
||
| **Warning escalation:** | ||
| - Si unemployment.severity == 'urgent' (<=14 days): Prefijo `URGENT`, lenguaje fuerte: "CRITICAL: Only {remaining} unemployment days left. Immediate employment required." | ||
| - Si unemployment.severity == 'warning' (<=30 days): Prefijo `WARNING`, lenguaje firme: "WARNING: {remaining} unemployment days remaining. Accelerate job search." | ||
| - Si unemployment.severity == 'info' (<=60 days): Nota informativa: "Note: {remaining} unemployment days remaining. Monitor closely." | ||
|
|
||
| **Nota:** opt-timeline.mjs es pura computacion (no Playwright), seguro para batch mode. | ||
|
|
||
| Si `opt:` no esta configurada en visa.yml, omitir esta subseccion silenciosamente. | ||
|
|
||
| #### Score Global | ||
|
|
||
| | Dimensión | Score | | ||
|
|
@@ -335,6 +356,7 @@ Al terminar, imprime por stdout un resumen JSON para que el orquestador lo parse | |
| "role": "{rol}", | ||
| "score": {score_num}, | ||
| "legitimacy": "{High Confidence|Proceed with Caution|Suspicious}", | ||
| "optStatus": { "remainingDays": null, "unemploymentRemaining": null, "capPhase": null }, | ||
| "pdf": "{ruta_pdf}", | ||
| "report": "{ruta_report}", | ||
| "error": null | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # Career-Ops Visa Configuration | ||
| # Copy this file to config/visa.yml to activate visa features. | ||
| # This file is the template -- edit visa.yml with your actual data. | ||
| # Your visa.yml is user-layer data and will never be overwritten by updates. | ||
|
|
||
| # --- Sponsorship Mode (Phase 3) --- | ||
| # Controls how visa sponsorship signals affect evaluations: | ||
| # hard_filter: Auto-SKIP JDs with WONT_SPONSOR (saves tokens) | ||
| # score_penalty: Apply penalty to overall score | ||
| # info_only: Show visa info without affecting score (default) | ||
| sponsorship_mode: info_only | ||
|
|
||
| # Penalty values for score_penalty mode | ||
| penalties: | ||
| wont_sponsor: -0.7 | ||
| unknown: -0.3 | ||
|
|
||
| # --- OPT Timeline (Phase 4) --- | ||
| # Activate by filling in your OPT details below. | ||
| # type: regular (12 months) or stem (36 months from initial OPT start) | ||
| # start_date: Your OPT start date in YYYY-MM-DD format | ||
| # end_date: (optional) Override auto-calculated expiration | ||
| # unemployment_days_used: Manual counter -- update this yourself | ||
| # h1b_lottery_status: selected | not_selected | pending (optional, affects cap season advice) | ||
| opt: | ||
| type: stem | ||
| start_date: "2025-06-01" | ||
| # end_date: "2028-05-31" | ||
| unemployment_days_used: 0 | ||
| # h1b_lottery_status: pending | ||
|
Comment on lines
+25
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment out the sample As written, copying this template verbatim already satisfies the 🤖 Prompt for AI Agents |
||
|
|
||
| # --- Time-to-Hire Defaults (Phase 4, optional) --- | ||
| # Override heuristic estimates by company type [min_days, max_days] | ||
| # Defaults if omitted: startup [14,28], midsize [28,56], enterprise [56,112] | ||
| time_to_hire_defaults: | ||
| startup: [14, 28] | ||
| midsize: [28, 56] | ||
| enterprise: [56, 112] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| # F-1 Visa Module | ||
|
|
||
| Career-ops extension for F-1 international students: visa sponsorship detection, H-1B sponsor history lookup, OPT timeline tracking, and proactive company discovery. | ||
|
|
||
| ## Overview | ||
|
|
||
| The visa module transforms career-ops from "find good companies" into "predict and maximize hiring probability" for F-1 OPT/STEM OPT holders. It combines: | ||
|
|
||
| - **JD signal detection** -- Identify sponsorship language in job descriptions | ||
| - **H-1B filing history** -- Look up actual USCIS data for employer sponsorship track record | ||
| - **Visa score** -- Composite visa-friendliness rating (1-5) | ||
| - **OPT timeline tracking** -- Expiration countdown, unemployment day limits, cap season awareness | ||
|
|
||
| ## Setup | ||
|
|
||
| 1. Copy `config/visa.example.yml` to `config/visa.yml` | ||
| 2. Fill in your details (sponsorship preferences, OPT dates, etc.) | ||
| 3. The visa module activates automatically when `config/visa.yml` exists | ||
|
|
||
| ## OPT Timeline Tracking | ||
|
|
||
| Track your F-1 OPT status, unemployment days, and H-1B cap season timing. | ||
|
|
||
| ### OPT Setup | ||
|
|
||
| 1. Copy `config/visa.example.yml` to `config/visa.yml` (if not done already) | ||
| 2. Fill in the `opt:` section: | ||
|
|
||
| ```yaml | ||
| opt: | ||
| type: stem # regular (12 months) or stem (36 months) | ||
| start_date: "2025-06-01" # Your OPT start date | ||
| unemployment_days_used: 0 # Update manually as needed | ||
| # h1b_lottery_status: pending # Optional: selected | not_selected | pending | ||
| ``` | ||
|
|
||
| ### Quick Status | ||
|
|
||
| Run `/career-ops visa-status` to see your OPT dashboard: | ||
| - Days remaining until OPT expiration | ||
| - Unemployment days used vs limit (90 regular, 150 STEM) | ||
| - Current H-1B cap season phase with actionable advice | ||
| - Next key deadline | ||
|
|
||
| ### Automatic Warnings | ||
|
|
||
| When configured, OPT warnings appear automatically in: | ||
| - **Evaluations** (Block G) -- includes time-to-hire estimate vs your remaining OPT window | ||
| - **Batch evaluations** -- same warnings, works in headless mode | ||
| - **Scan results** -- one-line OPT status summary at top | ||
|
|
||
| ### Warning Thresholds | ||
|
|
||
| | Unemployment Days Remaining | Level | | ||
| |----------------------------|-------| | ||
| | <= 60 days | Info note | | ||
| | <= 30 days | Warning | | ||
| | <= 14 days | URGENT | | ||
|
|
||
| ### Time-to-Hire Estimates | ||
|
|
||
| The system estimates hiring timelines by company type: | ||
| - **Startup** (Series A/B, <100 employees): 2-4 weeks | ||
| - **Mid-size**: 4-8 weeks | ||
| - **Enterprise** (Fortune 500, 5000+ employees): 8-16 weeks | ||
|
|
||
| Customize in `config/visa.yml` under `time_to_hire_defaults:`. | ||
|
|
||
| ### CLI Usage | ||
|
|
||
| ```bash | ||
| node opt-timeline.mjs # Human-readable dashboard | ||
| node opt-timeline.mjs --test # Run built-in tests | ||
| node opt-timeline.mjs --json # JSON output for pipeline integration | ||
| ``` | ||
|
|
||
| ## H-1B Cap Season Phases | ||
|
|
||
| The system tracks 5 phases of the annual H-1B cap cycle: | ||
|
|
||
| | Phase | Months | What Happens | | ||
| |-------|--------|-------------| | ||
| | Pre-registration | Oct - Feb | Employers prepare petitions for upcoming fiscal year | | ||
| | Registration open | Mar | USCIS electronic registration window | | ||
| | Lottery results | Apr | Selections announced | | ||
| | Filing window | May - Jun | Selected petitions filed with USCIS | | ||
| | Post-cap | Jul - Sep | Focus on cap-exempt employers or next cycle | | ||
|
|
||
| ## Related Commands | ||
|
|
||
| | Command | Description | | ||
| |---------|-------------| | ||
| | `/career-ops visa-status` | OPT timeline dashboard | | ||
| | `/career-ops oferta` | Full evaluation with visa analysis in Block G | | ||
| | `/career-ops scan` | Portal scan with sponsorship indicators | | ||
| | `/career-ops batch` | Batch evaluation with OPT-aware warnings | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -132,6 +132,26 @@ Analyze the job posting for signals that indicate whether this is a real, active | |
|
|
||
| **Context Notes:** Any caveats (niche role, government job, evergreen position, etc.) that explain potentially concerning signals. | ||
|
|
||
| ### OPT Timeline Status (solo si config/visa.yml tiene seccion opt:) | ||
|
|
||
| Si `config/visa.yml` tiene seccion `opt:` configurada, ejecutar: | ||
| `echo '{"jdText":"<full JD text>"}' | node opt-timeline.mjs --json` | ||
|
Comment on lines
+137
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, check the actual content of modes/oferta.md around lines 137-153
head -153 modes/oferta.md | tail -17 | cat -nRepository: santifer/career-ops Length of output: 1465 🏁 Script executed: # Check if opt-timeline.mjs exists and examine its input handling
find . -name "opt-timeline.mjs" -type fRepository: santifer/career-ops Length of output: 81 🏁 Script executed: # Look for the complete context in modes/oferta.md
sed -n '130,155p' modes/oferta.md | cat -nRepository: santifer/career-ops Length of output: 1860 🏁 Script executed: # Now examine opt-timeline.mjs implementation
cat opt-timeline.mjs | head -100Repository: santifer/career-ops Length of output: 3627 🏁 Script executed: # Check the input handling and JSON parsing in opt-timeline.mjs
cat opt-timeline.mjs | tail -150Repository: santifer/career-ops Length of output: 5490 🏁 Script executed: # Get the full size of opt-timeline.mjs first
wc -l opt-timeline.mjsRepository: santifer/career-ops Length of output: 83 🏁 Script executed: # Look for the runJsonMode function that handles --json stdin
sed -n '300,450p' opt-timeline.mjs | cat -nRepository: santifer/career-ops Length of output: 5488 🏁 Script executed: # Search for the stdin handling logic
rg -A 20 "runJsonMode" opt-timeline.mjs | head -60Repository: santifer/career-ops Length of output: 790 🏁 Script executed: # Get the complete runJsonMode function to see JSON output construction
sed -n '750,871p' opt-timeline.mjs | cat -nRepository: santifer/career-ops Length of output: 5217 🏁 Script executed: # Search for where the JSON output is constructed
rg -B 5 -A 15 "JSON.stringify.*optStatus" opt-timeline.mjsRepository: santifer/career-ops Length of output: 45 🏁 Script executed: # Search more broadly for JSON output in runJsonMode
sed -n '760,820p' opt-timeline.mjs | cat -nRepository: santifer/career-ops Length of output: 2750 🏁 Script executed: # Find the complete runJsonMode function
rg -B 2 -A 40 "async function runJsonMode" opt-timeline.mjsRepository: santifer/career-ops Length of output: 1369 🏁 Script executed: # Get the rest of runJsonMode to see if tthEstimate is included and how
sed -n '742,800p' opt-timeline.mjs | cat -nRepository: santifer/career-ops Length of output: 3174 🏁 Script executed: # Get the complete rest of runJsonMode function
sed -n '702,760p' opt-timeline.mjs | cat -nRepository: santifer/career-ops Length of output: 2937 🏁 Script executed: # Search for where tthEstimate is computed in runJsonMode
rg -A 50 "async function runJsonMode" opt-timeline.mjs | grep -A 30 "tthEstimate"Repository: santifer/career-ops Length of output: 162 🏁 Script executed: # Get the full context around tthEstimate assignment in runJsonMode
rg -B 10 "result.tthEstimate" opt-timeline.mjs | head -50Repository: santifer/career-ops Length of output: 545 🏁 Script executed: # Verify the exact instruction text in modes/oferta.md to understand if this is a template or literal instruction
sed -n '6,11p' modes/oferta.md | cat -nRepository: santifer/career-ops Length of output: 357 🏁 Script executed: # Check if there's any surrounding context that explains how to use this command
sed -n '1,20p' modes/oferta.md | cat -nRepository: santifer/career-ops Length of output: 873 🏁 Script executed: # Test what happens if JD text contains quotes or special characters - simulate the brittle echo approach
cat > /tmp/test_json.sh << 'EOF'
#!/bin/bash
# Test 1: JD with single quotes
JD_WITH_QUOTES='JD says: "We need a developer"'
echo "Test 1 - With quotes in JD:"
echo "{\"jdText\":\"$JD_WITH_QUOTES\"}" 2>&1 | head -5
# Test 2: JD with newlines
JD_WITH_NEWLINE="Line 1
Line 2"
echo -e "\nTest 2 - With newlines in JD:"
echo "{\"jdText\":\"$JD_WITH_NEWLINE\"}" 2>&1 | head -5
# Test 3: Proper JSON construction with jq
echo -e "\nTest 3 - Proper jq approach:"
echo "$JD_WITH_QUOTES" | jq -Rs '{"jdText": .}' 2>&1 | head -5
EOF
bash /tmp/test_json.shRepository: santifer/career-ops Length of output: 275 🏁 Script executed: # Verify tthEstimate field is truly optional by checking the actual rendering template usage
sed -n '8,20p' modes/oferta.md | cat -nRepository: santifer/career-ops Length of output: 503 🏁 Script executed: # Get the exact lines 137-145 as shown in the review snippet
sed -n '137,145p' modes/oferta.md | cat -nRepository: santifer/career-ops Length of output: 749 Do not inline raw JD text into this shell command — use proper JSON encoding instead. Line 138's Additionally, the banner template on line 142 unconditionally references 🤖 Prompt for AI Agents |
||
|
|
||
| Con el resultado JSON, mostrar este banner ANTES de la tabla de sponsorship: | ||
|
|
||
| > **OPT STATUS:** {remainingDays} days remaining (expires {endDate}) | ||
| > Unemployment: {unemployment.used}/{unemployment.limit} days used ({unemployment.remaining} remaining) | ||
| > **Cap Season:** {capSeason.phase}. {capSeason.advice} | ||
| > **Time-to-Hire:** {tthEstimate.type} company, est. {tthEstimate.minDays}-{tthEstimate.maxDays} days. Your OPT window: {remainingDays} days. {tthEstimate.warning || "Within range."} | ||
|
Comment on lines
+140
to
+145
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard the Time-to-Hire row behind The 🤖 Prompt for AI Agents |
||
|
|
||
| **Warning escalation (per D-06):** | ||
| - Si unemployment.severity == 'urgent' (<=14 days): Prefijo `URGENT` en rojo, lenguaje fuerte: "CRITICAL: Only {remaining} unemployment days left. Immediate employment required." | ||
| - Si unemployment.severity == 'warning' (<=30 days): Prefijo `WARNING`, lenguaje firme: "WARNING: {remaining} unemployment days remaining. Accelerate job search." | ||
| - Si unemployment.severity == 'info' (<=60 days): Nota informativa: "Note: {remaining} unemployment days remaining. Monitor closely." | ||
| - Similar escalation for optStatus.remainingDays approaching 0. | ||
|
|
||
| Si `opt:` no esta configurada en visa.yml, omitir esta subseccion silenciosamente. | ||
|
|
||
| ### Edge case handling: | ||
| - **Government/academic postings:** Longer timelines are standard. Adjust thresholds (60-90 days is normal). | ||
| - **Evergreen/continuous hire postings:** If the JD explicitly says "ongoing" or "rolling," note it as context -- this is not a ghost job, it is a pipeline role. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,25 +33,9 @@ Leer `portals.yml` que contiene: | |
|
|
||
| **Cada empresa DEBE tener `careers_url` en portals.yml.** Si no la tiene, buscarla una vez, guardarla, y usar en futuros scans. | ||
|
|
||
| ### Nivel 2 — ATS APIs / Feeds (COMPLEMENTARIO) | ||
|
|
||
| Para empresas con API pública o feed estructurado, usar la respuesta JSON/XML como complemento rápido de Nivel 1. Es más rápido que Playwright y reduce errores de scraping visual. | ||
|
|
||
| **Soporte actual (variables entre `{}`):** | ||
| - **Greenhouse**: `https://boards-api.greenhouse.io/v1/boards/{company}/jobs` | ||
| - **Ashby**: `https://jobs.ashbyhq.com/api/non-user-graphql?op=ApiJobBoardWithTeams` | ||
| - **BambooHR**: lista `https://{company}.bamboohr.com/careers/list`; detalle de una oferta `https://{company}.bamboohr.com/careers/{id}/detail` | ||
| - **Lever**: `https://api.lever.co/v0/postings/{company}?mode=json` | ||
| - **Teamtailor**: `https://{company}.teamtailor.com/jobs.rss` | ||
| - **Workday**: `https://{company}.{shard}.myworkdayjobs.com/wday/cxs/{company}/{site}/jobs` | ||
|
|
||
| **Convención de parsing por provider:** | ||
| - `greenhouse`: `jobs[]` → `title`, `absolute_url` | ||
| - `ashby`: GraphQL `ApiJobBoardWithTeams` con `organizationHostedJobsPageName={company}` → `jobBoard.jobPostings[]` (`title`, `id`; construir URL pública si no viene en payload) | ||
| - `bamboohr`: lista `result[]` → `jobOpeningName`, `id`; construir URL de detalle `https://{company}.bamboohr.com/careers/{id}/detail`; para leer el JD completo, hacer GET del detalle y usar `result.jobOpening` (`jobOpeningName`, `description`, `datePosted`, `minimumExperience`, `compensation`, `jobOpeningShareUrl`) | ||
| - `lever`: array raíz `[]` → `text`, `hostedUrl` (fallback: `applyUrl`) | ||
| - `teamtailor`: RSS items → `title`, `link` | ||
| - `workday`: `jobPostings[]`/`jobPostings` (según tenant) → `title`, `externalPath` o URL construida desde el host | ||
| ### Nivel 2 — Greenhouse API (COMPLEMENTARIO) | ||
|
|
||
| Para empresas con Greenhouse, la API JSON (`boards-api.greenhouse.io/v1/boards/{slug}/jobs`) devuelve datos estructurados limpios. Usar como complemento rápido de Nivel 1 — es más rápido que Playwright pero solo funciona con Greenhouse. | ||
|
|
||
| ### Nivel 3 — WebSearch queries (DESCUBRIMIENTO AMPLIO) | ||
|
|
||
|
|
@@ -80,18 +64,11 @@ Los niveles son aditivos — se ejecutan todos, los resultados se mezclan y dedu | |
| f. Acumular en lista de candidatos | ||
| g. Si `careers_url` falla (404, redirect), intentar `scan_query` como fallback y anotar para actualizar la URL | ||
|
|
||
| 5. **Nivel 2 — ATS APIs / feeds** (paralelo): | ||
| 5. **Nivel 2 — Greenhouse APIs** (paralelo): | ||
| Para cada empresa en `tracked_companies` con `api:` definida y `enabled: true`: | ||
| a. WebFetch de la URL de API/feed | ||
| b. Si `api_provider` está definido, usar su parser; si no está definido, inferir por dominio (`boards-api.greenhouse.io`, `jobs.ashbyhq.com`, `api.lever.co`, `*.bamboohr.com`, `*.teamtailor.com`, `*.myworkdayjobs.com`) | ||
| c. Para **Ashby**, enviar POST con: | ||
| - `operationName: ApiJobBoardWithTeams` | ||
| - `variables.organizationHostedJobsPageName: {company}` | ||
| - query GraphQL de `jobBoardWithTeams` + `jobPostings { id title locationName employmentType compensationTierSummary }` | ||
| d. Para **BambooHR**, la lista solo trae metadatos básicos. Para cada item relevante, leer `id`, hacer GET a `https://{company}.bamboohr.com/careers/{id}/detail`, y extraer el JD completo desde `result.jobOpening`. Usar `jobOpeningShareUrl` como URL pública si viene; si no, usar la URL de detalle. | ||
| e. Para **Workday**, enviar POST JSON con al menos `{"appliedFacets":{},"limit":20,"offset":0,"searchText":""}` y paginar por `offset` hasta agotar resultados | ||
| f. Para cada job extraer y normalizar: `{title, url, company}` | ||
| g. Acumular en lista de candidatos (dedup con Nivel 1) | ||
| a. WebFetch de la URL de API → JSON con lista de jobs | ||
| b. Para cada job extraer: `{title, url, company}` | ||
| c. Acumular en lista de candidatos (dedup con Nivel 1) | ||
|
|
||
| 6. **Nivel 3 — WebSearch queries** (paralelo si posible): | ||
| Para cada query en `search_queries` con `enabled: true`: | ||
|
|
@@ -167,6 +144,28 @@ https://... 2026-02-10 Ashby — AI PM SA AI OldCo skipped_dup | |
| https://... 2026-02-10 WebSearch — AI PM PM AI ClosedCo skipped_expired | ||
| ``` | ||
|
|
||
| ## OPT Timeline Notice (si config/visa.yml tiene seccion opt:) | ||
|
|
||
| Al inicio del scan output, si `config/visa.yml` tiene seccion `opt:` configurada, ejecutar `node opt-timeline.mjs` (sin args, human-readable) y mostrar un one-line summary: | ||
|
|
||
| > OPT: {remainingDays}d remaining | Unemployment: {used}/{limit}d | Cap: {phase} | ||
|
|
||
| Si unemployment.severity o OPT expiration severity es 'warning' o 'urgent', highlight the line con prefijo `WARNING:` o `URGENT:`. | ||
|
Comment on lines
+149
to
+153
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use
🤖 Prompt for AI Agents |
||
|
|
||
| Si `opt:` no esta configurada, omitir silenciosamente. | ||
|
|
||
| ## Sponsorship Indicator (solo si config/visa.yml existe) | ||
|
|
||
| Si visa features estan activas (config/visa.yml existe), agregar columna de sponsorship al output de scan: | ||
| - Para cada oferta encontrada, si el titulo o snippet del job posting contiene keywords de sponsorship (from `config/sponsorship-keywords.yml`): | ||
| - Positive keywords found (e.g., "visa sponsorship", "will sponsor"): mostrar `[SPONSOR]` | ||
| - Negative keywords found (e.g., "will not sponsor", "security clearance required"): mostrar `[NO-SPNS]` | ||
| - No keywords found: mostrar `[?]` | ||
| - Esta columna es SOLO indicativa -- NO filtrar ofertas. Todas se muestran al usuario. | ||
| - El usuario decide cuales evaluar. | ||
|
|
||
| Nota: el scan solo tiene acceso al titulo y snippet, no al JD completo. La deteccion completa de sponsorship ocurre durante la evaluacion (oferta/batch mode) cuando se obtiene el JD completo. | ||
|
|
||
| ## Resumen de salida | ||
|
|
||
| ``` | ||
|
|
@@ -193,18 +192,8 @@ Cada empresa en `tracked_companies` debe tener `careers_url` — la URL directa | |
| - **Ashby:** `https://jobs.ashbyhq.com/{slug}` | ||
| - **Greenhouse:** `https://job-boards.greenhouse.io/{slug}` o `https://job-boards.eu.greenhouse.io/{slug}` | ||
| - **Lever:** `https://jobs.lever.co/{slug}` | ||
| - **BambooHR:** lista `https://{company}.bamboohr.com/careers/list`; detalle `https://{company}.bamboohr.com/careers/{id}/detail` | ||
| - **Teamtailor:** `https://{company}.teamtailor.com/jobs` | ||
| - **Workday:** `https://{company}.{shard}.myworkdayjobs.com/{site}` | ||
| - **Custom:** La URL propia de la empresa (ej: `https://openai.com/careers`) | ||
|
|
||
| **Patrones de API/feed por plataforma:** | ||
| - **Ashby API:** `https://jobs.ashbyhq.com/api/non-user-graphql?op=ApiJobBoardWithTeams` | ||
| - **BambooHR API:** lista `https://{company}.bamboohr.com/careers/list`; detalle `https://{company}.bamboohr.com/careers/{id}/detail` (`result.jobOpening`) | ||
| - **Lever API:** `https://api.lever.co/v0/postings/{company}?mode=json` | ||
| - **Teamtailor RSS:** `https://{company}.teamtailor.com/jobs.rss` | ||
| - **Workday API:** `https://{company}.{shard}.myworkdayjobs.com/wday/cxs/{company}/{site}/jobs` | ||
|
|
||
| **Si `careers_url` no existe** para una empresa: | ||
| 1. Intentar el patrón de su plataforma conocida | ||
| 2. Si falla, hacer un WebSearch rápido: `"{company}" careers jobs` | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # visa-status -- OPT Timeline Dashboard | ||
|
|
||
| Quick-glance view of your F-1 OPT status, unemployment day counter, H-1B cap season phase, and upcoming deadlines. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - `config/visa.yml` must exist with `opt:` section configured | ||
| - If missing, tell user: "OPT tracking not configured. Copy config/visa.example.yml to config/visa.yml and fill in your OPT details." | ||
|
|
||
| ## Steps | ||
|
|
||
| 1. Run `node opt-timeline.mjs` (no args -- human-readable dashboard mode) | ||
| 2. Display the output directly to the user | ||
| 3. If any warnings are present (unemployment severity or OPT expiration approaching), highlight them prominently | ||
|
|
||
| ## Output Format | ||
|
|
||
| The script produces a formatted dashboard. Display it as-is. Example: | ||
|
|
||
| ``` | ||
| === F-1 OPT Status === | ||
|
|
||
| Type: STEM OPT | ||
| Start Date: 2025-06-01 | ||
| Expiration: 2028-06-01 | ||
| Remaining: 792 days | ||
|
|
||
| --- Unemployment Counter --- | ||
| Used: 45 / 150 days | ||
| Remaining: 105 days | ||
| Status: OK | ||
|
|
||
| --- H-1B Cap Season --- | ||
| Current Phase: Filing window (Apr - Jun) | ||
| Advice: Selected petitions being filed. If not selected, focus on cap-exempt employers. | ||
| Lottery Status: pending | ||
|
|
||
| --- Next Key Deadline --- | ||
| Oct 1, 2026: H-1B employment start date for FY2027 selectees | ||
| ``` | ||
|
Comment on lines
+18
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep this example synchronized with the real dashboard output. The current sample shows a different header/section layout than 🧰 Tools🪛 markdownlint-cli2 (0.22.0)[warning] 20-20: Fenced code blocks should have a language specified (MD040, fenced-code-language) 🤖 Prompt for AI Agents |
||
|
|
||
| ## Warnings | ||
|
|
||
| If unemployment remaining <= 60 days or OPT remaining is within warning thresholds, the script output includes warning banners. Make sure these are visible and not buried. | ||
|
|
||
|
Comment on lines
+42
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not promise OPT-expiration warning banners unless the script emits them. The provided 🤖 Prompt for AI Agents |
||
| ## Tips | ||
|
|
||
| After displaying status, suggest: | ||
| - "Update your unemployment days: edit `opt.unemployment_days_used` in config/visa.yml" | ||
| - "Run `/career-ops oferta` to evaluate a job with OPT-aware timing analysis" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: santifer/career-ops
Length of output: 172
🏁 Script executed:
Repository: santifer/career-ops
Length of output: 1969
🏁 Script executed:
Repository: santifer/career-ops
Length of output: 45
🏁 Script executed:
Repository: santifer/career-ops
Length of output: 195
🏁 Script executed:
Repository: santifer/career-ops
Length of output: 35699
🏁 Script executed:
Repository: santifer/career-ops
Length of output: 81
🏁 Script executed:
Repository: santifer/career-ops
Length of output: 1964
Use
jqto safely construct the OPT JSON payload instead ofechowith string interpolation.The pattern
echo '{"jdText":"<full JD text>"}' | node opt-timeline.mjs --jsonwill fail on common JD content such as quoted requirements, newlines, and special characters. Use:jq -n --arg jd "$jdText" '{"jdText": $jd}' | node opt-timeline.mjs --json(or equivalent JSON-safe method) to properly escape all input.🤖 Prompt for AI Agents