/* Recruitment Desk — the fixture. One record, one entry: every screen reads
   these arrays, and no screen writes a second literal to match. Nothing is
   stored that can be derived — days in stage, funnel rates, panel averages,
   pipeline health, the talent pool itself and every count in the product are
   computed in metrics.js from what is here.

   Three things deliberately NOT stored:
   · Whether an application is stalled. That is days in its current stage
     against that stage's own service level (APPLICATION_FLAGS.isStalled).
   · The talent pool. It is the declined and withdrawn people we said we would
     talk to again — a predicate over this same array, not a second list that
     could disagree with it.
   · A panel average. It is the mean of the scorecards on the record, and it
     changes the moment one is added.

   The narrative agrees with the records: a note that names a panel, a reference
   or an offer is dated after the thing it names, and says what the record says. */

const DAY_MS = 864e5;
const d0 = (iso) => new Date(iso + "T00:00:00").getTime();
const d1 = (iso) => new Date(iso + "T23:59:59").getTime();
const at = (iso) => new Date(iso).getTime();

const RD_NOW = at("2026-08-28T09:20:00");

const RD_ORG = {
  name: "Larkspur Health",
  desk: "Recruitment Desk",
  what: "Community and hospital group, 4,100 people",
  domain: "larkspurhealth.uk",
};

/* ── The desk and the panel ───────────────────────────────────────────────
   Nine people: two recruiters, a coordinator, the head of resourcing, three
   hiring managers and two clinicians who sit on panels.

   Faces are cast, not hashed. PORTRAITS names one photograph per person on this
   fixture — thirty-three people, thirty-three different photographs — chosen so
   the face agrees with the name and the role: nobody is shown as the wrong sex,
   the desk skews older than the newly qualified applicants, and the register
   numbers sit with faces of the right professional age. A person therefore keeps
   the same face on every screen because there is one entry for her, not because
   a hash happens to be stable.

   SPARE_WOMEN and SPARE_MEN are the photographs no fixture person holds; they
   dress candidates created in the running product, where the only thing known
   about the name is what the recruiter typed. MEN lists who the men are, which
   is what lets a spare be allocated without guessing from the name. */
const img = (file) => "./assets/img/" + file;
const PORTRAITS = {
  /* The desk */
  "Nadia Ferreira": "avatar-f-17.jpg",
  "Joel Whitcombe": "avatar-m-13.jpg",
  "Marek Zielinski": "avatar-m-1.jpg",
  "Amara Boateng": "avatar-f-9.jpg",
  "Esme Okonkwo": "avatar-80-06.jpg",
  "Fern Adeyemi": "avatar-f-6.jpg",
  "David Aliyev": "avatar-m-10.jpg",
  "Ruth Calloway": "avatar-f-7.jpg",
  "Tomas Iglesias": "avatar-m-20.jpg",
  /* Applied */
  "Aisha Farrow": "avatar-f-1.jpg",
  "Callum Beattie": "avatar-m-2.jpg",
  "Dilnoza Rakhimova": "avatar-80-02.jpg",
  "Michael Osei-Bonsu": "avatar-m-14.jpg",
  "Yasmin Qureshi": "avatar-f-18.jpg",
  /* Screening */
  "Grace Lindqvist": "avatar-f-14.jpg",
  "Tobias Vale": "avatar-m-15.jpg",
  "Nkechi Ibrahim": "avatar-f-16.jpg",
  "Samuel Doherty": "avatar-m-19.jpg",
  /* Assessment */
  "Helena Marquez": "avatar-80-05.jpg",
  "Ravi Chandran": "avatar-m-9.jpg",
  "Bea Whitlock": "avatar-f-11.jpg",
  /* Panel */
  "Priya Raghunathan": "avatar-f-13.jpg",
  "Jonah Okwu": "avatar-80-03.jpg",
  "Marta Kaczmarek": "avatar-f-5.jpg",
  /* Checks */
  "Elias Nordahl": "avatar-m-4.jpg",
  "Sonia Bhattacharya": "avatar-f-2.png",
  /* Offer */
  "Freya Lindsay": "avatar-80-04.jpg",
  "Damian Oyelaran": "avatar-m-6.jpg",
  /* Settled */
  "Adaeze Nwankwo": "avatar-f-12.jpg",
  "Peter Halloran": "avatar-m-12.jpg",
  "Ines Duarte": "avatar-f-3.jpg",
  "Tom Sefton": "avatar-m-16.jpg",
  "Lila Farid": "avatar-f-8.jpg",
};
const SPARE_WOMEN = ["avatar-f-4.jpg", "avatar-f-10.jpg", "avatar-f-15.jpg"];
const SPARE_MEN = ["avatar-m-3.jpg", "avatar-m-5.jpg", "avatar-m-7.jpg", "avatar-80-01.jpg", "avatar.jpg"];
const MEN = new Set([
  "Joel Whitcombe", "Marek Zielinski", "David Aliyev", "Tomas Iglesias",
  "Callum Beattie", "Michael Osei-Bonsu", "Tobias Vale", "Samuel Doherty",
  "Ravi Chandran", "Jonah Okwu", "Elias Nordahl", "Damian Oyelaran",
  "Peter Halloran", "Tom Sefton",
]);
const allocated = new Map();
const nextSpare = { women: 0, men: 0 };
const portraitFor = (name) => {
  if (PORTRAITS[name]) return img(PORTRAITS[name]);
  if (allocated.has(name)) return allocated.get(name);
  const man = MEN.has(name);
  const list = man ? SPARE_MEN : SPARE_WOMEN;
  const key = man ? "men" : "women";
  const file = img(list[nextSpare[key]++ % list.length]);
  allocated.set(name, file);
  return file;
};
const initialsOf = (name) => name.split(" ").map((w) => w[0]).join("").slice(0, 2);

const S = (slug, name, title, role, department, site) => ({
  id: "p-" + slug, name, initials: initialsOf(name), photo: portraitFor(name),
  email: name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/ /g, ".") + "@" + RD_ORG.domain,
  title, role, department, site,
});

const STAFF = [
  S("nadia", "Nadia Ferreira", "Recruiter, clinical", "recruiter", "nursing", "hq"),
  S("joel", "Joel Whitcombe", "Recruiter, corporate and digital", "recruiter", "digital", "hq"),
  S("marek", "Marek Zielinski", "Resourcing coordinator", "coordinator", "operations", "hq"),
  S("amara", "Amara Boateng", "Head of resourcing", "lead", "operations", "hq"),
  S("esme", "Esme Okonkwo", "Community nursing lead", "hiring_manager", "nursing", "community"),
  S("fern", "Fern Adeyemi", "Therapies service manager", "hiring_manager", "therapies", "eastgate"),
  S("david", "David Aliyev", "Head of clinical systems", "hiring_manager", "digital", "hq"),
  S("ruth", "Ruth Calloway", "Matron, community teams", "panel", "nursing", "community"),
  S("tomas", "Tomas Iglesias", "Principal engineer", "panel", "digital", "hq"),
];

const RD_SESSION = { personId: "p-nadia", role: "recruiter" };

/* ── Requisitions ─────────────────────────────────────────────────────────
   `headcount` is how many seats this requisition carries, so a hire does not
   necessarily fill it — /requisitions counts hires against headcount rather
   than assuming one each. */
const R = (ref, title, department, site, type, bandMajor, hiringManagerId, recruiterId, headcount, openedISO, targetISO, status, extra) => Object.assign({
  id: "rq-" + ref, ref: "LH-" + ref, title, department, site, employmentType: type,
  bandMinor: [bandMajor[0] * 100, bandMajor[1] * 100],
  hiringManagerId, recruiterId, headcount,
  openedAt: d0(openedISO), targetStartAt: d0(targetISO), status,
  /* Which professional register this SEAT requires, null where none does. It is
     the requisition that knows — the advert's essentials say the same thing in
     words — so compliance can tell a nurse with no NMC number from a ward clerk
     who needs none. */
  registration: null,
  holdNote: null, closedNote: null, closedAt: null,
}, extra || {});

const REQUISITIONS = [
  R("1042", "Community staff nurse", "nursing", "community", "permanent", [31049, 37796], "p-esme", "p-nadia", 3, "2026-06-30", "2026-10-05", "open",
    { registration: "NMC", brief: "Three seats across the north and west community teams. Band 5, NMC registration essential, driving licence essential." }),
  R("1051", "Senior physiotherapist", "therapies", "eastgate", "permanent", [38682, 46580], "p-fern", "p-nadia", 1, "2026-07-27", "2026-11-02", "open",
    { registration: "HCPC", brief: "Musculoskeletal outpatients at Eastgate. HCPC registration essential, first-contact practitioner experience desirable." }),
  R("1063", "Clinical systems engineer", "digital", "hq", "permanent", [46148, 52809], "p-david", "p-joel", 1, "2026-08-10", "2026-10-26", "open",
    { brief: "Integration work on the shared care record. On-call rota one week in six." }),
  R("1067", "Ward clerk", "operations", "riverside", "fixed_term", [24071, 25674], "p-david", "p-joel", 1, "2026-08-17", "2026-09-28", "open",
    { brief: "Twelve months, Bramble ward. Cover for a secondment." }),
  R("1058", "Occupational therapist", "therapies", "community", "permanent", [35392, 42618], "p-fern", "p-nadia", 1, "2026-07-13", "2026-10-19", "on_hold",
    { registration: "HCPC", holdNote: "Establishment review in the community division — Amara is taking it to the October finance meeting. Two live applicants have been told." }),
  R("1039", "Night nurse practitioner", "nursing", "riverside", "permanent", [43742, 50056], "p-esme", "p-nadia", 1, "2026-05-18", "2026-09-07", "filled",
    { registration: "NMC", brief: "Nights, Riverside acute floor." }),
];

/* ── Applications ─────────────────────────────────────────────────────────
   `stageEnteredAt` is what the stepper and every clock read; `firstReadAt` is
   null until somebody opens the application, which is the only thing that makes
   an application unread. Documents, scorecards and references are sub-records on
   the application because they are meaningless without it. */
const doc = (kind, name, iso) => ({ kind, name, at: at(iso) });
const card = (byId, iso, scores, note, recommend) => ({ byId, at: at(iso), scores, note, recommend });
const ref = (name, relationship, status, requestedISO, extra) => Object.assign({
  id: "rf-" + name.toLowerCase().replace(/[^a-z]/g, "").slice(0, 8), name, relationship, status,
  requestedAt: d0(requestedISO), receivedAt: null, note: null,
}, extra || {});

const A = (id, name, headline, location, requisitionId, stage, source, appliedISO, stageISO, extra) => Object.assign({
  id: "ap-" + id, ref: "APP-" + id,
  candidate: { name, initials: initialsOf(name), photo: portraitFor(name), headline, location },
  requisitionId, stage, source,
  appliedAt: at(appliedISO), stageEnteredAt: at(stageISO), firstReadAt: null,
  ownerId: null, rightToWork: "documents_seen", salaryExpectationMinor: null,
  /* DBS is a status like any other (DBS_STATUS in enums.js), so it is present on
     every application rather than only on the ones that have reached it. */
  dbs: "not_started", dbsAt: null, registration: null, registrationExpiresAt: null,
  documents: [], scorecards: [], references: [], offer: null, lastLiveStage: null,
  watcherIds: [], declineReason: null, declineNote: null, wouldReconsider: false, decidedAt: null, decidedById: null,
  withdrawReason: null, withdrawNote: null,
}, extra || {});

const APPLICATIONS = [
  /* ── Applied: nobody has read three of these five ───────────────────── */
  A("3101", "Aisha Farrow", "Band 5 nurse, district team, three years", "Eastgate", "rq-1042", "applied", "careers_site", "2026-08-27T18:42", "2026-08-27T18:42",
    { ownerId: "p-nadia", salaryExpectationMinor: 3400000, registration: "NMC 21F4478", registrationExpiresAt: d1("2027-03-31"), documents: [doc("cv", "Aisha-Farrow-CV.pdf", "2026-08-27T18:42"), doc("statement", "Supporting statement", "2026-08-27T18:42")] }),
  A("3102", "Callum Beattie", "Newly qualified nurse, September cohort", "Riverside", "rq-1042", "applied", "event", "2026-08-27T09:05", "2026-08-27T09:05",
    { ownerId: "p-nadia", salaryExpectationMinor: 3104900, registration: "NMC pending — September", documents: [doc("cv", "C-Beattie-CV.pdf", "2026-08-27T09:05")] }),
  A("3103", "Dilnoza Rakhimova", "Integration engineer, HL7 and FHIR", "Remote, Manchester", "rq-1063", "applied", "nhs_jobs", "2026-08-26T21:14", "2026-08-26T21:14",
    { ownerId: "p-joel", rightToWork: "needs_sponsorship", salaryExpectationMinor: 5500000, documents: [doc("cv", "Rakhimova-CV.pdf", "2026-08-26T21:14")] }),
  A("3104", "Michael Osei-Bonsu", "Ward administrator, six years", "Riverside", "rq-1067", "applied", "referral", "2026-08-25T11:30", "2026-08-25T11:30",
    { ownerId: "p-joel", firstReadAt: at("2026-08-26T08:40"), salaryExpectationMinor: 2500000, referredById: "p-marek",
      documents: [doc("cv", "MOB-CV.pdf", "2026-08-25T11:30")] }),
  A("3105", "Yasmin Qureshi", "MSK physiotherapist, rotational band 6", "Eastgate", "rq-1051", "applied", "nhs_jobs", "2026-08-21T16:02", "2026-08-21T16:02",
    { ownerId: "p-nadia", firstReadAt: at("2026-08-24T09:12"), salaryExpectationMinor: 4200000, registration: "HCPC PH108842", registrationExpiresAt: d1("2026-10-31"),
      documents: [doc("cv", "Y-Qureshi-CV.pdf", "2026-08-21T16:02"), doc("statement", "Supporting statement", "2026-08-21T16:02")] }),

  /* ── Screening ──────────────────────────────────────────────────────── */
  A("3088", "Grace Lindqvist", "Community nurse, returning after two years abroad", "Community, north", "rq-1042", "screening", "careers_site", "2026-08-14T10:20", "2026-08-24T11:00",
    { ownerId: "p-nadia", firstReadAt: at("2026-08-17T09:30"), rightToWork: "verified", salaryExpectationMinor: 3500000, registration: "NMC 18L2210", registrationExpiresAt: d1("2027-05-31"),
      watcherIds: ["p-esme"], documents: [doc("cv", "G-Lindqvist-CV.pdf", "2026-08-14T10:20")] }),
  A("3089", "Tobias Vale", "Integration developer, private sector", "Larkspur House", "rq-1063", "screening", "agency", "2026-08-13T14:45", "2026-08-25T15:30",
    { ownerId: "p-joel", firstReadAt: at("2026-08-14T08:05"), rightToWork: "verified", salaryExpectationMinor: 5800000,
      documents: [doc("cv", "TVale.pdf", "2026-08-13T14:45")] }),
  A("3090", "Nkechi Ibrahim", "Band 5 nurse, acute to community move", "Riverside", "rq-1042", "screening", "referral", "2026-08-12T08:50", "2026-08-18T09:00",
    { ownerId: "p-nadia", firstReadAt: at("2026-08-12T15:20"), rightToWork: "verified", salaryExpectationMinor: 3300000, registration: "NMC 20I8830", registrationExpiresAt: d1("2026-09-30"), referredById: "p-ruth",
      watcherIds: ["p-esme", "p-ruth"], documents: [doc("cv", "N-Ibrahim-CV.pdf", "2026-08-12T08:50")] }),
  A("3091", "Samuel Doherty", "Ward clerk, bank staff with us since March", "Riverside", "rq-1067", "screening", "pool", "2026-08-19T12:10", "2026-08-26T10:15",
    { ownerId: "p-joel", firstReadAt: at("2026-08-19T16:40"), rightToWork: "verified", salaryExpectationMinor: 2450000,
      documents: [doc("cv", "S-Doherty-CV.pdf", "2026-08-19T12:10")] }),

  /* ── Assessment ─────────────────────────────────────────────────────── */
  A("3072", "Helena Marquez", "Senior physiotherapist, first-contact practitioner", "Eastgate", "rq-1051", "assessment", "nhs_jobs", "2026-08-04T19:30", "2026-08-20T09:00",
    { ownerId: "p-nadia", firstReadAt: at("2026-08-05T08:15"), rightToWork: "verified", salaryExpectationMinor: 4500000, registration: "HCPC PH094117", registrationExpiresAt: d1("2027-01-31"),
      watcherIds: ["p-fern"], documents: [doc("cv", "HMarquez-CV.pdf", "2026-08-04T19:30"), doc("case", "MSK case study response", "2026-08-24T22:05")] }),
  A("3073", "Ravi Chandran", "Systems engineer, shared care records", "Larkspur House", "rq-1063", "assessment", "careers_site", "2026-08-11T13:05", "2026-08-21T14:00",
    { ownerId: "p-joel", firstReadAt: at("2026-08-11T17:50"), rightToWork: "verified", salaryExpectationMinor: 5100000,
      watcherIds: ["p-david", "p-tomas"], documents: [doc("cv", "R-Chandran-CV.pdf", "2026-08-11T13:05"), doc("case", "Integration exercise", "2026-08-26T20:40")] }),
  A("3074", "Bea Whitlock", "Community nurse, band 5, rural caseload", "Community, west", "rq-1042", "assessment", "careers_site", "2026-08-06T09:40", "2026-08-13T09:00",
    { ownerId: "p-nadia", firstReadAt: at("2026-08-06T11:20"), rightToWork: "verified", salaryExpectationMinor: 3600000, registration: "NMC 17W6642", registrationExpiresAt: d1("2026-11-30"),
      watcherIds: ["p-esme"], documents: [doc("cv", "B-Whitlock-CV.pdf", "2026-08-06T09:40")] }),

  /* ── Panel ──────────────────────────────────────────────────────────── */
  A("3051", "Priya Raghunathan", "Band 6 community nurse, eight years, prescriber", "Community, north", "rq-1042", "panel", "referral", "2026-07-24T20:15", "2026-08-25T09:00",
    { ownerId: "p-nadia", firstReadAt: at("2026-07-25T08:30"), rightToWork: "verified", salaryExpectationMinor: 3750000, registration: "NMC 14R1187", registrationExpiresAt: d1("2027-07-31"), referredById: "p-esme",
      /* The checks pack went out on the 27th with the references — the disclosure
         is with the service and nobody here can move it along. */
      dbs: "submitted", dbsAt: at("2026-08-27T09:15"),
      watcherIds: ["p-esme", "p-ruth", "p-amara"],
      documents: [doc("cv", "Priya-Raghunathan-CV.pdf", "2026-07-24T20:15"), doc("statement", "Supporting statement", "2026-07-24T20:15"), doc("case", "Caseload scenario response", "2026-08-18T21:10")],
      scorecards: [
        card("p-ruth", "2026-08-26T17:40", { clinical: 4, people: 5, judgement: 4, values: 5 },
          "Talked through a deteriorating patient on a Friday afternoon with no GP cover and got to the right answer without drama. Would have her on my team.", "yes"),
        card("p-esme", "2026-08-26T19:05", { clinical: 4, people: 4, judgement: 5, values: 4 },
          "Strong on prioritising a mixed caseload. Prescribing is current. Asked good questions about the north team's vacancy gap — she has read the job, not the advert.", "yes"),
      ],
      references: [
        ref("Hannah Iversen", "Current line manager, Meadowvale Trust", "requested", "2026-08-27"),
        ref("Dr Owen Marsh", "GP, north locality", "requested", "2026-08-27"),
      ] }),
  A("3052", "Jonah Okwu", "Band 5 nurse, urgent care", "Riverside", "rq-1042", "panel", "nhs_jobs", "2026-07-29T11:00", "2026-08-27T14:00",
    { ownerId: "p-nadia", firstReadAt: at("2026-07-29T14:10"), rightToWork: "verified", salaryExpectationMinor: 3200000, registration: "NMC 19O4520", registrationExpiresAt: d1("2026-12-31"),
      watcherIds: ["p-esme"],
      documents: [doc("cv", "J-Okwu-CV.pdf", "2026-07-29T11:00")],
      scorecards: [
        card("p-ruth", "2026-08-27T18:20", { clinical: 3, people: 4, judgement: 3, values: 4 },
          "Sound, a bit early in his career for a lone-working caseload. Esme's card will decide it — she saw the second half.", "maybe"),
      ] }),
  A("3053", "Marta Kaczmarek", "Clinical systems analyst, integration lead", "Larkspur House", "rq-1063", "panel", "agency", "2026-07-30T16:20", "2026-08-24T10:00",
    { ownerId: "p-joel", firstReadAt: at("2026-07-31T09:00"), rightToWork: "verified", salaryExpectationMinor: 5300000,
      watcherIds: ["p-david", "p-tomas"],
      documents: [doc("cv", "M-Kaczmarek-CV.pdf", "2026-07-30T16:20"), doc("case", "Integration exercise", "2026-08-19T20:00")],
      scorecards: [
        card("p-tomas", "2026-08-25T12:15", { clinical: 5, people: 4, judgement: 4, values: 4 },
          "Best answer on the shared care record we have had. Knew the failure modes of the message queue before I described them.", "yes"),
        card("p-david", "2026-08-25T16:45", { clinical: 5, people: 3, judgement: 4, values: 4 },
          "Technically the strongest in the field. Will need support presenting to the clinical safety group — not a blocker.", "yes"),
      ] }),

  /* ── Checks: one waiting on a referee, one ready for the offer ───────── */
  A("3020", "Elias Nordahl", "Senior physiotherapist, spinal service", "Eastgate", "rq-1051", "checks", "careers_site", "2026-07-10T10:00", "2026-08-19T09:00",
    { ownerId: "p-nadia", firstReadAt: at("2026-07-10T14:30"), rightToWork: "verified", salaryExpectationMinor: 4400000, registration: "HCPC PH077310", registrationExpiresAt: d1("2026-09-14"),
      watcherIds: ["p-fern"], dbs: "clear", dbsAt: d1("2026-08-20"),
      documents: [doc("cv", "E-Nordahl-CV.pdf", "2026-07-10T10:00"), doc("case", "MSK case study response", "2026-07-28T12:00")],
      scorecards: [
        card("p-fern", "2026-08-18T15:00", { clinical: 5, people: 4, judgement: 4, values: 5 }, "Ready for the senior post today. Clear about supervision and happy to run the Friday clinic.", "yes"),
        card("p-ruth", "2026-08-18T16:10", { clinical: 4, people: 5, judgement: 4, values: 5 }, "Patients will like him. Gave a straight answer about the incident he was involved in two years ago.", "yes"),
      ],
      references: [
        ref("Marianne Voss", "Current line manager, Eastgate private clinic", "received", "2026-08-19", { receivedAt: at("2026-08-21T11:20"), note: "Six years, no concerns, would re-employ." }),
        ref("Sarah Beeching", "Clinical supervisor", "chased", "2026-08-19", { note: "Out of office until Monday 31st — auto-reply. Chased 26 Aug." }),
      ] }),
  A("3021", "Sonia Bhattacharya", "Band 5 nurse, community rotation", "Community, west", "rq-1042", "checks", "careers_site", "2026-07-06T09:15", "2026-08-14T09:00",
    { ownerId: "p-nadia", firstReadAt: at("2026-07-06T13:00"), rightToWork: "verified", salaryExpectationMinor: 3250000, registration: "NMC 20B3391", registrationExpiresAt: d1("2027-02-28"),
      watcherIds: ["p-esme"], dbs: "clear", dbsAt: d1("2026-08-12"),
      documents: [doc("cv", "S-Bhattacharya-CV.pdf", "2026-07-06T09:15")],
      scorecards: [
        card("p-ruth", "2026-08-13T16:00", { clinical: 4, people: 4, judgement: 4, values: 4 }, "Good fit for the west team. Knows the patch.", "yes"),
        card("p-esme", "2026-08-13T17:30", { clinical: 4, people: 4, judgement: 3, values: 5 }, "Yes from me. Would want her paired with Ruth for the first month.", "yes"),
      ],
      references: [
        ref("Priti Raval", "Ward manager, Riverside", "received", "2026-08-14", { receivedAt: at("2026-08-17T09:40"), note: "Two years on Bramble, reliable, left to travel." }),
        ref("Alan Kerr", "Practice educator", "received", "2026-08-14", { receivedAt: at("2026-08-20T14:15"), note: "Supported her preceptorship. No concerns." }),
      ] }),

  /* ── Offer: one out with the candidate, one accepted ─────────────────── */
  A("2990", "Freya Lindsay", "Band 6 community nurse, prescriber", "Community, north", "rq-1042", "offer", "nhs_jobs", "2026-06-29T12:00", "2026-08-24T10:30",
    { ownerId: "p-nadia", firstReadAt: at("2026-06-29T15:30"), rightToWork: "verified", salaryExpectationMinor: 3700000, registration: "NMC 15L9902", registrationExpiresAt: d1("2027-06-30"),
      watcherIds: ["p-esme", "p-amara"], dbs: "clear", dbsAt: d1("2026-08-19"),
      documents: [doc("cv", "F-Lindsay-CV.pdf", "2026-06-29T12:00")],
      scorecards: [
        card("p-esme", "2026-08-11T15:20", { clinical: 4, people: 5, judgement: 4, values: 4 }, "Strong. Wants the north patch specifically, which is the seat we are struggling to fill.", "yes"),
        card("p-ruth", "2026-08-11T17:00", { clinical: 4, people: 4, judgement: 4, values: 5 }, "No hesitation.", "yes"),
      ],
      references: [
        ref("Gillian Frayne", "Current line manager", "received", "2026-08-12", { receivedAt: at("2026-08-14T10:05"), note: "Would re-employ without question." }),
        ref("Dr Neil Ashworth", "GP partner", "received", "2026-08-12", { receivedAt: at("2026-08-18T16:30"), note: "Confident lone worker." }),
      ],
      offer: { salaryMinor: 3679600, startAt: d0("2026-10-05"), sentAt: at("2026-08-24T10:30"), expiresAt: d1("2026-08-31"), acceptedAt: null, note: "Band 6 bottom point plus recruitment premium agreed with Amara." } }),
  A("2991", "Damian Oyelaran", "Ward clerk, twelve months' NHS admin", "Riverside", "rq-1067", "offer", "referral", "2026-07-20T08:30", "2026-08-21T11:00",
    { ownerId: "p-joel", firstReadAt: at("2026-07-20T10:00"), rightToWork: "verified", salaryExpectationMinor: 2480000,
      watcherIds: ["p-marek"], dbs: "clear", dbsAt: d1("2026-08-15"),
      documents: [doc("cv", "D-Oyelaran-CV.pdf", "2026-07-20T08:30")],
      scorecards: [card("p-david", "2026-08-14T14:00", { clinical: 4, people: 5, judgement: 4, values: 4 }, "Exactly what Bramble needs while Kemi is seconded.", "yes")],
      references: [
        ref("Tessa Marlowe", "Ward manager", "received", "2026-08-14", { receivedAt: at("2026-08-17T13:20"), note: "No concerns." }),
        ref("Ben Adeyeye", "Team leader, bank office", "received", "2026-08-14", { receivedAt: at("2026-08-18T09:10"), note: "Reliable on late notice." }),
      ],
      offer: { salaryMinor: 2407100, startAt: d0("2026-09-28"), sentAt: at("2026-08-21T11:00"), expiresAt: d1("2026-08-28"), acceptedAt: at("2026-08-27T19:40"), note: "Fixed term to 27 September 2027." } }),

  /* ── Settled ────────────────────────────────────────────────────────── */
  A("2870", "Adaeze Nwankwo", "Night nurse practitioner", "Riverside", "rq-1039", "hired", "nhs_jobs", "2026-05-26T18:00", "2026-08-07T09:00",
    { ownerId: "p-nadia", firstReadAt: at("2026-05-27T08:40"), rightToWork: "verified", registration: "NMC 12N7745", registrationExpiresAt: d1("2026-10-15"), dbs: "clear", dbsAt: d1("2026-07-20"),
      documents: [doc("cv", "A-Nwankwo-CV.pdf", "2026-05-26T18:00")],
      offer: { salaryMinor: 4650000, startAt: d0("2026-09-07"), sentAt: at("2026-07-28T10:00"), expiresAt: d1("2026-08-04"), acceptedAt: at("2026-08-03T12:15"), note: "Nights premium in the contract." } }),
  A("3010", "Peter Halloran", "Physiotherapist, band 5", "Eastgate", "rq-1051", "declined", "nhs_jobs", "2026-07-16T09:00", "2026-08-12T16:00",
    { ownerId: "p-nadia", firstReadAt: at("2026-07-16T12:00"), rightToWork: "verified", registration: "HCPC PH120664", registrationExpiresAt: d1("2027-04-30"),
      lastLiveStage: "assessment", declineReason: "experience", wouldReconsider: true, declineNote: "A year off the senior bar rather than the wrong person — no rotational MSK yet. Fern would see him for the band 5 seat when it comes back in the autumn.",
      decidedAt: at("2026-08-12T16:00"), decidedById: "p-nadia",
      documents: [doc("cv", "P-Halloran-CV.pdf", "2026-07-16T09:00")] }),
  A("3011", "Ines Duarte", "Integration engineer", "Remote, Porto", "rq-1063", "declined", "agency", "2026-08-05T22:10", "2026-08-20T10:00",
    { ownerId: "p-joel", firstReadAt: at("2026-08-06T09:00"), rightToWork: "needs_sponsorship",
      lastLiveStage: "screening", declineReason: "money", wouldReconsider: true, declineNote: "Expectation £68k against a band 7 top of £52,809. Told her plainly. Worth a call if the band 8a architecture post is funded.",
      decidedAt: at("2026-08-20T10:00"), decidedById: "p-joel",
      documents: [doc("cv", "I-Duarte-CV.pdf", "2026-08-05T22:10")] }),
  A("3012", "Tom Sefton", "Ward administrator", "Riverside", "rq-1067", "declined", "careers_site", "2026-08-18T13:40", "2026-08-24T09:30",
    { ownerId: "p-joel", firstReadAt: at("2026-08-19T08:30"), rightToWork: "documents_seen",
      lastLiveStage: "screening", declineReason: "stronger_field", wouldReconsider: false, declineNote: "Four stronger applications for one twelve-month seat. Nothing against him; the field was simply better.",
      decidedAt: at("2026-08-24T09:30"), decidedById: "p-joel",
      documents: [doc("cv", "T-Sefton-CV.pdf", "2026-08-18T13:40")] }),
  A("3013", "Lila Farid", "Community nurse, band 5", "Community, west", "rq-1042", "withdrawn", "careers_site", "2026-07-27T10:20", "2026-08-26T08:15",
    { ownerId: "p-nadia", firstReadAt: at("2026-07-27T14:00"), rightToWork: "verified", registration: "NMC 21F0093", registrationExpiresAt: d1("2027-08-31"),
      lastLiveStage: "panel", withdrawReason: "another_offer", wouldReconsider: true,
      withdrawNote: "Took a band 5 post nearer home before our panel date. Asked to be kept on file for the west patch — she lives on it.",
      decidedAt: at("2026-08-26T08:15"), decidedById: "p-nadia",
      documents: [doc("cv", "L-Farid-CV.pdf", "2026-07-27T10:20")] }),
];

/* ── Interviews ───────────────────────────────────────────────────────────
   One booked conversation, one row. `kind` names what it is for and matches the
   stage that books it, so the ladder reads against the stepper. */
const IV = (id, applicationId, kind, iso, minutes, panelIds, status, extra) => Object.assign({
  id: "iv-" + id, applicationId, kind, at: at(iso), minutes, panelIds, status,
  where: "Larkspur House, room 2.4", note: null,
}, extra || {});

const INTERVIEWS = [
  IV("501", "ap-3088", "screening", "2026-08-28T11:30", 30, ["p-nadia"], "scheduled", { where: "Telephone" }),
  IV("502", "ap-3089", "screening", "2026-08-28T15:00", 30, ["p-joel"], "scheduled", { where: "Telephone" }),
  IV("503", "ap-3090", "screening", "2026-08-31T09:30", 30, ["p-nadia"], "scheduled", { where: "Telephone" }),
  IV("504", "ap-3072", "assessment", "2026-09-01T13:00", 90, ["p-fern"], "scheduled", { where: "Eastgate Clinic, therapies office" }),
  IV("505", "ap-3073", "assessment", "2026-09-02T10:00", 90, ["p-tomas", "p-david"], "scheduled", { where: "Larkspur House, room 1.2" }),
  IV("506", "ap-3051", "panel", "2026-08-26T14:00", 60, ["p-esme", "p-ruth"], "complete", { where: "Community hub, north" }),
  IV("507", "ap-3053", "panel", "2026-08-25T11:00", 60, ["p-david", "p-tomas"], "complete", { where: "Larkspur House, room 2.4" }),
  IV("508", "ap-3052", "panel", "2026-08-27T13:30", 60, ["p-esme", "p-ruth"], "awaiting_feedback", { where: "Community hub, north" }),
  IV("509", "ap-3074", "assessment", "2026-08-26T09:30", 90, ["p-esme"], "awaiting_feedback", { where: "Community hub, north" }),
  IV("510", "ap-3091", "screening", "2026-08-25T16:00", 30, ["p-joel"], "no_show", { where: "Telephone", note: "Rang twice, no answer, voicemail left. He is bank staff on shift — Marek is finding another slot." }),
];

/* ── The thread ───────────────────────────────────────────────────────────
   Notes on an application, in the order they were written. This is where the
   desk actually talks: a mention pulls somebody in, a reaction is how the rest
   of the panel agrees without writing "agreed", and `seenBy` is who has read it.
   Every note here is dated after what it names and says what the record says. */
const N = (id, applicationId, authorId, iso, body, extra) => Object.assign({
  id: "nt-" + id, applicationId, authorId, at: at(iso), body, kind: "note", parentId: null,
  reactions: {}, seenBy: [],
}, extra || {});

const NOTES = [
  /* Priya Raghunathan — the application at panel, and the one being decided. */
  N("801", "ap-3051", "p-nadia", "2026-07-25T08:52",
    "Referred by @p-esme. Eight years community, independent prescriber, currently at Meadowvale on the same band. Lives on the north patch — that is the seat we have had open since April.",
    { seenBy: ["p-esme", "p-ruth", "p-amara"], reactions: { watching: ["p-esme", "p-ruth"] } }),
  N("802", "ap-3051", "p-esme", "2026-07-25T12:10",
    "Worth saying out loud: I worked with her at Meadowvale for two years, so I will sit on the panel but I am not scoring the values section on my own. @p-ruth to chair.",
    { kind: "decision", seenBy: ["p-nadia", "p-ruth", "p-amara"], reactions: { agree: ["p-nadia", "p-ruth", "p-amara"] } }),
  N("803", "ap-3051", "p-marek", "2026-08-18T16:30",
    "Caseload scenario back in, attached to the file. She sent it two days early.",
    { seenBy: ["p-nadia"], reactions: { agree: ["p-nadia"] } }),
  N("804", "ap-3051", "p-ruth", "2026-08-26T17:52",
    "Scorecard in — 4.5 average, yes from me. The Friday afternoon question is the one I always ask and she is the first this year to say she would ring the out-of-hours GP before the family.",
    { kind: "decision", seenBy: ["p-nadia", "p-esme", "p-amara"], reactions: { agree: ["p-esme", "p-nadia"], watching: ["p-amara"] } }),
  N("805", "ap-3051", "p-nadia", "2026-08-27T09:15",
    "Both scorecards are in, so checks can start. References went out this morning to Hannah Iversen and Dr Marsh, and the disclosure is with the DBS service. Nothing moves to offer until both references are back — @p-amara, the band 6 premium is the same shape as Freya Lindsay's if we get there.",
    { seenBy: ["p-esme", "p-amara"], reactions: { agree: ["p-esme"], question: ["p-amara"] } }),
  N("806", "ap-3051", "p-amara", "2026-08-27T13:40",
    "Same shape is fine. Two band 6 premiums on one requisition is the limit for this year, so if the third seat needs one it comes to me first.",
    { parentId: "nt-805", kind: "decision", seenBy: ["p-nadia", "p-esme"], reactions: { agree: ["p-nadia"] } }),
  N("807", "ap-3051", "p-esme", "2026-08-28T08:05",
    "She rang me directly — wants to know whether the north team covers the Thornbury villages, because it changes her drive. It does. Told her nothing about where we are with the decision.",
    { kind: "candidate", seenBy: ["p-nadia"], reactions: { agree: ["p-nadia"] } }),

  /* Jonah Okwu — the panel that is one scorecard short. */
  N("810", "ap-3052", "p-ruth", "2026-08-27T18:26",
    "Mine is in but I would not move him on my card alone — @p-esme saw the second half of the interview and the lone-working question is hers.",
    { seenBy: ["p-esme", "p-nadia"], reactions: { agree: ["p-nadia"] } }),
  N("811", "ap-3052", "p-nadia", "2026-08-28T08:40",
    "Chased the second scorecard. He is a strong band 5 for the acute floor if the community caseload is too early for him — that is a reopen for another seat, not a decline.",
    { seenBy: ["p-esme"], reactions: { watching: ["p-esme"] } }),

  /* Elias Nordahl — checks, waiting on one referee. */
  N("815", "ap-3020", "p-marek", "2026-08-26T11:05",
    "Second referee is out of office until Monday 31st. Chased today; Marianne's reference came back clean on the 21st. Right to work and DBS both done.",
    { seenBy: ["p-nadia", "p-fern"], reactions: { agree: ["p-fern"] } }),
  N("816", "ap-3020", "p-fern", "2026-08-26T15:20",
    "He has asked twice about a start date. Can we tell him the week of the 12th October assuming the reference lands Monday? @p-nadia",
    { kind: "candidate", seenBy: ["p-nadia"], reactions: { question: ["p-nadia"] } }),
  N("817", "ap-3020", "p-nadia", "2026-08-27T09:50",
    "I will tell him the week of the 12th is what we are aiming at and that it depends on one outstanding reference. No offer letter until it is in — that is the rule and it has saved us twice.",
    { parentId: "nt-816", kind: "decision", seenBy: ["p-fern", "p-marek"], reactions: { agree: ["p-fern", "p-marek"] } }),

  /* Freya Lindsay — offer out, expiring. */
  N("820", "ap-2990", "p-nadia", "2026-08-24T10:44",
    "Offer sent: £36,796 to start 5 October, valid to 31 August. She asked for the north patch and that is what is in the letter.",
    { kind: "decision", seenBy: ["p-esme", "p-amara"], reactions: { agree: ["p-esme", "p-amara"] } }),
  N("821", "ap-2990", "p-esme", "2026-08-27T16:15",
    "Three days left on it. She was straight with me that she has one other conversation running. Worth a call tomorrow rather than an email.",
    { seenBy: ["p-nadia"], reactions: { agree: ["p-nadia"], watching: ["p-amara"] } }),

  /* Dilnoza Rakhimova — unread, and there is a sponsorship question. */
  N("825", "ap-3103", "p-joel", "2026-08-27T08:20",
    "Flagging before anyone reads it: sponsorship needed and we have no certificate left on the digital establishment this year. Do not screen her in without @p-amara.",
    { seenBy: ["p-amara"], reactions: { watching: ["p-amara"] } }),
];

/* ── Job adverts ──────────────────────────────────────────────────────────
   One advert per requisition: the wording that produces the pipeline, and where
   it is posted. `views` is the only figure held here, because it comes from
   outside the product — the applications a channel produced are counted from the
   applications array by `source`, never stored twice. */
const AD = (requisitionId, headline, body, essentials, desirables, updatedISO, updatedById, postedTo) => ({
  requisitionId, headline, body, essentials, desirables,
  updatedAt: at(updatedISO), updatedById, postedTo,
});
const post = (channel, postedISO, views) => ({ channel, postedAt: d0(postedISO), views });

const ADVERTS = [
  AD("rq-1042", "Community staff nurse — north and west teams",
    "Three band 5 nurses to join the community teams covering the north and west of the county. You will hold your own caseload of around eighteen patients, work alongside a matron and a locality GP, and spend most of your week in people's homes rather than on a ward.\n\nWe are honest about the drive: the north patch reaches the Thornbury villages and a car is essential. In return the rota is nine to five with no nights, and every nurse gets a protected study afternoon a fortnight.",
    ["Current NMC registration", "Full UK driving licence and use of a car", "At least a year post-preceptorship"],
    ["Community or district nursing experience", "Independent prescriber", "Wound care or diabetes special interest"],
    "2026-07-02T11:20", "p-nadia",
    [post("careers_site", "2026-06-30", 1840), post("nhs_jobs", "2026-06-30", 2610), post("event", "2026-07-18", 96)]),
  AD("rq-1051", "Senior physiotherapist — MSK outpatients, Eastgate",
    "A band 7 physiotherapist for the musculoskeletal service at Eastgate Clinic. Mixed caseload, first-contact clinics two days a week, and supervision of two band 5s and a rotational band 6.\n\nThe service runs a Friday injection clinic and we would want you comfortable taking that on within six months, with the training paid for.",
    ["Current HCPC registration", "Postgraduate MSK experience", "Experience supervising junior staff"],
    ["First-contact practitioner qualification", "Injection therapy", "Research or audit experience"],
    "2026-07-27T09:05", "p-nadia",
    [post("careers_site", "2026-07-27", 640), post("nhs_jobs", "2026-07-27", 1180)]),
  AD("rq-1063", "Clinical systems engineer — shared care record",
    "Integration work on the shared care record that joins our hospital, the community teams and forty GP practices. HL7 and FHIR, a message queue that has to be right, and clinicians who will tell you plainly when it is not.\n\nOn-call is one week in six and paid as such. Hybrid: two days at Larkspur House, three wherever you work best.",
    ["Integration experience with HL7 v2 or FHIR", "Comfortable being the person who owns an incident", "Right to work in the UK without sponsorship"],
    ["NHS or healthcare experience", "Clinical safety (DCB0129) awareness", "Python or Java"],
    "2026-08-11T16:40", "p-joel",
    [post("careers_site", "2026-08-10", 410), post("nhs_jobs", "2026-08-10", 760), post("agency", "2026-08-14", 0)]),
  AD("rq-1067", "Ward clerk — Bramble ward, twelve months",
    "Twelve-month cover for a secondment on Bramble ward. You are the person who knows where everything is: notes, discharge letters, the consultant's clinic list and which porter to ring.\n\nEarly shifts, no weekends, and the ward team eats lunch together.",
    ["Administrative experience in a busy office", "Confident on the phone with anxious relatives"],
    ["NHS ward or clinic experience", "Familiar with electronic patient records"],
    "2026-08-17T10:15", "p-joel",
    [post("careers_site", "2026-08-17", 220), post("nhs_jobs", "2026-08-17", 340)]),
  AD("rq-1058", "Occupational therapist — community, band 6",
    "Community occupational therapy across the county, with a caseload weighted towards falls prevention and discharge to assess.\n\nThis advert is unpublished while the establishment review runs. It is kept here so the wording is not written twice when it comes back.",
    ["Current HCPC registration", "Community or intermediate care experience"],
    ["Falls prevention", "Moving and handling trainer"],
    "2026-07-13T14:00", "p-nadia", []),
  AD("rq-1039", "Night nurse practitioner — Riverside acute floor",
    "Nights on the acute floor, leading a team of four and working alongside the on-call registrar. Filled in August 2026.",
    ["Current NMC registration", "Advanced clinical practice qualification or in training", "Acute experience"],
    ["Non-medical prescribing", "Simulation teaching"],
    "2026-05-18T09:30", "p-nadia",
    [post("nhs_jobs", "2026-05-18", 1420), post("careers_site", "2026-05-18", 510)]),
];

/* ── Notifications ────────────────────────────────────────────────────────
   Each one names a record in the arrays above, routes to it, and carries who
   caused it. `readAt` is per notification rather than one "all read" flag, so
   the bell's count and the inbox cannot disagree — both count the same array. */
const NOTE_ROUTE = (id) => "/applications/" + id;
const NOTIFICATIONS = [
  { id: "n1", kind: "mention", actorId: "p-amara", at: at("2026-08-27T13:40"), readAt: null,
    title: "Amara Boateng replied to your note",
    body: "Two band 6 premiums on one requisition is the limit for this year — a third comes to me first.",
    route: NOTE_ROUTE("ap-3051") },
  { id: "n2", kind: "offer", actorId: null, at: at("2026-08-27T19:40"), readAt: null,
    title: "Damian Oyelaran accepted",
    body: "Ward clerk, £24,071, starting 28 September. Record the hire to close the seat.",
    route: NOTE_ROUTE("ap-2991") },
  { id: "n3", kind: "offer", actorId: null, at: at("2026-08-28T07:00"), readAt: null,
    title: "Freya Lindsay's offer runs out on Monday",
    body: "Sent 24 August, valid to 31 August. No answer yet, and Esme thinks a call beats an email.",
    route: NOTE_ROUTE("ap-2990") },
  { id: "n4", kind: "compliance", actorId: null, at: at("2026-08-28T06:05"), readAt: null,
    title: "Elias Nordahl's HCPC registration renews 14 September",
    body: "He is at checks with one reference outstanding. Nobody starts on a lapsed registration.",
    route: NOTE_ROUTE("ap-3020") },
  { id: "n5", kind: "panel", actorId: "p-esme", at: at("2026-08-28T06:05"), readAt: null,
    title: "A scorecard is outstanding for Jonah Okwu",
    body: "Panel was 27 August. Ruth has filed hers; the second card is what the decision waits on.",
    route: NOTE_ROUTE("ap-3052") },
  { id: "n6", kind: "application", actorId: null, at: at("2026-08-28T06:00"), readAt: null,
    title: "Three applications arrived overnight",
    body: "Aisha Farrow and Callum Beattie for the community nurse seats, Dilnoza Rakhimova for the systems engineer.",
    route: "/applications" },
  { id: "n7", kind: "interview", actorId: null, at: at("2026-08-28T08:00"), readAt: null,
    title: "Screening call with Grace Lindqvist at 11:30",
    body: "Telephone, 30 minutes, yours. She is returning to community nursing after two years abroad.",
    route: "/interviews" },
  { id: "n8", kind: "panel", actorId: "p-ruth", at: at("2026-08-26T17:52"), readAt: at("2026-08-27T08:10"),
    title: "Ruth Calloway scored Priya Raghunathan",
    body: "4.5 average, recommends yes. Both cards are now in, so checks can start.",
    route: NOTE_ROUTE("ap-3051") },
  { id: "n9", kind: "mention", actorId: "p-fern", at: at("2026-08-26T15:20"), readAt: at("2026-08-27T08:11"),
    title: "Fern Adeyemi asked you about a start date",
    body: "Can we tell Elias the week of the 12th October, assuming the reference lands Monday?",
    route: NOTE_ROUTE("ap-3020") },
  { id: "n10", kind: "compliance", actorId: null, at: at("2026-08-25T06:05"), readAt: at("2026-08-25T09:30"),
    title: "Nkechi Ibrahim's NMC registration renews 30 September",
    body: "She is at screening, so nothing is blocked yet. It will be if she reaches checks.",
    route: NOTE_ROUTE("ap-3090") },
];

Object.assign(window, {
  DAY_MS, d0, d1, at, RD_NOW, RD_ORG, STAFF, RD_SESSION,
  REQUISITIONS, APPLICATIONS, INTERVIEWS, NOTES, ADVERTS, NOTIFICATIONS,
  portraitFor, initialsOf,
});
