fix(upskill,notion-sync): join tracker rows to ranked entries on title, the field entries carry (#614)

Both specs said to match "on company + role" with "casefold + strip on
both fields", but a seen_jobs.json entry has title and no role key (the
job-scraper schema), so read literally the lookup never matched: /upskill
then weighted the same job twice, once as a tracker row and once as a
ranked entry - the double-count its dedupe step exists to prevent - and
/notion-sync split one job into two rows with different Keys.
tools/rank_state.py already does the cross-name join (tracker role
against entry title); both specs now say so.

Two derived tests read the entry schema and require the join wording to
name title for the entry and role for the tracker; they fail on master.
This commit is contained in:
Ayobami Adegoke authored and GitHub committed 2026-10-17 20:01:34 +02:00
1 parent 709339758a
commit 2697fa46fb
5 files changed
+46 -3

No files matched your search

+1 -1
View File
@@ -40,7 +40,7 @@ Validate the cheap, local precondition before creating anything external. A run
1. Read `job_scraper/seen_jobs.json` and `job_search_tracker.csv` (either may be missing).
2. Select `seen_jobs.json` entries with status `ranked` whose `rank_score` meets the threshold from Step 0. `--all` lifts the threshold entirely.
3. Every tracker row joins the sync set (an applied-to job always syncs, ranked or not), matched to `seen_jobs.json` entries case-insensitively on company + role where possible. Tracker rows with no `seen_jobs.json` entry sync too - build their Key as `<company>_<role>` lowercased with underscores.
3. Every tracker row joins the sync set (an applied-to job always syncs, ranked or not), matched to `seen_jobs.json` entries case-insensitively on company + title where possible (the tracker's `role` column against the entry's `title` field — entries carry `title`, not `role`). Tracker rows with no `seen_jobs.json` entry sync too - build their Key as `<company>_<role>` lowercased with underscores.
4. **Status precedence:** the tracker wins. A job that is `ranked` in `seen_jobs.json` but `interview` in the tracker syncs as `interview`. Jobs only in `seen_jobs.json` keep their stored status. **Deadline precedence: the tracker wins too** - the tracker's `deadline` (written by `/apply` from the posting the application was actually built on) overrides the `seen_jobs.json` value; jobs only in `seen_jobs.json` keep the scraper's stored deadline. Omit the property when neither states one, and **never reconcile the two by picking the earlier or later date** - both were read from the posting at different times, and the safe-looking `min()` substitutes a date the user never applied against.
5. **If the sync set is empty** (no ranked entries meet the threshold and there are no tracker rows), say "Nothing to sync - run `/scrape` and `/rank` first" (or, when jobs exist but all score below the threshold, say so and suggest `--min-score`/`--all`) and **stop**.
6. State the counts before touching the destination: how many rows will be created or checked, and the threshold in effect.