A coach with a client was told there was nothing to do - #156
Merged
Conversation
Observed in production on 2026-09-02. Simon B. (Jobcoach) had one client assigned — George B, created that morning: job-seeking, two years without work, an unrecognised trade qualification, German at A2. His dashboard said "Alles unter Kontrolle! Keine dringenden Aufgaben" and never mentioned him. `openTaskCount` was criticalIncidents + overdueCheckIns + unplacedResidents + pendingTransfers + proposalsAwaitingStaff + problemUnits. Every term is a HOUSING queue, and a Jobcoach holds none of those permissions — so the count was structurally zero, the state was always `quiet`, and the screen always congratulated them. Same failure the `unassigned` state was added to fix, one level deeper. Then it was "nobody is assigned to me". Now it is "somebody is, with real work outstanding, and the queue is written in another domain's vocabulary". The fix had to be evidence-shaped rather than invented, because the job side had NO documented scientific basis at all while housing has had algorithm-docs since the beginning. That asymmetry is the deeper problem: placement and employment are the two things AOZ is measured on, and only one of them could explain itself. `config/job-integration-docs.ts` carries the evidence, Swiss and German first because institutions do not transfer even where mechanisms do: Integrationsagenda Schweiz (the binding Wirkungsziele AOZ operates under), IAB on lock-in effects and on refugee employment trajectories, IPS supported employment, OECD on parallel language and work, SBFI/SEM on recognition and de-skilling. Each principle declares whether the product ACTS on it. Three raise work; three raise nothing on purpose — recognition, post-start support and stated job goals matter and are not detectable from data this product holds. Inventing a signal from data that does not exist would be worse than the gap, and an evidence page implying more than the software does is worse than none. The queue is pure, with `now` injected: no contact past a grace period, a course running with nothing alongside it, a record untouched for six weeks. The thresholds are named constants carrying their reasoning, because they are numbers to argue with — 42 days and not 14, since a language course legitimately runs for months and a queue that fires fortnightly is one a coach learns to dismiss. Tiles name the CLIENTS. A bare count would reproduce the original failure of never mentioning George. Structure: EvidenceStrength and ResearchSource move to config/evidence.ts so the second domain does not import the housing algorithm's module for a shared type; algorithm-docs re-exports them, so no import path changes. Signal copy lives in the config SSOT and the queue keeps no opinion about rendering. The caseload query is scoped to the coach's own JOB seat and only runs for a viewer holding `learning:write`. A test pins that every `signal` principle has an implementation and every signal has a principle — the guard against evidence-as-decoration, in both directions. verify exit 0 (unpiped): 206 suites, 3624 passed. Build exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cd183M6472xBgTKWA2is6h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(jobcoach): a coach with a client was told there was nothing to do
Observed in production on 2026-09-02. Simon B. (Jobcoach) had one client
assigned — George B, created that morning: job-seeking, two years without
work, an unrecognised trade qualification, German at A2. His dashboard said
"Alles unter Kontrolle! Keine dringenden Aufgaben" and never mentioned him.
openTaskCountwas criticalIncidents + overdueCheckIns + unplacedResidents +pendingTransfers + proposalsAwaitingStaff + problemUnits. Every term is a
HOUSING queue, and a Jobcoach holds none of those permissions — so the count
was structurally zero, the state was always
quiet, and the screen alwayscongratulated them.
Same failure the
unassignedstate was added to fix, one level deeper. Thenit was "nobody is assigned to me". Now it is "somebody is, with real work
outstanding, and the queue is written in another domain's vocabulary".
The fix had to be evidence-shaped rather than invented, because the job side
had NO documented scientific basis at all while housing has had algorithm-docs
since the beginning. That asymmetry is the deeper problem: placement and
employment are the two things AOZ is measured on, and only one of them could
explain itself.
config/job-integration-docs.tscarries the evidence, Swiss and German firstbecause institutions do not transfer even where mechanisms do:
Integrationsagenda Schweiz (the binding Wirkungsziele AOZ operates under), IAB
on lock-in effects and on refugee employment trajectories, IPS supported
employment, OECD on parallel language and work, SBFI/SEM on recognition and
de-skilling.
Each principle declares whether the product ACTS on it. Three raise work;
three raise nothing on purpose — recognition, post-start support and stated
job goals matter and are not detectable from data this product holds.
Inventing a signal from data that does not exist would be worse than the gap,
and an evidence page implying more than the software does is worse than none.
The queue is pure, with
nowinjected: no contact past a grace period, acourse running with nothing alongside it, a record untouched for six weeks.
The thresholds are named constants carrying their reasoning, because they are
numbers to argue with — 42 days and not 14, since a language course
legitimately runs for months and a queue that fires fortnightly is one a coach
learns to dismiss.
Tiles name the CLIENTS. A bare count would reproduce the original failure of
never mentioning George.
Structure: EvidenceStrength and ResearchSource move to config/evidence.ts so
the second domain does not import the housing algorithm's module for a shared
type; algorithm-docs re-exports them, so no import path changes. Signal copy
lives in the config SSOT and the queue keeps no opinion about rendering. The
caseload query is scoped to the coach's own JOB seat and only runs for a
viewer holding
learning:write.A test pins that every
signalprinciple has an implementation and everysignal has a principle — the guard against evidence-as-decoration, in both
directions.
verify exit 0 (unpiped): 206 suites, 3624 passed. Build exit 0.
Rebased onto the Drizzle migration (#154), which landed mid-flight. The pure modules needed no change — only the caseload query was ORM-shaped.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Cd183M6472xBgTKWA2is6h