mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
A condition (illness episode) can carry where on the body it sits, in the person's own words and encrypted at rest, plus an optional side (LEFT, RIGHT, BOTH). Same enum, codec and reasoning as the procedure's site. - migration 0354: two nullable columns on illness_episodes, reusing the laterality type from 0349 - create and edit accept bodySite and laterality; on PATCH an absent key keeps the stored value, so an edit from the shipped iPhone app, which never sends either field, leaves them untouched - GET /api/body-sites: the sites the record holds across visits and conditions, folded after the decrypt and bounded per kind; with site (and laterality) the visits and conditions there, one hop to their documents, lab results and visits. Read in the profile section; conditions only under an illness grant with the module on, and links into ungranted sections come back as unlabelled placeholders - the link service reads document and visit links from the condition's end of the two existing tables - /checkups gains a Body sites tab; the visit and condition forms share one body-site field pair that suggests sites already used; a condition's page links its site into the view - the doctor report, the clinician page and the MCP doctor-visit summary name the site on conditions; the Coach illness block carries it sanitised, and MCP get_illness_recovery returns it fenced - backup and restore carry both fields in both file kinds; the site is registered as ciphertext and rotates with the key - i18n in all seven locales, OpenAPI regenerated Refs #1025
95 lines
4.3 KiB
TypeScript
95 lines
4.3 KiB
TypeScript
/**
|
|
* Query keys — visits and the address book behind them.
|
|
* Part of the centralized factory; aggregated in `./index.ts`.
|
|
*
|
|
* These exist before any client reads them, because the in-repo
|
|
* `healthlog/queryKey-factory` rule errors on a bare array: the first
|
|
* component that lists visits cannot compile without a slot here. Same key
|
|
* with a different `queryFn` shape silently poisons the cache, which is the
|
|
* whole reason the factory is the only legal source.
|
|
*
|
|
* There is deliberately no matching `invalidateUserVisits` in
|
|
* `src/lib/cache/invalidate.ts`. That module evicts SERVER-side cached
|
|
* payloads, and no cached payload reads a visit: the dashboard snapshot, the
|
|
* insights targets and the analytics buckets are all computed from
|
|
* measurements, medications and mood. Adding a sweep over caches nothing reads
|
|
* would be a placeholder that looks like coverage. It belongs in the release
|
|
* that puts visits into one of those payloads, alongside the reader that makes
|
|
* it necessary.
|
|
*/
|
|
export const encounterKeys = {
|
|
/**
|
|
* The root prefix every visit write invalidates through
|
|
* (`encounterDependentKeys` in `./index.ts`). TanStack's hierarchical
|
|
* prefix semantics mean evicting `["encounters"]` clears the windowed list
|
|
* and every open detail in one tick, so a visit edited in a sheet and the
|
|
* list behind it never disagree.
|
|
*/
|
|
encounters: () => ["encounters"] as const,
|
|
/**
|
|
* The windowed list. The window is part of the key: the page can ask for a
|
|
* year at a time, and two windows are two different answers rather than one
|
|
* answer that overwrites the other.
|
|
*/
|
|
encounterList: (
|
|
from: string | null,
|
|
to: string | null,
|
|
status?: string,
|
|
episodeId?: string,
|
|
) =>
|
|
[
|
|
"encounters",
|
|
"list",
|
|
from,
|
|
to,
|
|
status ?? null,
|
|
episodeId ?? null,
|
|
] as const,
|
|
encounter: (id: string) => ["encounters", "detail", id] as const,
|
|
/**
|
|
* The procedure history for one filter. Under the `["encounters"]` root, so
|
|
* every visit write (switching a kind, editing a site) evicts it with the
|
|
* list. The filter is part of the key because it is part of the question:
|
|
* the search runs server-side over the decrypted site.
|
|
*/
|
|
encounterProcedures: (q: string, laterality: string | null) =>
|
|
["encounters", "procedures", q, laterality] as const,
|
|
/**
|
|
* v1.39.2 — the body-site view and the body-site suggestions. Under the
|
|
* `["encounters"]` root, so a visit write evicts it with the list; a
|
|
* condition write reaches it through `bodySitesAll` in
|
|
* `illnessDependentKeys`, because conditions are the other half of it. The
|
|
* picked site and side are part of the key: the selection is resolved on the
|
|
* server after the decrypt. The suggestions read is the no-site slot.
|
|
*/
|
|
bodySitesAll: () => ["encounters", "body-sites"] as const,
|
|
bodySites: (site: string | null, laterality: string | null) =>
|
|
["encounters", "body-sites", site, laterality] as const,
|
|
/**
|
|
* The "which visit does this belong to" verdict for one anchor date. The
|
|
* anchor is part of the key because it IS the question: a document dated the
|
|
* 3rd and one dated the 20th get different answers, and one overwriting the
|
|
* other would pre-select the wrong visit on the second review step.
|
|
*/
|
|
encounterSuggestion: (anchor: string) =>
|
|
["encounters", "suggest", anchor] as const,
|
|
/**
|
|
* The address book. Under its own root rather than nested beneath
|
|
* `["encounters"]`: a practitioner outlives the visits that name it, and a
|
|
* visit write should not evict a list that did not change.
|
|
*/
|
|
practitioners: () => ["practitioners"] as const,
|
|
practitionerList: (q?: string) =>
|
|
["practitioners", "list", q ?? null] as const,
|
|
practitioner: (id: string) => ["practitioners", "detail", id] as const,
|
|
|
|
/* ── mutation keys ──────────────────────────────────────────────── */
|
|
|
|
encounterCreate: () => ["encounters", "create"] as const,
|
|
encounterUpdate: () => ["encounters", "update"] as const,
|
|
encounterDelete: () => ["encounters", "delete"] as const,
|
|
practitionerCreate: () => ["practitioners", "create"] as const,
|
|
practitionerUpdate: () => ["practitioners", "update"] as const,
|
|
practitionerDelete: () => ["practitioners", "delete"] as const,
|
|
};
|