/* Ward Desk — every number on every screen comes from here.
   The rule this file exists to keep: a figure, a section count and a nav badge
   are one function called three times. If a screen cannot name the array a
   number came from, the number is wrong.

   Nothing here formats and nothing here assigns a tone — ui-runtime.js does the
   first, enums.js the second. */

const staffById = (staff, id) => staff.find((s) => s.id === id) || null;
const staffName = (staff, id) => { const s = staffById(staff, id); return s ? s.name : "Not recorded"; };
const patientById = (patients, id) => patients.find((p) => p.id === id) || null;
const patientName = (patients, id) => { const p = patientById(patients, id); return p ? p.name : "Unknown patient"; };
const wardName = (value) => labelOf(WARDS, value).split(" — ")[0];
const initialsOf = (name) => (name || "").split(/\s+/).slice(0, 2).map((w) => w[0]).join("").toUpperCase();
const drugById = (formulary, id) => formulary.find((d) => d.id === id) || null;

/* Doctors are the medical family; employees are everybody with a badge. Two
   views of one array, never two arrays. */
const doctorsOf = (staff) => staff.filter((s) => s.family === "medical");
const employeesOf = (staff) => staff.slice();

/* ── Admissions ──────────────────────────────────────────────────────────── */
const openAdmissions = (admissions) => admissions.filter((a) => a.status === "awaiting_bed" || a.status === "on_ward" || a.status === "fit_for_discharge");
const bedsWaiting = (admissions) => admissions.filter((a) => a.status === "awaiting_bed");
const bedWaitsBreached = (admissions, now) => bedsWaiting(admissions).filter((a) => ADMISSION_FLAGS.bedWaitBreached(a, now));
const fitForDischarge = (admissions) => admissions.filter((a) => a.status === "fit_for_discharge");
const onWard = (admissions) => admissions.filter((a) => a.status === "on_ward");
const admissionOfPatient = (admissions, patientId) => admissions
  .filter((a) => a.patientId === patientId)
  .sort((a, b) => (b.admittedAt || b.requestedAt) - (a.admittedAt || a.requestedAt))[0] || null;

/* ── Interventions ───────────────────────────────────────────────────────── */
const interventionsToday = (interventions, now) => interventions
  .filter((i) => INTERVENTION_FLAGS.isToday(i, now) && i.status !== "cancelled")
  .sort((a, b) => a.scheduledAt - b.scheduledAt);
const consentMissing = (interventions) => interventions.filter((i) => INTERVENTION_FLAGS.consentMissing(i));
const unscheduledInterventions = (interventions) => interventions.filter((i) => INTERVENTION_FLAGS.unscheduled(i));
const interventionsForPatient = (interventions, patientId) => interventions
  .filter((i) => i.patientId === patientId)
  .sort((a, b) => (b.scheduledAt || b.requestedAt) - (a.scheduledAt || a.requestedAt));
const interventionsForSurgeon = (interventions, staffId) => interventions.filter((i) => i.surgeonId === staffId || i.anaesthetistId === staffId);

/* ── Medication ──────────────────────────────────────────────────────────── */
const ordersAwaitingPharmacy = (orders) => orders.filter((o) => MEDICATION_FLAGS.awaitingPharmacy(o));
const activeOrders = (orders) => orders.filter((o) => o.status === "active");
const dosesDue = (orders, now) => orders.filter((o) => MEDICATION_FLAGS.doseDue(o, now));
const dosesMissed = (orders, now) => orders.filter((o) => MEDICATION_FLAGS.doseMissed(o, now));
const ordersForPatient = (orders, patientId) => orders.filter((o) => o.patientId === patientId);
const ordersOnHold = (orders) => orders.filter((o) => o.status === "on_hold");

/* ── Formulary ───────────────────────────────────────────────────────────── */
const belowReorder = (formulary) => formulary.filter((d) => FORMULARY_FLAGS.belowReorder(d));
const outOfStock = (formulary) => formulary.filter((d) => d.stock === "out");
const ordersForDrug = (orders, drugId) => orders.filter((o) => o.drugId === drugId && (o.status === "active" || o.status === "awaiting_verification"));

/* ── The social layer ────────────────────────────────────────────────────── */
const commentsFor = (comments, type, id) => comments
  .filter((c) => c.record.type === type && c.record.id === id)
  .sort((a, b) => a.at - b.at);

/* A mention is owed until the person mentioned has written on that record since.
   The nav badge, the feed section and the "waiting on" line all read this one
   predicate. */
function mentionsOwed(comments, viewerId) {
  return comments.filter((c) => {
    if (c.mentions.indexOf(viewerId) < 0) return false;
    return !comments.some((later) => later.record.type === c.record.type && later.record.id === c.record.id && later.authorId === viewerId && later.at > c.at);
  });
}

const commentsByAuthor = (comments, authorId) => comments.filter((c) => c.authorId === authorId).sort((a, b) => b.at - a.at);
const feedItems = (activity) => activity.slice().sort((a, b) => b.at - a.at);

/* ── Import and export ───────────────────────────────────────────────────── */
const transfersNeedingReview = (transfers) => transfers.filter((t) => t.status === "needs_review");
const failedTransfers = (transfers) => transfers.filter((t) => t.status === "failed");
const imports = (transfers) => transfers.filter((t) => t.kind === "import");
const exports_ = (transfers) => transfers.filter((t) => t.kind === "export");
const rowsAccepted = (transfers) => imports(transfers).reduce((n, t) => n + t.accepted, 0);

/* ── Money ────────────────────────────────────────────────────────────────
   The product bills in sterling, so it spells money itself rather than reaching
   for the shared USD formatter in lib/ui-runtime.js. One helper, product-wide;
   amounts are pence everywhere. */
const poundsFormatter = new Intl.NumberFormat("en-GB", { style: "currency", currency: "GBP", maximumFractionDigits: 0 });
const poundsExact = new Intl.NumberFormat("en-GB", { style: "currency", currency: "GBP", minimumFractionDigits: 2 });
const formatPounds = (minor) => poundsFormatter.format((minor || 0) / 100);
const formatPoundsExact = (minor) => poundsExact.format((minor || 0) / 100);

/* ── Invoices: nothing is stored that can be summed ──────────────────────── */
const lineTotal = (line) => line.qty * line.unitMinor;
const invoiceTotal = (invoice) => invoice.lines.reduce((n, line) => n + lineTotal(line), 0);
const paymentsFor = (payments, invoiceId) => payments.filter((p) => p.invoiceId === invoiceId).sort((a, b) => b.receivedAt - a.receivedAt);
const invoicePaid = (payments, invoiceId) => paymentsFor(payments, invoiceId).filter((p) => p.status === "cleared").reduce((n, p) => n + p.amountMinor, 0);
const invoiceBalance = (invoice, payments) => Math.max(0, invoiceTotal(invoice) - invoicePaid(payments, invoice.id));
const invoicesOverdue = (invoices, payments, now) => invoices.filter((i) => INVOICE_FLAGS.overdue(i, invoiceBalance(i, payments), now));
const invoicesBillable = (invoices) => invoices.filter((i) => INVOICE_FLAGS.billable(i));
const invoicesUnbilled = (invoices) => invoices.filter((i) => i.status === "draft" || i.status === "awaiting_coding");
const invoicesForPatient = (invoices, patientId) => invoices.filter((i) => i.patientId === patientId);
const invoicesDisputed = (invoices) => invoices.filter((i) => i.status === "disputed");
const billedTotal = (invoices) => invoicesBillable(invoices).reduce((n, i) => n + invoiceTotal(i), 0);
const collectedTotal = (invoices, payments) => invoicesBillable(invoices).reduce((n, i) => n + invoicePaid(payments, i.id), 0);
const outstandingTotal = (invoices, payments) => invoicesBillable(invoices).reduce((n, i) => n + invoiceBalance(i, payments), 0);

/* Aged debt: every issued invoice with a balance, in the bucket its due date
   puts it in. The buckets partition one set — an invoice appears once. */
function agedDebt(invoices, payments, now) {
  const buckets = [
    { id: "current", label: "Not yet due", rows: [] },
    { id: "d30", label: "1–30 days over", rows: [] },
    { id: "d60", label: "31–60 days over", rows: [] },
    { id: "d60plus", label: "Over 60 days", rows: [] },
  ];
  invoicesBillable(invoices).forEach((invoice) => {
    const balance = invoiceBalance(invoice, payments);
    if (balance <= 0) return;
    const over = invoice.dueAt ? Math.round((now - invoice.dueAt) / DAY_MS) : 0;
    const bucket = over <= 0 ? buckets[0] : over <= 30 ? buckets[1] : over <= 60 ? buckets[2] : buckets[3];
    bucket.rows.push({ invoice: invoice, balance: balance, over: over });
  });
  return buckets.map((b) => Object.assign({}, b, { total: b.rows.reduce((n, r) => n + r.balance, 0) }));
}

/* Revenue by payer: billed, collected and outstanding over the same set. */
function revenueByPayer(invoices, payments) {
  return PAYERS.map((payer) => {
    const rows = invoicesBillable(invoices).filter((i) => i.payer === payer.value);
    const billed = rows.reduce((n, i) => n + invoiceTotal(i), 0);
    const collected = rows.reduce((n, i) => n + invoicePaid(payments, i.id), 0);
    return { payer: payer.value, label: payer.label, invoices: rows.length, billed: billed, collected: collected, outstanding: billed - collected, share: 0, rows: rows };
  }).map((row, _, all) => {
    const total = all.reduce((n, r) => n + r.billed, 0);
    return Object.assign({}, row, { share: total ? row.billed / total : 0 });
  });
}

/* What the hospital billed for, by the kind of line it billed. */
function revenueByKind(invoices) {
  return BILL_LINE_KINDS.map((kind) => {
    const lines = invoicesBillable(invoices).reduce((list, i) => list.concat(i.lines.filter((l) => l.kind === kind.value)), []);
    return { kind: kind.value, label: kind.label, lines: lines.length, total: lines.reduce((n, l) => n + lineTotal(l), 0) };
  }).filter((row) => row.lines > 0);
}

/* ── Contracts and tariffs ────────────────────────────────────────────────
   A line's contracted price is looked up by the payer and the line's own label;
   a line with no contract row is off contract, and so is one whose unit price
   differs from the row. Both are findings, not silent differences. */
const contractOf = (contracts, payer) => contracts.find((c) => c.payer === payer) || null;
const contractDueDays = (contracts, payer) => { const c = contractOf(contracts, payer); return c ? c.dueDays : 30; };
const tariffItemFor = (items, label) => items.find((i) => i.label === label) || null;
function contractedPrice(data, payer, label) {
  const item = tariffItemFor(data.tariffItems, label);
  if (!item) return null;
  const row = data.contractPrices.find((p) => p.payer === payer && p.itemId === item.id);
  return row ? row.minor : null;
}
/* One predicate, read by the finding list, the invoice line chip and the badge. */
function lineOffContract(data, invoice, line) {
  const price = contractedPrice(data, invoice.payer, line.label);
  if (price === null) return { kind: "no_price", price: null, difference: 0 };
  if (price !== line.unitMinor) return { kind: "different", price: price, difference: line.unitMinor - price };
  return null;
}
function offContractLines(data) {
  const out = [];
  data.invoices.forEach((invoice) => invoice.lines.forEach((line) => {
    const finding = lineOffContract(data, invoice, line);
    if (finding) out.push(Object.assign({ invoice: invoice, line: line }, finding));
  }));
  return out;
}
const contractPricesFor = (data, payer) => data.contractPrices.filter((p) => p.payer === payer);
const contractsExpiring = (contracts, now) => contracts.filter((c) => CONTRACT_FLAGS.expiringSoon(c, now));

/* ── Credit control ───────────────────────────────────────────────────────
   The worklist is every issued invoice with a balance that is NOT disputed — a
   query is answered before a payer is chased. Each row carries its last contact
   and the step the ladder says comes next. */
const chasesFor = (chases, invoiceId) => chases.filter((c) => c.invoiceId === invoiceId).sort((a, b) => b.at - a.at);
function creditWorklist(data, now) {
  return data.invoices
    .filter((i) => (i.status === "issued" || i.status === "part_paid") && invoiceBalance(i, data.payments) > 0)
    .map((invoice) => {
      const rows = chasesFor(data.chases, invoice.id);
      const last = rows[0] || null;
      const balance = invoiceBalance(invoice, data.payments);
      const failed = data.payments.filter((p) => p.invoiceId === invoice.id && p.status === "failed");
      const promisedRows = data.payments.filter((p) => p.invoiceId === invoice.id && p.status === "pending");
      return {
        invoice: invoice, balance: balance, chases: rows, last: last, lastStep: last ? last.step : null,
        next: nextChaseStep(last ? last.step : null),
        overdue: INVOICE_FLAGS.overdue(invoice, balance, now),
        failed: failed, promised: promisedRows.reduce((n, p) => n + p.amountMinor, 0),
      };
    })
    .sort((a, b) => (a.invoice.dueAt || 0) - (b.invoice.dueAt || 0));
}

/* ── Cost and margin per episode ──────────────────────────────────────────
   A line is costed from the record it names: bed days at the ward's day cost, a
   procedure at its planned theatre minutes, a drug course from the order's own
   frequency against the formulary unit cost. A line naming no record carries no
   cost, and the screen says how much billing that is. */
const DOSES_PER_DAY = { "Once daily": 1, "Twice daily": 2, "Three times daily": 3, "Four times daily": 4 };
function doseCount(order, now) {
  const perDay = DOSES_PER_DAY[order.frequency] || 1;
  const until = order.endsAt || now;
  const days = Math.max(1, Math.round((until - order.startedAt) / DAY_MS));
  return days * perDay;
}
function lineCost(data, line, now) {
  if (!line.ref) return null;
  if (line.kind === "bed" && line.ref.type === "admission") {
    const admission = data.admissions.find((a) => a.id === line.ref.id);
    return admission ? line.qty * (COST_RATES.bedDayByWard[admission.ward] || 0) : null;
  }
  if (line.kind === "procedure" && line.ref.type === "intervention") {
    const intervention = data.interventions.find((i) => i.id === line.ref.id);
    return intervention ? intervention.plannedMin * COST_RATES.theatreMinute : null;
  }
  if (line.kind === "medication" && line.ref.type === "medication") {
    const order = data.orders.find((o) => o.id === line.ref.id);
    const drug = order ? drugById(data.formulary, order.drugId) : null;
    return order && drug ? doseCount(order, now) * (drug.unitCostMinor || 0) : null;
  }
  return null;
}
function episodeCost(data, invoice, now) {
  let cost = 0, uncosted = 0;
  invoice.lines.forEach((line) => {
    const c = lineCost(data, line, now);
    if (c === null) uncosted += lineTotal(line); else cost += c;
  });
  return { cost: cost, uncosted: uncosted };
}
function marginRows(data, now) {
  return invoicesBillable(data.invoices).map((invoice) => {
    const billed = invoiceTotal(invoice);
    const costed = episodeCost(data, invoice, now);
    const margin = billed - costed.cost;
    return { invoice: invoice, billed: billed, cost: costed.cost, uncosted: costed.uncosted, margin: margin, rate: billed ? margin / billed : 0 };
  }).sort((a, b) => a.rate - b.rate);
}
function costByKind(data, now) {
  return BILL_LINE_KINDS.map((kind) => {
    let cost = 0, billed = 0, lines = 0;
    invoicesBillable(data.invoices).forEach((invoice) => invoice.lines.filter((l) => l.kind === kind.value).forEach((line) => {
      const c = lineCost(data, line, now);
      lines += 1; billed += lineTotal(line); if (c !== null) cost += c;
    }));
    return { kind: kind.value, label: kind.label, lines: lines, billed: billed, cost: cost, margin: billed - cost };
  }).filter((row) => row.lines > 0);
}

/* ── Wards: capacity, occupancy and who is on ─────────────────────────────── */
const wardBedsOf = (beds, ward) => beds.find((b) => b.ward === ward) || { ward: ward, beds: 0, establishment: { day: 0, late: 0, night: 0 } };

function wardRows(admissions, beds) {
  return WARDS.map((ward) => {
    const capacity = wardBedsOf(beds, ward.value);
    const rows = admissions.filter((a) => a.ward === ward.value);
    const occupied = rows.filter((a) => a.status === "on_ward" || a.status === "fit_for_discharge");
    const held = rows.filter((a) => a.status === "fit_for_discharge");
    const waiting = rows.filter((a) => a.status === "awaiting_bed");
    return {
      ward: ward.value, label: wardName(ward.value), specialty: labelOf(WARDS, ward.value).split(" — ")[1],
      beds: capacity.beds, establishment: capacity.establishment,
      occupied: occupied.length, held: held.length, waiting: waiting.length,
      free: Math.max(0, capacity.beds - occupied.length),
      occupancy: capacity.beds ? occupied.length / capacity.beds : 0,
      rows: occupied.concat(waiting),
    };
  });
}

/* ── Rota ─────────────────────────────────────────────────────────────────── */
const rotaFor = (rota, day, ward, shift) => rota.filter((r) => r.day === day && r.ward === ward && r.shift === shift);
function understaffedShifts(rota, beds, days) {
  const out = [];
  (days || [0, 1, 2]).forEach((day) => {
    beds.forEach((capacity) => {
      SHIFT_TYPES.forEach((shift) => {
        const rostered = rotaFor(rota, day, capacity.ward, shift.value);
        if (!rostered.length && !rota.some((r) => r.ward === capacity.ward)) return;
        const establishment = capacity.establishment[shift.value];
        if (SHIFT_FLAGS.understaffed(rostered, establishment)) out.push({ day: day, ward: capacity.ward, shift: shift.value, rostered: rostered, establishment: establishment, shortfall: SHIFT_FLAGS.shortfall(rostered, establishment) });
      });
    });
  });
  return out;
}
const rotaWards = (rota) => WARDS.filter((w) => rota.some((r) => r.ward === w.value));

/* ── The drug round: active orders grouped by where their clock has got to ── */
function roundGroups(orders, now) {
  const active = activeOrders(orders).filter((o) => o.nextDoseAt);
  return [
    { id: "overdue", label: "Overdue", note: "Due more than an hour ago and not recorded as given.", rows: active.filter((o) => MEDICATION_FLAGS.doseMissed(o, now)) },
    { id: "now", label: "Due within the hour", note: "The round you are on.", rows: active.filter((o) => MEDICATION_FLAGS.doseDue(o, now) && !MEDICATION_FLAGS.doseMissed(o, now)) },
    { id: "later", label: "Later today", note: "Due after this hour and before midnight.", rows: active.filter((o) => o.nextDoseAt > now + HOUR_MS && new Date(o.nextDoseAt).toDateString() === new Date(now).toDateString()) },
  ].map((group) => Object.assign({}, group, { rows: group.rows.slice().sort((a, b) => a.nextDoseAt - b.nextDoseAt) }));
}
const dosesGivenSince = (orders, now, hours) => orders.filter((o) => o.lastGivenAt && now - o.lastGivenAt <= hours * HOUR_MS);

/* ── Report figures: each one names the array it divides ──────────────────── */
function reportFigures(data, now) {
  const open = openAdmissions(data.admissions);
  const beds = data.wardBeds.reduce((n, b) => n + b.beds, 0);
  const occupied = onWard(data.admissions).concat(fitForDischarge(data.admissions));
  const todaysTheatre = interventionsToday(data.interventions, now);
  const bookedMinutes = todaysTheatre.reduce((n, i) => n + i.plannedMin, 0);
  const scheduled = data.interventions.filter((i) => i.status === "scheduled");
  const withConsent = scheduled.filter((i) => !!i.consentAt);
  const active = activeOrders(data.orders);
  const missed = dosesMissed(data.orders, now);
  const stays = open.filter((a) => a.admittedAt).map((a) => ADMISSION_FLAGS.lengthOfStayDays(a, now));
  return {
    occupancy: { value: beds ? occupied.length / beds : 0, of: beds, count: occupied.length },
    theatre: { value: bookedMinutes / THEATRE_MINUTES_PER_DAY, minutes: bookedMinutes, cases: todaysTheatre.length },
    consent: { value: scheduled.length ? withConsent.length / scheduled.length : 1, of: scheduled.length, count: withConsent.length },
    doses: { value: active.length ? (active.length - missed.length) / active.length : 1, of: active.length, count: active.length - missed.length },
    stay: { mean: stays.length ? stays.reduce((n, d) => n + d, 0) / stays.length : 0, longest: stays.length ? Math.max.apply(null, stays) : 0, of: stays.length },
    breaches: { count: bedWaitsBreached(data.admissions, now).length, of: bedsWaiting(data.admissions).length },
  };
}

/* ── What a note is attached to. One place, so a title never drifts. ─────── */
function recordTitle(data, type, id) {
  if (type === "patient") return patientName(data.patients, id);
  if (type === "admission") {
    const admission = data.admissions.find((a) => a.id === id);
    return admission ? patientName(data.patients, admission.patientId) + " · " + wardName(admission.ward) : "Admission";
  }
  if (type === "intervention") {
    const intervention = data.interventions.find((i) => i.id === id);
    return intervention ? intervention.name + " · " + patientName(data.patients, intervention.patientId) : "Intervention";
  }
  if (type === "medication") {
    const order = data.orders.find((o) => o.id === id);
    return order ? order.drug + " " + order.dose + " · " + patientName(data.patients, order.patientId) : "Medication order";
  }
  if (type === "formulary") {
    const drug = data.formulary.find((d) => d.id === id);
    return drug ? drug.name + " " + drug.form : "Formulary line";
  }
  if (type === "invoice") {
    const invoice = data.invoices.find((i) => i.id === id);
    return invoice ? invoice.reference + " · " + patientName(data.patients, invoice.patientId) : "Invoice";
  }
  if (type === "transfer") {
    const job = data.transfers.find((t) => t.id === id);
    return job ? job.label + " · " + labelOf(TRANSFER_KINDS, job.kind) : "Transfer";
  }
  return id;
}

/* Where a note lives, so the feed and the inbox can open it. */
function recordRoute(data, record) {
  if (record.type === "patient") return "/patients/" + record.id;
  if (record.type === "admission") {
    const admission = data.admissions.find((a) => a.id === record.id);
    return admission ? "/patients/" + admission.patientId : "/admissions";
  }
  if (record.type === "intervention") return "/interventions/" + record.id;
  if (record.type === "medication") return "/medication";
  if (record.type === "formulary") return "/formulary";
  if (record.type === "invoice") return "/invoices/" + record.id;
  if (record.type === "transfer") return "/data";
  return "/";
}

/* ── Nav badges: one predicate over one array, recoverable by the reader ─── */
const HD_NAV_BADGES = {
  bedsWaiting: { label: "waiting for a bed", count: (data) => bedsWaiting(data.admissions).length },
  dosesDueOrMissed: { label: "doses due or overdue", count: (data) => dosesDue(data.orders, HD_NOW).length + dosesMissed(data.orders, HD_NOW).length },
  shiftsUnderstaffed: { label: "shifts under establishment", count: (data) => understaffedShifts(data.rota, data.wardBeds).length },
  consentMissing: { label: "without consent recorded", count: (data) => consentMissing(data.interventions).length },
  ordersAwaitingPharmacy: { label: "awaiting a pharmacist", count: (data) => ordersAwaitingPharmacy(data.orders).length },
  mentionsOwed: { label: "mentions you have not answered", count: (data, viewerId) => mentionsOwed(data.comments, viewerId).length },
  transfersNeedingReview: { label: "imports with rows to review", count: (data) => transfersNeedingReview(data.transfers).length },
  invoicesOverdue: { label: "invoices past their due date", count: (data) => invoicesOverdue(data.invoices, data.payments, HD_NOW).length },
  linesOffContract: { label: "invoice lines billed off contract", count: (data) => offContractLines(data).length },
  thinMargins: { label: "episodes under a 15% margin", count: (data) => marginRows(data, HD_NOW).filter((r) => r.rate < 0.15).length },
  invoicesToCode: { label: "invoices waiting on a coder", count: (data) => data.invoices.filter((i) => i.status === "awaiting_coding").length },
};

/* Whether a category's rail icon shows a dot: a badge that would sum different
   kinds of record degrades to a dot rather than counting them together (§6.3). */
function categoryHasSignal(category, data, viewerId) {
  return category.items.some((route) => {
    const spec = route.badge ? HD_NAV_BADGES[route.badge] : null;
    return spec ? spec.count(data, viewerId) > 0 : false;
  });
}

Object.assign(window, {
  staffById, staffName, patientById, patientName, wardName, initialsOf, drugById, doctorsOf, employeesOf,
  openAdmissions, bedsWaiting, bedWaitsBreached, fitForDischarge, onWard, admissionOfPatient,
  interventionsToday, consentMissing, unscheduledInterventions, interventionsForPatient, interventionsForSurgeon,
  ordersAwaitingPharmacy, activeOrders, dosesDue, dosesMissed, ordersForPatient, ordersOnHold,
  belowReorder, outOfStock, ordersForDrug,
  commentsFor, mentionsOwed, commentsByAuthor, feedItems,
  transfersNeedingReview, failedTransfers, imports, exports_, rowsAccepted,
  recordTitle, recordRoute, HD_NAV_BADGES, categoryHasSignal,
  wardBedsOf, wardRows, rotaFor, understaffedShifts, rotaWards, roundGroups, dosesGivenSince, reportFigures,
  formatPounds, formatPoundsExact, lineTotal, invoiceTotal, paymentsFor, invoicePaid, invoiceBalance, invoicesOverdue,
  invoicesBillable, invoicesUnbilled, invoicesForPatient, invoicesDisputed, billedTotal, collectedTotal, outstandingTotal,
  agedDebt, revenueByPayer, revenueByKind,
  contractOf, contractDueDays, tariffItemFor, contractedPrice, lineOffContract, offContractLines, contractPricesFor, contractsExpiring,
  chasesFor, creditWorklist, doseCount, lineCost, episodeCost, marginRows, costByKind,
});
