feat(scout): Norway outside Oslo via NAV + dedicated boards, language check, reputation, region split

Norway search (user request 2026-10-21/22: Trondheim, Bergen or similar, not Oslo)
- New location policy norway_no_oslo: keeps a role if any Norwegian location
  is outside the Oslo area. Equinor and Telenor move to it.
- New `nav` adapter for the NAV national register (arbeidsplassen.nav.no):
  keyword queries on a fixed request budget, paced; on HTTP 429 it stops and
  returns a PARTIAL run with a warning instead of failing. Full-ad text checks
  are cached in state/nav_detail_cache.json (ignored). Tested against
  fixtures only: the live API was rate-limiting during the build.
- Dedicated boards where NAV was measured thin (2026-10-22: Nordic 0, DNV 2,
  Equinor 2 vs ~10, NATO 0): Nordic Semiconductor (teamtailor custom domain),
  DNV (new `algolia_jobs` adapter; app id/key/index read from the page each
  run), Arm Trondheim (new `radancy` adapter), Microsoft Norway (pcsx).
  Verified live: 7 / 6 / 7 / 6 roles outside Oslo.
- NAV rows that duplicate a dedicated board's posting are dropped
  (employer alias + normalized title).

Language / citizenship check
- classify_language_requirement reads the requirement SENTENCE, not the
  posting language: "norsk og/eller engelsk" is English OK, "kommunisere
  godt ... på norsk og engelsk" is Norwegian required. Only an explicit
  Norwegian requirement or Norwegian-citizenship requirement filters a role;
  clearance is noted, never a gate. Runs on full ad text (NAV detail page,
  untruncated Teamtailor/PCSX descriptions). Telenor Lead Platform Engineer
  now reads NORWEGIAN REQUIRED (it read UNCLEAR on truncated text).
- Norwegian title keywords added (they all scored 0 and were never checked).

Employer reputation (user requirement 2026-10-22)
- state/employer_reputation.json (now tracked): dated Glassdoor lookups with
  sources and the approved thresholds (good >= 3.8 on >= 50 reviews; ok
  3.5-3.79 or thin-but-backed; avoid < 3.5; Vizrt excluded). Every report
  row shows the verdict; unlooked-up employers show UNCHECKED.

Report
- Sections per region: Switzerland, Norway, Europe-remote / Denmark (user
  preference); `--region=ch|no` runs one region only.

Fixes
- Microsoft links: apply.careers.microsoft.com<positionUrl>?jobId=<id>
  with a host alias so all 7 logged Microsoft decisions still resolve; the
  old jobs.careers.microsoft.com form redirected to a generic search page.
  PCSX calls retry on 429.

CLAUDE.md corrections log: design/architecture gap (2026-10-21) and the
Microsoft link fix; the EMEA-suffix location leak stays OPEN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
dennisthiessenandClaude Opus 5.5 committed 2026-10-22 09:19:05 +02:00
1 parent 7d8166ddd2
commit 92c3c005b4
4 files changed
+867 -73

No files matched your search

+1
View File
@@ -30,6 +30,7 @@ job_scout/state/*
!job_scout/state/decisions.json
!job_scout/state/application_cohort.json
!job_scout/state/applications.json
!job_scout/state/employer_reputation.json
!job_scout/state/history/
job_scout/state/history/*
!job_scout/state/history/application_status_before_2026-09-11.json
+2
View File
@@ -183,6 +183,8 @@ _See `config.md` for user-specific corrections. Add verified errors here as you
| Date | Correction | Files fixed |
|------|-----------|-------------|
| 2026-10-21 | **Software design and architecture was under-recorded: the seventh canonical gap of the recurring shape, surfaced by the Google *Staff SWE, Shopping Graph* minimum *"3 years of experience with software design and architecture."*** Before this, the only design evidence was SW-7 (current, undated, 2026-10-03) and a *contribution* at Generali (GN-3), so the line read as unproven. User-confirmed: **software design and architecture were part of his work at Bosch (ingestion and inference scripts, 2020-2022) and at Swisscom (data products)**, and both count toward the year requirement; together they exceed three years. Recorded on BS-1, BS-2 and SW-7, with `designed` added to the BS-1/BS-2 allowed verbs. **Scope kept tight:** at Bosch, "designed" covers the ingestion scripts and the inference integration only, not the data services in general (the first patch over-extended it and was trimmed the same session). Design never means an architect title, design authority or shared-platform architecture. The Swisscom start date stays undated. **Lesson, the same as 10-08 and 10-14: when a JD has a years-of-X minimum that the canonical file states without a duration, ask for the duration before scoring.** | `claims.json` (BS-1, BS-2, SW-7); `config.md` (data-product design row); `knowledge_base/extractions/2026-10-21_software_design_architecture_clarification.md` |
| 2026-10-21 | **FIXED 2026-10-22 (Microsoft links); the EMEA leak below is still OPEN.** Fix: `fetch_pcsx` now emits `apply.careers.microsoft.com<positionUrl>?jobId=<displayJobId>`, a `?jobId=` pattern precedes the `/job/` one in `_JOB_ID_PATTERNS`, and `_HOST_ALIASES` maps the new host to the old so `_decision_key` is unchanged. Verified: all 7 logged Microsoft decisions still resolve (applied 200043897 included), and a new-form link opened the real posting with the job number on the page. Reports before 2026-10-22 still carry the dead form. Original finding: **every Microsoft link the scout emits is dead, in every report**, found in the 2026-10-20 triage. `fetch_pcsx` builds `jobs.careers.microsoft.com/global/en/job/<displayJobId>`, which now redirects to a generic `apply.careers.microsoft.com/careers?...&pid=1970393557041551` search page. Verified on 200060867 and on the **pending application 200043897** (Principal FDE). The application itself is unaffected; only the link is. The working form is `https://apply.careers.microsoft.com/careers/job/<pcsx id>`, the API's `positionUrl`. **Deliberately NOT patched yet:** 6 logged Microsoft decisions (including 200043897) are keyed on `displayJobId` through the `careers\.microsoft\.com /job/(\d+)` pattern. Swapping the URL would re-key them on the pcsx id and orphan them, the same failure as the 09-04 RUAG/BIS de-linking. **Safe fixes:** (a) keep `url` and add a working `apply_url` field to the row, or (b) emit the new URL with the display id carried in it, plus a host-normalised key pattern. Either way, verify all 6 rows still resolve through `find_decision_url` afterwards. Same triage, also OPEN: an **"EMEA" region suffix lets country-fenced roles through the location filter** (GitLab FR/DE/NL/ES/UK, Grafana ×5, Fivetran Serbia: 7 of 109 new rows), which breaks the docstring rule against multi-country allowlists that omit CH. | not yet fixed: `scout.py` (`fetch_pcsx`, location filter); evidence in `reports/2026-10-20.triage.md` |
| 2026-10-15 | **Two scout adapters were reporting wrong data without failing; both were found by the 2026-10-14 triage.** (a) **BIS:** the RSS adapter stamped `default_location: Basel, Switzerland` on every vacancy, so **jr100491 (Hong Kong, BIS Asia office)** passed the CH filter and reached a JD read. The feed carries no location, but each vacancy page has a static *Geographical location* field. `fetch_rss` now reads it per item (`location_page_regex`), falls back to a *"based in ..."* sentence in the feed description, and only then uses `default_location`, printing a warning when it does. Verified: Basel reqs still pass, and jr100491 now reads *8 Finance Street, Central, Hong Kong* and is excluded. (b) **Confluent:** IBM closed the acquisition on 2026-03-17, and the Ashby board went from 17 jobs (10-08) to `{"jobs":[]}` (10-14). careers.confluent.io sits behind a Vercel bot checkpoint. The roles are now IBM requisitions, served by the open IBM careers search API (`www-api.ibm.com/search/api/v2`, POST, `size` capped at 100, pages with `from`). New `ibm` adapter; Confluent keeps its id, so seen-state and decisions still link. First run: 84 roles, 0 in CH/Europe-remote scope. **HashiCorp was dropped on 2026-06-06 for having "no clean public ATS API" after IBM absorbed it. That API exists, so HashiCorp can come back through the same adapter if wanted.** **Rule: when an adapter must assume a field, the assumption has to be visible. A silent default looks exactly like real data.** | `scout.py` (`fetch_rss`, `_rss_item_location`, `fetch_ibm`, BIS and Confluent config) |
| 2026-10-14 | **Spotfire was the sixth evidenced-but-unregistered skill, and junior mentoring was unrecorded - both surfaced by the BIS Basel jr100498 *Senior/Lead Data & Analytics Engineer* (MED IT) triage.** (a) **TIBCO Spotfire** sat in the BS-5 and BS-8 scopes (co-owned environment, technical Application Owner, C# extensions, TAF 2022, spatial wafer-data map charts) but not in the `skills` array, so the JD line *"using database reporting and business intelligence tools"* had no citable skill; registered `production-historical`, `allowed` on user instruction. It does not license Power BI, Tableau or Qlik. (b) The JD requires *"Experience providing general supervision and task-specific guidance to junior colleagues"*, and the only canonical evidence was BS-7 (students, one thesis as Zweitpruefer). User-confirmed: he **onboarded and mentored junior developers at Bosch and at Swisscom** - recorded as **BS-9** and **SW-10**, one per employer. The Swisscom phase (ART B2B or ART DPT) is not recorded, so write it in past tense. Neither is people management. (c) **SDMX: user-confirmed never used**; registered `forbidden` so the scanner catches it. It is an accountability line in the JD, not a qualification. (d) The same audit found **Teradata, Oracle Database and Hadoop/Impala** evidenced only in the SW-2/BS-2/BS-8 scopes and absent from the `skills` array, although every past skills line listed them; registered `production-historical` from those scopes (Oracle explicitly does not establish DBA work). **Lesson, same as 10-08: when a JD has a supervision or mentoring line, ask about junior colleagues before falling back on BS-7. Student supervision is weaker evidence and the stronger evidence was simply unrecorded.** | `claims.json` (BS-9, SW-10; skills TIBCO Spotfire, SDMX, Teradata, Oracle Database, Hadoop / Impala); `knowledge_base/extractions/2026-10-14_mentoring_spotfire_sdmx_clarification.md` |
| 2026-10-08 | **Three more canonical gaps of the recurring shape, surfaced by the Kongsberg Seatex *Lead Engineer, DevOps and Test Infrastructure* (Trondheim) package.** (a) **Jenkins and GitLab CI/CD** were evidenced in claim scopes (GN-1, FC-1, BS-6, SW-3) but absent from the `skills` array, so no skills line could cite them; registered as `production-historical`, with GitLab keeping SW-3's "used, not built" limit. (b) **Embedded coursework** was unrecorded: user-confirmed an Embedded Systems course (C/C++ and VHDL with an ARM platform) and Robotics during his studies, and **no embedded work since** - recorded as **ED-1**, coursework-only, degree attribution not yet given. (c) **Current unit testing** was unrecorded: he writes unit tests today and knows pytest - recorded as **SW-9**; whether pytest is the Swisscom framework is not confirmed. The JD's required line *"Experience working with embedded SW and Linux"* stays a **half Gap** (hard-gate FAIL); the user overrode it knowingly. **Lesson, again: for any DevOps/test JD ask about current testing practice and about academic hardware work before scoring - neither lives in `claims.json` by default.** | `claims.json` (ED-1, SW-9, 7 skills); `knowledge_base/extractions/2026-10-08_embedded_coursework_testing_clarification.md`; Kongsberg package |
+764 -73
View File
File diff suppressed because it is too large. Load diff
+100
View File
@@ -0,0 +1,100 @@
{
"description": "Employer reputation lookups for job_scout (user requirement 2026-10-22: only recommend roles at employers with a good reputation as a place to work). Glassdoor blocks direct access (HTTP 403), so every figure here is a dated lookup from search results, not live data. Verdicts are computed by scout.reputation_verdict() from the thresholds below unless verdict_override is set. Entries older than 365 days show as STALE and must be re-checked. An employer missing from this file is UNCHECKED: look it up before recommending a role there.",
"thresholds": {
"good_min_rating": 3.8,
"good_min_reviews": 50,
"ok_min_rating": 3.5,
"rule": "good: Glassdoor >= 3.8 with >= 50 reviews. ok: 3.5-3.79, or >= 3.8 on fewer than 50 reviews backed by a second source, or no Glassdoor page but a solid second source. avoid: below 3.5 unless the user overrides. excluded: user decision.",
"approved": "user, 2026-10-22"
},
"employers": {
"Vizrt": {
"aliases": [],
"verdict_override": "excluded",
"note": "User decision 2026-10-22: worked there (Bergen, 2017-18) and does not want to work there again. Roles are hidden from reports.",
"checked": "2026-10-22"
},
"Arm": {
"aliases": ["Arm Norway", "ARM Norway"],
"glassdoor": {"rating": 4.5, "reviews": 2633, "scope": "company-wide"},
"note": "Trondheim-specific reviews are mixed: recent ones positive, a 2023 one criticised local middle management for engineers relocating from abroad.",
"sources": ["https://static.glassdoor.at/Reviews/Arm-Reviews-E7834_P800.htm", "https://static-pc.glassdoor.fr/Reviews/Employee-Review-Arm-E7834-RVW80607582.htm"],
"checked": "2026-10-22"
},
"Nordic Semiconductor": {
"aliases": ["Nordic Semi"],
"glassdoor": {"rating": 4.0, "reviews": 103, "recommend_pct": 83, "scope": "company-wide"},
"secondary": "TrueUp 82% recommend (100+ reviews)",
"sources": ["https://www.glassdoor.com/Overview/X-EI_IE681660.htm", "https://www.trueup.io/co/nordic-semi"],
"checked": "2026-10-22"
},
"Equinor": {
"aliases": [],
"glassdoor": {"rating": 4.3, "reviews": 769, "recommend_pct": 87, "scope": "company-wide"},
"secondary": "Indeed 4.0 (299); Universum: top student employer in engineering/business/technology 2024",
"sources": ["https://www.glassdoor.co.uk/Reviews/Equinor-Reviews-E10408.htm", "https://uk.indeed.com/cmp/Equinor/reviews"],
"checked": "2026-10-22"
},
"Kongsberg": {
"aliases": ["Kongsberg Gruppen", "Kongsberg Maritime", "Kongsberg Digital", "Kongsberg Defence", "Kongsberg Discovery", "Kongsberg Satellite Services", "KSAT", "Kongsberg Norcontrol", "Kongsberg Seatex"],
"not_aliases": ["Kongsberg Automotive"],
"glassdoor": {"rating": null, "reviews": null},
"secondary": "No Glassdoor page found; Indeed 3.8 (101 reviews, group); Universum 2025 professionals: 3rd in IT, 2nd in Engineering",
"note": "Ratings vary by division; Kongsberg Maritime JobStreet sample is small (7). Kongsberg Automotive is an unrelated company and is NOT covered by this entry.",
"sources": ["https://uk.indeed.com/cmp/Kongsberg-1", "https://www.kongsberg.com/news/stories/2025/11/Professionals-place-KONGSBERG-among-Norways-top-employers-in-Universum/"],
"checked": "2026-10-22"
},
"DNV": {
"aliases": ["DNV AS"],
"glassdoor": {"rating": 4.2, "reviews": 94, "recommend_pct": 82, "scope": "company-wide (CA/NZ snapshot; UK page 3.9 on 47)"},
"secondary": "Indeed 3.8 (444 reviews, Feb 2026)",
"note": "Praised for flexibility and people; common complaints are pay and slow progression.",
"sources": ["https://www.glassdoor.ca/Reviews/DNV-Reviews-E6648554.htm", "https://uk.indeed.com/cmp/Dnv/reviews"],
"checked": "2026-10-22"
},
"SINTEF": {
"aliases": [],
"glassdoor": {"rating": 4.0, "reviews": 62, "recommend_pct": 81, "scope": "company-wide"},
"note": "Autonomy and good people; salary on the lower end, some bureaucracy for project leads.",
"sources": ["https://www.glassdoor.co.in/Reviews/SINTEF-Reviews-E458950.htm"],
"checked": "2026-10-22"
},
"Telenor": {
"aliases": ["Telenor Norge", "Telenor ASA"],
"glassdoor": {"rating": 3.8, "reviews": 1305, "recommend_pct": 65, "scope": "company-wide, not Norway-specific"},
"secondary": "Indeed 4.1 (311)",
"note": "Exactly at the threshold. Good work-life balance; complaints about limited growth and frequently changing strategy.",
"sources": ["https://www.glassdoor.com/Reviews/Telenor-Reviews-E11282.htm"],
"checked": "2026-10-22"
},
"Microsoft": {
"aliases": ["Microsoft Norge", "Microsoft Development Center Norway"],
"glassdoor": {"rating": 4.0, "reviews": 54474, "recommend_pct": 76, "scope": "company-wide"},
"sources": ["https://static-pc.glassdoor.fr/Reviews/Microsoft-Reviews-E1651.htm"],
"checked": "2026-10-22"
},
"NATO": {
"aliases": ["NATO JWC", "Joint Warfare Centre"],
"glassdoor": {"rating": 4.0, "reviews": 240, "recommend_pct": 93, "scope": "all NATO bodies"},
"note": "Pay, benefits, international setting; heavy workload, bureaucracy, rigid military structure. Reviews do not separate civilian staff.",
"sources": ["https://www.glassdoor.com/Reviews/NATO-Reviews-E22846.htm"],
"checked": "2026-10-22"
},
"Nammo": {
"aliases": [],
"glassdoor": {"rating": 3.9, "reviews": 21, "recommend_pct": 56, "scope": "company-wide"},
"secondary": "Indeed 3.2 (87 reviews)",
"verdict_override": "avoid",
"note": "Thin and mixed: Glassdoor 3.9 on only 21 reviews with 56% recommending; the larger Indeed sample is 3.2, below the 3.5 line. A 2026 review alleges production pressure compromising safety. User may override.",
"sources": ["https://www.glassdoor.co.nz/Reviews/Nammo-Reviews-E4444878.htm", "https://www.indeed.com/cmp/Nammo"],
"checked": "2026-10-22"
},
"Kitron": {
"aliases": [],
"glassdoor": {"rating": 3.1, "reviews": 15, "recommend_pct": 34, "scope": "company-wide"},
"note": "Below threshold on a thin sample: limited growth, pay, unclear strategy.",
"sources": ["https://www.glassdoor.co.uk/Reviews/Kitron-Reviews-E311133.htm"],
"checked": "2026-10-22"
}
}
}