Tasks: 19b — Localized Comp / Benefit Knowledge Packs
Status legend:
[ ]pending •[~]in-progress •[x]done •[-]dropped
Phase 1 — Data spine (table + structured schema + seed packs)
-
T01 — Add the
comp_knowledge_packsDrizzle table- Files:
packages/db/src/schema/comp-knowledge-packs.ts(new),packages/db/src/schema/index.ts(exportcompKnowledgePacks+CompPackSemanticstype) - Acceptance:
- Table follows house style:
integer("id").primaryKey().generatedAlwaysAsIdentity(),market text("market").notNull()(ISO alpha-2 orgeneric),version integer("version").notNull().default(1),isActive boolean("is_active").notNull().default(true),semantics jsonb("semantics").$type<CompPackSemantics>().notNull(),timestamp("created_at").notNull().defaultNow()+updated_at. semanticstype captures:currency,statutoryComponents[],norms,equityConventions,severance,thirteenthMonth(matches the spec §3 list).- Unique constraint on
(market, version); indexcomp_knowledge_packs_market_idxonmarket(and(market, isActive)for the latest-active lookup). - Exported from
packages/db/src/schema/index.ts; no existing export removed.
- Table follows house style:
- Estimate: 0.5 day
- Files:
-
T02 —
pnpm db:pushthe new table + schema-shape unit test- Files:
packages/db/src/schema/comp-knowledge-packs.test.ts(new) - Acceptance:
pnpm db:pushapplies the table cleanly (push-based, perdrizzle.config.ts).- Unit test asserts column names/types, the
(market, version)unique constraint, themarketindex, and the enum/default values; green underpnpm test -- --selectProjects db.
- Estimate: 0.5 day
- Files:
-
T03 —
compKnowledgePackSchemastructured artifact (epic #5 contract)- Files:
packages/ai/src/structured/schemas/comp-knowledge-pack.ts(new),packages/ai/src/structured/index.ts(export),packages/ai/src/structured/schemas/comp-knowledge-pack.test.ts(new) - Acceptance:
compKnowledgePackSchemais a.max()-boundedz.object(...)mirroring the tablesemanticsshape; registered viadefineArtifact("comp_knowledge_pack", 1, schema).COMP_KNOWLEDGE_PACK_SCHEMA_VERSION = 1exported; typeCompKnowledgePackinferred.- Unit test: a valid seed parses; a malformed pack (missing
currency) failssafeParse; green under--selectProjects ai.
- Estimate: 0.5 day
- Files:
-
T04 — Seed starter market packs (US / DE / GB / IN +
generic)- Files:
packages/db/src/seed-comp-packs.ts(new),packages/db/src/seed-comp-packs.test.ts(new) - Acceptance:
- Seeds 5 rows:
US,DE,GB,IN(version 1) + agenericfallback, each with correctcurrency(USD/EUR/GBP/INR) and market-specificsemantics(e.g. DE statutory notice/severance, IN/GB conventions) — hand-authored market data only. - Each seed row validates against
compKnowledgePackSchemabefore insert (no invalid seed can be written). - Idempotent: re-running upserts on
(market, version)rather than duplicating. - Test asserts all 5 rows validate and
genericis present; green under--selectProjects db.
- Seeds 5 rows:
- Estimate: 1 day
- Files:
Phase 2 — Pack loader (market → semantics, generic fallback, user-data-wins)
-
T05 —
resolveMarket(job)market normaliser + unit test- Files:
packages/ai/src/comp/market.ts(new),packages/ai/src/comp/market.test.ts(new) - Acceptance:
resolveMarket(job)mapsjobs.locationCountryto an ISO alpha-2 code (handles common names/aliases, e.g. "United States" →US, "Germany" →DE).- A null/unknown country, or
isRemote === truewith no country, resolves togeneric(never a silentUSdefault). - Pure function, no DB/network; unit test covers US/DE/GB/IN, an alias, null country, and
remote-only →
generic; green under--selectProjects ai.
- Estimate: 0.5 day
- Files:
-
T06 —
loadCompPack(market)loader with latest-version +genericfallback- Files:
packages/ai/src/comp/pack-loader.ts(new),packages/ai/src/comp/pack-loader.test.ts(new) - Acceptance:
loadCompPack(market)readscomp_knowledge_packsviadb(@ever-hust/db), selects the latest active version for the market (isActive,orderBy(version desc).limit(1)), and validates it throughcompKnowledgePackSchemabefore returning.- Returns the
genericpack when no row exists for the market; throws/logs perassertArtifacton a malformed row (dev throws, prod degrades togeneric). - Per-version in-process memoisation (packs immutable per version); one query per market per request.
- Unit test (mocked
db): latest-version selection, unknown market →generic, malformed row handling; green under--selectProjects ai.
- Estimate: 1 day
- Files:
-
T07 —
mergeUserComp(pack, userFacts)two-layer (user-data-wins) merge + test- Files:
packages/ai/src/comp/pack-loader.ts(extend),packages/ai/src/comp/pack-loader.test.ts(extend) - Acceptance:
mergeUserComp(pack, userFacts)returns market semantics where the pack supplies defaults and any field the user actually provided overrides the pack (constitution Article 7); absent user fields keep the pack default.- Never invents a user figure; a missing user fact leaves the pack default, not a fabricated value.
- Unit test: user currency/equity overrides win; empty
userFactsreturns the pack unchanged; partial user facts merge field-by-field; green under--selectProjects ai.
- Estimate: 0.5 day
- Files:
-
T08 —
packages/ai/src/comp/index.tsseam barrel for #3 / #15- Files:
packages/ai/src/comp/index.ts(new) - Acceptance:
- Re-exports
resolveMarket,loadCompPack,mergeUserComp,compKnowledgePackSchema, and theCompKnowledgePacktype with TSDoc describing the #3 Comp/Demand and #15 negotiation read paths. pnpm check-typespasses; the barrel is importable from@ever-hust/ai.
- Re-exports
- Estimate: 0.5 day
- Files:
Phase 3 — Wire into comp consumers (salary insights now; #3/#15 seams)
-
T09 — Make
salaryInsightsmarket-aware (replace hard-coded USD default)- Files:
packages/ai/src/tools/salary-insights.ts(edit),packages/ai/src/tools/salary-insights.test.ts(new or extend) - Acceptance:
- The dominant market is resolved from the matched jobs'
locationCountryand its pack is loaded vialoadCompPack; the tool uses the pack'scurrencyinstead of the hard-codeddominantCurrency = "USD"default when a market pack resolves. - The returned object gains an optional
marketContextfield{ market, currency, statutoryNotes }derived from the pack semantics; the existing return shape (overall/byLevel/byWorkMode/topCompanies) is unchanged (additive). userIdis not introduced as an LLM param (the tool stays market-data-only).- Unit test: a non-US (DE) fixture returns
marketContext.currency === "EUR"and a statutory note; a US/unknown fixture keeps prior behaviour; green under--selectProjects ai.
- The dominant market is resolved from the matched jobs'
- Estimate: 1 day
- Files:
-
T10 — Render the market/statutory note on the salary card
- Files:
apps/web/components/canvas/salary-insights-card.tsx(edit) - Acceptance:
SalaryInsightsDatagains optionalmarketContext; when present, the card renders the market + currency + a short statutory note using@ever-hust/ui/badge/cardandcn(); absentmarketContextrenders exactly as today (no layout break).- Currency formatting respects the pack currency (EUR/GBP/INR already handled by
currencySymbol; extend theswitchfor INR if missing).
- Estimate: 0.5 day
- Files:
-
T11 — Verify canvas-sync passes the extended shape; add coverage
- Files:
apps/web/hooks/use-canvas-sync.ts(verify),apps/web/hooks/use-canvas-sync.test.ts(new or extend,web-libproject) - Acceptance:
- The existing
case "salaryInsights"inhandleToolResultforwards the extendedSalaryInsightsData(incl.marketContext) into canvas state unchanged — no new case, no field dropped. - Test asserts a
salaryInsightsresult carryingmarketContextlands onstate.salaryInsightsintact; green under--selectProjects web-lib.
- The existing
- Estimate: 0.5 day
- Files:
-
T12 — Document the comp-pack seam in the orchestrator prompt
- Files:
packages/ai/src/prompts.ts(editDEFAULT_ORCHESTRATOR_PROMPT),packages/ai/src/prompts.test.ts(extend) - Acceptance:
- The system prompt gains a short comp-localization paragraph: when discussing comp for a
non-US role, reason with the market's semantics from
marketContext(statutory bonuses, 13th-month, severance, equity conventions), and never assume US conventions; figures from a pack are a market basis, not the user's own number (grounded / no-invent). - Mirrored in the Langfuse
orchestrator-systemprompt (note added in the file's setup comment / runbook). prompts.test.tsasserts the new guidance string is present in the default prompt; green under--selectProjects ai.
- The system prompt gains a short comp-localization paragraph: when discussing comp for a
non-US role, reason with the market's semantics from
- Estimate: 0.5 day
- Files:
Phase 4 — E2E + hardening + docs
-
T13 — Playwright E2E: non-US salary insights reflect the market
- Files:
tests/e2e/localized-comp.spec.ts(new) - Acceptance:
- Seeds/uses a non-US (DE)
jobsfixture; drives chat to request salary insights for that role; asserts the rendered card shows the market currency (EUR) and a statutory note — not raw USD assumptions. - Runs against
http://localhost:8443; green underpnpm test:e2e.
- Seeds/uses a non-US (DE)
- Estimate: 1 day
- Files:
-
T14 — Version-independence + fixture consistency hardening
- Files:
packages/ai/src/comp/pack-loader.test.ts(extend),packages/db/src/seed-comp-packs.test.ts(extend) - Acceptance:
- A test adds a
(DE, version 2)active row and assertsloadCompPack("DE")returns v2 while user data rows are untouched (packs version independently of user data, #13). resolveMarket→loadCompPack→salaryInsightsconsistency asserted on a non-US fixture; green under--selectProjects ai/db.
- A test adds a
- Estimate: 0.5 day
- Files:
-
T15 — Docs + ROADMAP update + competitor-clean verification
- Files:
docs/specs/19b-localized-comp/note (e.g.NOTES.md) orSTRUCTURED_OUTPUT.mdaddendum,docs/specs/ROADMAP.md(progress) - Acceptance:
- Doc records the
compKnowledgePackSchema, the two-layer (pack vs user-data) contract, and "how to add a market" (seed a(market, version)row — no deploy). - ROADMAP marks 19b progress.
rgover the staged change confirms zero competitor references (constitution Article 11) before commit.
- Doc records the
- Estimate: 0.5 day
- Files:
Notes
- Write tests alongside each implementation task; do not batch testing into a final task.
- Verify zero competitor references before every commit (see constitution Article 11).
- Update
docs/specs/ROADMAP.mdprogress when an epic's tasks complete. - 19b ships the loader + schema +
packages/ai/src/comp/index.tsseam as the stable interface that #3 (evaluation Comp/Demand) and #15 (negotiation) import when they land; only the existingsalaryInsightsconsumer is wired here.