Skip to content

Genesis 03 — The synthetic cast and the career arc

Six commits in 24 hours built a synthetic-data generator that walks career trajectories rather than emitting flat rows

A pack’s first weeks need a body. Real customer data is sensitive, slow to arrive, and inconvenient to share. Synthetic data is the alternative — but most synthetic-data generators emit flat rows that are statistically plausible per-row and operationally implausible per-career. A row says “this person started here on this date, with this role, with this department”. The next row says “a different person started elsewhere on a different date, with no relationship to the first row”. Nothing about the data resembles a working institution.

On Wednesday-Thursday of hrcentral’s first week (May 6–7), the pack built synth-v2 — a generator that walks career arcs rather than emitting rows. Six commits in 24 hours, each named Pass A.N (with a sister Pass B for SIS-side artefacts). By Thursday evening the generator orchestrated all generators behind a single command, and the worked-example tenant had a population that behaved like an academic institution.

Before synth-v2, the pack had a flat-row generator inherited from nuMetrix patterns. It produced:

  • A list of persons with random names, birth dates, roles.
  • A list of org units with a flat hierarchy.
  • A list of assignments linking persons to positions to org units.

Each table was internally consistent. Across tables, they were not. A person could have a teaching assignment without ever having been a PhD student. A doctoral-affiliation could start after the assignment that should have qualified it. The data passed is_valid checks but didn’t cohere across the analytical pyramid.

A signal like signal_affiliation_drift produced findings — but the findings were generator artefacts, not domain artefacts. The signal worked; the data didn’t tell a story.

Six discrete passes across Wednesday-Thursday, each adding one layer:

Pass A.1 — 66b0aee“lift VAI cast to tenants/vai/synthetic.yaml”. The 18 named persons of the worked-example tenant move out of generator code into a config file. The cast becomes data, not code. Each person has a biography: name, birth year, joining date, initial role, intended career arc.

Pass A.2 — e492dc8“career-arc walker + multi-row emp_job chains”. The walker is the central invention. Given a person’s biography, it generates a sequence of emp_job rows that read as a plausible career: hire → promote → transfer → role-change → separate. Multi-row, time-ordered, overlapping at transitions. Not just a list of jobs; a chain of role-changes that compose.

Pass A.2.1 — 81fbc15“tune walker for visible chain motion”. The walker had been producing chains, but the transitions weren’t visible in downstream Lab views (the duration of role overlaps was too short to render). This pass tuned the walker’s timing distributions so that a career arc, viewed in the Hiring Lab time-axis, looks like the staircase a real career looks like.

Pass A.3 — 2241f03“cast career chains + Affiliations generator”. With careers generated, the Affiliations generator runs alongside. A person’s career arc determines what affiliations they accumulate: a teaching contract opens a teaching affiliation; a PhD enrolment opens a student affiliation; a research grant opens a research affiliation. Multi-role concurrency emerges naturally from the career walker’s output.

Pass A.4 — d3c918a“population qualifications + state-file coordination”. Each person accumulates qualifications over their career — degrees earned, certifications acquired. The state file tracks which qualifications each person has, so subsequent passes can read consistent state. This is the moment the generator becomes stateful.

Pass B — f90868a“SIS cohort generator”. A separate generator, living in SIS rather than source/, produces population blocks (bachelor / master / doctoral / postdoc) over time. The cohorts are the macro-population; the careers are the individuals within them. Both must reconcile.

Pass A.5 — 3e4042a“orchestrator — generate_tenant.py wires all generators”. The single-command entry point. A pack maintainer types python3 scripts/generate_tenant.py vai and the full population, careers, affiliations, qualifications, cohorts get generated in deterministic order.

The total work: ~36 hours of design and code, six commits.

Three things stop working when synthetic data is flat-row:

  1. Temporal Labs are unusable. A Hiring Lab that visualises new-hire rate over time needs hires to cluster the way real hires cluster (university semesters, fiscal-year onboarding). Flat-row generators don’t cluster — they emit uniform random distributions over time. The Lab renders something but the something doesn’t teach anything.
  2. Concurrency signals don’t fire honestly. signal_dual_affiliation_overload flags persons whose concurrent affiliations exceed plausible workload. Against flat-row data this signal either fires on everyone (random concurrency is always above threshold) or no one (random concurrency clusters around the mean). Neither is useful.
  3. The worked-example tenant doesn’t read as an institution. A demo of hrcentral on flat-row data shows tables and charts but doesn’t tell a story. A demo on career-arc data shows tables and charts that describe a real academic institution — promotions happening, contracts renewing, doctoral candidates completing, succession risks emerging. The difference is the difference between a feature demo and a working analytical product.

By Thursday evening, the worked-example tenant had:

  • 18 named persons across the cast, each with a complete career arc
  • Multi-row emp_job chains showing hire → promote → transfer → separate
  • Affiliations layered on careers (teaching, student, research, contract)
  • Qualifications accumulating across careers (degrees, certifications)
  • Cohort blocks tracking macro-population over time
  • A deterministic generator runnable with one command

Every Lab variant declared the following day (Genesis 01) — Critical-Roles, Hiring, Cohorts — could read this data and render something that resembled an institution. The pack’s first 641-finding thesis would fire on Sunday against this synthetic population, and the findings would be domain-shaped artefacts, not generator-shaped artefacts.

Because the synthetic-data work is the kind of week-1 investment that pays for the next six months but doesn’t make any of the marketing materials. The career-arc walker is not a feature you describe to a prospect. It is the prerequisite that lets every later feature read honestly. Six months from now, no one will remember that the pack spent 36 hours on synth-v2 in Week 1; everyone will benefit from the fact that it did. The Genesis post is for the record — naming the work that becomes invisible after it succeeds.

  • Commits: 66b0aee, e492dc8, 81fbc15, 2241f03, d3c918a, f90868a, 3e4042a — all 2026-05-07
  • The orchestrator: scripts/generate_tenant.py (Pass A.5)
  • The worked-example tenant: VAI (Valdoria Academic Institutions)
  • The first signal that benefits: signal_dual_affiliation_overload
  • The first Lab that benefits: Hiring Lab
jazzisnow jinflow is a jazzisnow product
v0.64.7 · built 2026-09-20 19:48 UTC