marmar-context / web / context / src / api / adapt-checkins.ts
adapt-checkins.ts
Raw
// Check-ins from the backend's JSON (wire.ts) to Context's view types (types.ts).
import { toViewDay, viewHours } from "./adapt";
import type * as T from "./types";
import type * as W from "./wire";

const NO_LIMITS: T.CheckInLimits = { usedToday: 0, usedThisWeek: 0, maxPerDay: 1, maxPerWeek: 3, nextPossibleAt: null };

/** YCI-5. A clinic-added patient has no app, so no patient zone or hours: the clinic's zone stands in. */
export function windowFromWire(w: W.PlanTemplate["window"]): T.WindowRules {
  return {
    patientTimeZone: w.patientTimeZone ?? w.clinicTimeZone,
    clinicTimeZone: w.clinicTimeZone,
    clinicHours: viewHours(w.clinicHours),
    alertWatchHours: viewHours(w.alertWatchHours),
    dayStart: w.dayStart ?? "08:00",
    dayEnd: w.dayEnd ?? "20:00",
    // Quiet hours the patient turned off read as an empty span.
    quietHours: w.quietHours ?? { start: "00:00", end: "00:00" },
    blockedTimes: (w.blockedTimes ?? []).map((b) => ({ day: b.day === null ? null : toViewDay(b.day), start: b.start, end: b.end, label: b.label ?? "" })),
    defaultExpiryHours: w.defaultExpiryHours,
    maxExpiryHours: w.maxExpiryHours,
    known: w.patientTimeZone !== null,
  };
}

export const planFromWire = (w: W.PlanTemplate): T.CheckInPlanTemplate => ({
  ...w,
  feelingFollowUps: w.feelingFollowUps ?? [],
  window: windowFromWire(w.window),
  limits: w.limits ?? NO_LIMITS,
  maxQuestions: w.maxQuestions ?? 10,
});

export const noteFromWire = (n: W.CheckInNote): T.CheckInNote => ({
  ...n,
  validation: n.validation ?? "matched",
  endedEarly: n.endedEarly ?? false,
});

const TIERS = new Set<string>(["get_help_now", "today", "this_week", "good_to_know"]);

/** The snapshot, with the linked findings' tiers and the alerts already sent. */
export function checkInFromWire(w: W.CheckIn): T.CheckIn {
  const { script: _script, alerts, ...rest } = w;
  return {
    ...rest,
    plan: { ...w.plan, linkedTiers: (w.plan.linkedTiers ?? []).filter((tier): tier is T.Tier => TIERS.has(tier)) },
    notes: w.notes.map(noteFromWire),
    alerts: (alerts ?? []).filter((a) => a.message).map((a) => ({ id: a.id, at: a.at, kind: a.kind ?? "alert", message: a.message! })),
  };
}

export function eventFromWire(e: W.CheckInEvent): T.CheckInEvent {
  if (e.type === "note" || e.type === "note_updated") return { ...e, note: noteFromWire(e.note) };
  return e;
}

export const updatesFromWire = (w: W.CheckInUpdates): T.CheckInUpdates => ({ events: w.events.map(eventFromWire), lastEventId: w.lastEventId });