UKCBC SITS → SJMS-5 slice
The seven-beat migration walkthrough over a synthetic, hand-authored UKCBC-shaped SITS fixture. Nothing is generated for submission, nothing is submitted anywhere; the one beat that writes (the load leg) targets only the local twin the operator configures.
10022021 · HESA INSTID 0249. A franchised provider that is NOT OfS-registered and makes no HESA return of its own — the mapped candidate is partner-supply material, not a UKCBC return.pnpm --filter @databridge/api demo:ukcbc · operator script docs/UKCBC_SITS_MIGRATION_DEMO.md- 1SITS file drop → the sits-file adapterA four-file SITS CSV-OUT-style drop (STU/ENG/MOD/CRS, real SITS:Vision column names) loaded through @databridge/adapter-sits-file's live path — the identical code path an SFTP/S3 drop takes.STU students20ENG engagements20MOD modules6CRS courses2
20 students across HND Business and HND Computing, six Pearson-HND-flavoured 15-credit units, two course records.
Synthetic fixture — fictional students and marks; provider identifiers are public register facts.
- 2SITS ↔ HESA crosswalk — unmapped codes flagged, never guessedUKCBC_SITS_FILE_MAPPING (hesa-mapper-sits) maps the file extract onto canonical HESA fields through the shared engine, translating local codes (SITS mode F→HESA 01, aim HND→J10) and validating every coded value against the HESA codesets.Student20 mapped · 0 findingsEngagement20 mapped · 0 findingsStudentCourseSession20 mapped · 1 finding · 1 row rejectedModule6 mapped · 0 findings
- ERROR
HESA-MAP-MODE— StudentCourseSession/UKC240019/1: source value "A" for MODE has no mapping — the engine refuses to guess, flags it, and lets the record travel on so the statutory rules can flag it too.
Synthetic fixture — fictional students and marks; provider identifiers are public register facts.
- 3Record-level HESA Quality Rules — POST /v1/hesa/validateThe mapped candidate goes to the stateless, record-level Quality-Rules contract (the same endpoint SJMS-5 calls). Never through a profile-sits audit: its 47 rules are all SQL-class and evaluate NOTHING over a staged file drop — a false green. Record-level validation of file input is /v1/hesa/validate.StreamStudentSubmittablefalseViolations5 ERROR / 0 WARN / 0 INFOClean records15 of 20
- ERROR
HUSID.FORMAT— D1 — UKC240007: stu_husid = 999992400007 (12 digits). - ERROR
BIRTHDTE.FORMAT— D2 — UKC240012: stu_dob = 14/03/2006 (UK date drift). - ERROR
ENDDATE.AFTER.COMDATE— D3 — UKC240004/1: scj_ends = 2024-10-01 before scj_coms = 2025-09-22. - ERROR
STULOAD.RANGE— D4 — UKC240016/1: scj_load = 999. - ERROR
StudentCourseSession.MODE.PRESENCE— D5 — UKC240019/1: scj_mode = A (unmapped upstream — the beat-2 crosswalk finding's downstream rule).
Five defects in, five findings out, each pointing at the exact row and column. Every violation carries a hesaCode (provisional DBQ-… catalogue entries, verified:false, until the published HESA catalogue is transcribed).
Synthetic fixture — fictional students and marks; provider identifiers are public register facts.
- 4Identity reconciliation — nothing lost in translationThe fixture's 20 STU rows (tagged sits) and the 20 mapped candidate records (tagged sjms5) go to POST /v1/reconciliation/report under the exact identity policy.Matched20sits-only0sjms5-only0Conflicting0
The sjms5 side HERE is still the MAPPED CANDIDATE — no load has happened yet. Beat 7 runs the same harness against rows read back from a genuinely loaded target.
Synthetic fixture — fictional students and marks; provider identifiers are public register facts.
- 5The franchise slice — extended tables, split by partnerFour extended SITS tables — SPR (programme routes incl. two partner Level-6 top-up routes), SMR (module results), SAS (assessment attempts), CAP (applications) — crosswalked through the same spec and engine, then split by the CRS awarding-body column using real, public partner identifiers.ProgrammeRoute22ModuleResult27AssessmentAttempt6Application20
- WARN
HESA-MAP-APELCRD— ProgrammeRoute/UKC240001/2 and UKC240011/2: source value "240" for APELCRD has no mapping — APEL/advanced-standing credit has NO canonical HESA field, so the value is flagged, never guessed.
Per-partner split: University of West London (UKPRN 10006566 · INSTID 0080) 10 HND students · 15 module results · 1 top-up route; Plymouth Marjon University (UKPRN 10037449 · INSTID 0014) 0 · 0 · 1; Ravensbourne University London (UKPRN 10005389 · INSTID 0030) 10 · 12 · 0.
Zero validate violations over the extended entities is ABSENCE OF COVERAGE (the Student-stream pack carries no rules for them), not a clean bill — stated on-face in the transcript.
Synthetic fixture — fictional students and marks; provider identifiers are public register facts.
- 6Migration-policy decision points — named, never executedThe two SPR top-up rows carrying scj_apel_cred=240 are printed with their awarding partner, then the migration-policy decision slots they exercise are named — slot names and strategy vocabularies read live from the real packages/migration-policy schema, so a schema rename fails the demo loudly.Top-up routes2Policy executednone
UKC240001/2 TOP-BUS-UWL → University of West London (10006566) · scj_apel_cred=240; UKC240011/2 TOP-COM-MAR → Plymouth Marjon University (10037449) · scj_apel_cred=240.
classificationGap — strategies: skip | queue-for-registry | default-band (default queue-for-registry); multiCurriculum — strategies: primary-only | preserve-all | combine-joint (default primary-only).
The worked answers — a complete strict-parsed sits→sjms5 policy with all ten slots filled — live in packages/migration-policy/examples/ukcbc-deployment.
Synthetic fixture — fictional students and marks; provider identifiers are public register facts.
- 7The load leg — target-adapter-sjms5 into SJMS-5's governed ingest@databridge/target-adapter-sjms5 maps the validated candidate to SJMS-5's governed POST /v1/migration/students/ingest and drives dry-run → live → read-back → source-vs-LOADED-target reconciliation — through SJMS-5's own front door, never a direct database write.Status (unconfigured)SKIPPED_NOT_CONFIGUREDCandidates that map19 of 20Excluded for remediation1 (D2, named ERROR)Idempotent re-runupdated/skipped, no duplicates
Dormant until SJMS5_INGEST_URL plus a credential (SJMS5_INGEST_TOKEN, or SJMS5_INGEST_PERSONA on a demo twin) are set — without them the beat sends nothing anywhere and beats 1–6 are byte-identical. Intended for a LOCAL twin; loading production is an operator runbook decision this demo never takes.
The load leg never reformats a value: the seeded D2 date-drift row is excluded with a named ERROR (remediate at source and re-run); the D5 unmapped mode code only omits the optional modeOfStudy field (WARN) — the student still loads.
A 2xx alone is never treated as a load: LOADED requires the whole parseable JSON ingest envelope ({received, created, updated, skipped, failed, dryRun, errors}) — pinned by a negative-control test.
Synthetic fixture — fictional students and marks; provider identifiers are public register facts.
- The Banner→SJMS-5 route and the orchestrator profiles.
- Loading anything beyond the student + enrolment spine — module results, registrations, assessment attempts, applications and the top-up routes are mapped and reconciled (beats 5–6) but never loaded.
- A live HESA Gateway submission backend (deliberately not invented).
- Full published rule parity — the DBQ-… codes are provisional, verified:false, until the published catalogue is transcribed.