export type FdiId = string; export type Dentition = 'permanent' | 'primary' | 'mixed'; export type Numbering = 'fdi' | 'universal' | 'palmer'; export type ToothMode = 'interactive' | 'display'; export type Surface = 'mesial' | 'distal' | 'occlusal' | 'buccal' | 'lingual'; /** * Built-in findings. `destroyed` is the platform's *radice persistente* * (retained root); the id is frozen for backward compatibility but the label * and docs read "Retained root" so the two systems describe the same thing. * `xray` is the chartable radiograph SYMBOL — distinct from the `radiographs` * image archive a tooth can also carry. */ export type ToothCondition = 'caries' | 'filled' | 'crowned' | 'temporaryCrown' | 'bridge' | 'missing' | 'implant' | 'implantExtraction' | 'stub' | 'destroyed' | 'rootCanal' | 'pin' | 'rootAmputation' | 'anchorage' | 'hook' | 'attachment' | 'mucogingivalSurgery' | 'periodontalSurgery' | 'curettage' | 'medication' | 'xray'; /** * Open condition id: a built-in, or an id from a consumer's condition registry. * The `(string & {})` keeps editor autocomplete on the built-ins while still * accepting a practice-defined finding. */ export type ConditionId = ToothCondition | (string & {}); /** Which anatomical layer a symbol marks — mirrors the platform's `part`. */ export type SymbolPart = 'element' | 'surface' | 'root'; /** Planned vs already executed. Matches the Care Plan's blue/green convention. */ export type SymbolStatus = 'planned' | 'executed'; /** * A tooth's roots, named anatomically; `'single'` covers every one-rooted * tooth. For flank-mounted markers (anchorage, hook) `mesial` / `distal` select * which SIDE of the tooth the marker sits on rather than a root proper. */ export type Root = 'mesial' | 'distal' | 'palatal' | 'buccal' | 'single'; /** * One charted finding. Unlike the legacy `conditions` + `surfaces` pair — where * a single flat surface list is shared by every condition on the tooth — a * symbol scopes its own surfaces and roots, so two fillings on one tooth (one * mesial, one distal) are finally representable. */ export interface ToothSymbol { /** * Provenance — the platform's carePlanEntryId / findingEntryId. Lets a * consumer route a click on the symbol back to the record that produced it. */ id?: string; condition: ConditionId; /** Defaults to the condition's natural part (see `conditionPart`). */ part?: SymbolPart; surfaces?: Surface[]; roots?: Root[]; /** * Where a flank-mounted marker sits — the gingival surgeries ship * `left` / `right` / `total` / `repeat` renders rather than per-root ones. * Omitted, they fall back to `repeat`. */ region?: SymbolRegion; status?: SymbolStatus; /** Per-practice colour override (a CSS colour or `var(--token)`). */ color?: string; note?: string; } export interface ToothState { conditions: ToothCondition[]; surfaces: Surface[]; /** * Per-symbol findings. When present these WIN over `conditions`/`surfaces`; * when absent `symbolsForTooth()` derives the equivalent list from them, so * the renderer only ever walks one shape. */ symbols?: ToothSymbol[]; notes?: string; } export type ToothChart = Record; /** Radiograph (x-ray) type. `'periapical'` is the intraoral *endorale*. */ export type RadiographKind = 'periapical' | 'bitewing' | 'panoramic' | 'other'; /** * A single radiograph attached to a tooth. Image urls are opaque, * consumer-hosted strings, interpolated into an SVG `` / `` * (which cannot execute scripts) — never bundled and never runtime-validated. * Real radiographs are PHI: stories and demos must use synthetic placeholders. */ export interface Radiograph { /** Stable id — keys the archive list and the imperative open call. */ id: string; /** Full-resolution image url. */ url: string; /** Optional smaller preview url (falls back to `url`). */ thumbnailUrl?: string; /** ISO acquisition date (`YYYY-MM-DD`). */ date?: string; /** Free-text clinical note. */ note?: string; /** Radiograph type. Default `'periapical'` (an *endorale*). */ kind?: RadiographKind; /** * Accessible description for the FULL-SIZE image, consumed by a downstream * viewer (e.g. the Dental Imaging Hub archive ``). The chart's slot * render is decorative (`aria-hidden` — the tooth's accessible name carries * the radiograph count), so `alt` is intentionally not read by the chart. */ alt?: string; } export type Anatomy = 'incisor' | 'canine' | 'premolar' | 'molar'; export type Arch = 'upper' | 'lower'; export type Side = 'right' | 'left'; export type Quadrant = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; export interface ToothMeta { fdi: FdiId; /** '1'–'32' for permanent, 'A'–'T' for primary. */ universal: string; /** Simplified Palmer quadrant-and-position string, e.g. 'UR3', 'LL6'. */ palmer: string; quadrant: Quadrant; /** 1–8 (permanent) or 1–5 (primary), counted from the midline outward. */ positionInQuadrant: number; anatomy: Anatomy; arch: Arch; side: Side; dentition: 'permanent' | 'primary'; } /** * 32 permanent teeth in visual layout order: * upper row: 18..11 (patient upper-right, outermost → midline), then 21..28 (upper-left, midline → outermost) * lower row: 48..41 (patient lower-right, outermost → midline), then 31..38 (lower-left, midline → outermost) */ export declare const PERMANENT_TEETH: FdiId[]; /** * 20 primary teeth in visual layout order: * upper row: 55..51 (upper-right outermost → midline), then 61..65 * lower row: 85..81, then 71..75 */ export declare const PRIMARY_TEETH: FdiId[]; export declare const FDI_TO_META: Record; export declare const FDI_TO_UNIVERSAL: Record; export declare const FDI_TO_PALMER: Record; /** * FDI used to resolve a tooth's RENDER. There are no separate deciduous * renders — primary teeth reuse the permanent image of the same position * (quadrant 5→1, 6→2, 7→3, 8→4; position digit unchanged), scaled down by * the renderer. Permanent teeth resolve to themselves. */ export declare function assetFdi(meta: ToothMeta): FdiId; /** Render scale applied to a tooth image (primary teeth are smaller). */ export declare const PRIMARY_TOOTH_SCALE = 0.72; /** Positional file stem used by the per-root `rootTreatment` renders. */ export type RootFileStem = 'full' | 'left' | 'right' | 'center'; /** * The roots a tooth's artwork distinguishes: upper molars are three-rooted * (mesial, distal, palatal), lower molars two-rooted, everything else renders * as one root. Derived from `assetFdi` rather than `meta.anatomy` because a * primary molar reuses PREMOLAR art (54 draws 14) and so has one root's worth * of renders. The upper first premolars 14/24 are treated as single-rooted for * the same reason — clinically they are often bifurcated, but no second render * ships, and a wrong answer here resolves to a file that does not exist. */ export declare function rootsForTooth(meta: ToothMeta): Root[]; /** * Which SCREEN side a tooth's mesial or distal root faces. The chart is FIXED * to clinical orientation — the patient's right sits on the viewer's left in * BOTH arches — so the midline falls on the viewer's RIGHT for the patient's * right quadrants (1 & 4) and on the viewer's LEFT for the patient's left * quadrants (2 & 3). Mesial means "toward the midline", so it flips with it; * distal is always its opposite. This is the same flip `zoneSurface` applies * to the plan view's left/right zones. Primary quadrants normalise (5-8 → 1-4). */ export declare function rootScreenSide(meta: ToothMeta, root: 'mesial' | 'distal'): 'left' | 'right'; /** * Map an anatomical root to the `rootTreatment` file stem. `center` is the * palatal root and ships for upper molars only; a root the tooth does not * distinguish falls back to the whole-tooth render rather than a dead URL. */ export declare function rootFileStem(meta: ToothMeta, root: Root): RootFileStem; /** CSS variable name (without the leading `var(...)`) that drives a condition's colour. */ export declare const CONDITION_TOKENS: Record; /** * Convenience: the `fill="var(--destructive)"` string ready to paste onto * an SVG attribute. DERIVED from `CONDITION_TOKENS` so the two can never drift. * Used for the legend swatch + the surface-condition ring; whole-tooth * conditions render a distinct symbol image (the glyph is the * colour-independent channel). */ export declare const CONDITION_COLORS: Record; /** * Every built-in condition id, in declaration order. Derived from * `CONDITION_TOKENS`, which the compiler forces to be exhaustive — so a new * condition can never silently fall out of a hand-maintained list. */ export declare const TOOTH_CONDITIONS: ToothCondition[]; /** True when an id names a built-in condition rather than a registry entry. */ export declare function isBuiltInCondition(id: ConditionId): id is ToothCondition; /** The layer each built-in finding marks, used when a symbol omits `part`. */ export declare const CONDITION_PART: Record; /** * Built-ins the chart draws ITSELF rather than from artwork — `missing` is an * SVG cross, `caries` / `filled` fall back to the surface ring in the side * view. They must never take the registry's glyph badge. */ export declare const INTRINSIC_CONDITIONS: ToothCondition[]; export declare const STATUS_TOKENS: Record; /** The `var(...)` form of `STATUS_TOKENS`, ready for an SVG attribute. */ export declare const STATUS_COLORS: Record; /** The second, non-colour channel: a planned symbol renders lighter. */ export declare const STATUS_OPACITY: Record; /** i18n key for a status label (`ui.toothScheme.status.*`). */ export declare function statusLabelKey(status: SymbolStatus): string; /** * Screen-relative marker slot. `repeat` is what the BARE gingival-surgery type * resolves to — it is the default marker, not a "repeat treatment" variant. */ export type SymbolRegion = 'left' | 'right' | 'total' | 'repeat'; /** Corner glyph stem in `side/other/` — curettage, medication, x-ray. */ export type SymbolLetter = 'c' | 'm' | 'x'; /** * How a symbol's file resolves inside `{folder}/{view}/`. A discriminated union * rather than a bare enum because two shapes carry their own data: `per-fdi-root` * has to know whether a multi-rooted tooth also ships a whole-tooth render, and * `letter` pins the single glyph its condition owns. */ export type SideSymbolSpec = /** `{fdi}.webp` — one render per tooth. */ { folder: string; resolve: 'per-fdi'; } /** `general.webp` — one render per arch. */ | { folder: string; resolve: 'general'; } /** `{fdi}/{full|left|right|center}.webp` — per-FDI subdirectory. */ | { folder: string; resolve: 'rct'; } /** * `{fdi}[-{mesial|distal|palatal}].webp`. `wholeTooth` distinguishes two * folders that look symmetric and are not: `pin` ships a bare file for every * FDI, `rootAmputation` only for single-rooted teeth. */ | { folder: string; resolve: 'per-fdi-root'; wholeTooth: 'always' | 'single-rooted-only'; } /** `{fdi}-{mesial|distal}.webp` — always suffixed, no bare file. */ | { folder: string; resolve: 'per-fdi-side'; } /** `{fdi}_{left|right}.webp` — the only folder using an UNDERSCORE. */ | { folder: string; resolve: 'per-fdi-lr'; } /** `{left|right|total|repeat}.webp` — no FDI in the name. */ | { folder: string; resolve: 'region'; } /** `{left|right}.webp` — no FDI, and only the two sides ship. */ | { folder: string; resolve: 'screen-side'; } /** `{c|m|x}.webp`. */ | { folder: string; resolve: 'letter'; letter: SymbolLetter; }; /** * Which instance of a symbol to resolve. The two fields are independent * discriminators read by different `resolve` strategies (roots by the per-root * shapes, regions by the screen-side ones) — passed in, never module state. */ export interface SymbolTarget { /** Root, or tooth side for a flank-mounted marker. */ root?: Root; /** Explicit screen-relative slot; wins over `root` where both apply. */ region?: SymbolRegion; } export declare const CONDITION_SIDE_SYMBOL: Partial>; /** * Resolve the side-view symbol overlay URL for a condition on a tooth. `target` * picks a specific root / side; omitted, every strategy resolves to the * whole-tooth or default marker, which is what the legacy call sites want. */ export declare function conditionSymbolUrl(meta: ToothMeta, assetBaseUrl: string, spec: SideSymbolSpec, target?: SymbolTarget): string; export type Projection = 'side' | 'plan'; /** The user-selectable view: a single projection, or both stacked together. */ export type ViewMode = Projection | 'both'; /** Map the clinical `Surface` enum to the asset surface filename token. */ export declare const SURFACE_ASSET_NAME: Record; /** Conditions that render as per-surface fills in the plan view. */ export declare const CONDITION_PLAN_FOLDER: Partial>; /** Resolve a per-surface plan-view overlay URL (e.g. caries on the distal). */ export declare function surfaceSymbolUrl(meta: ToothMeta, assetBaseUrl: string, folder: string, surface: Surface): string; /** * Whole-tooth symbols in the PLAN projection. Without these a crowned tooth * reads as a plain tooth in the occlusal view, which also made `projection` * `'both'` disagree with itself between its own rows. * * `anchorage` is the odd one: the plan art is two generic screen-side markers * (no FDI in the name) composited over the default render, so its mesial/distal * root has to be flipped through the quadrant — `symbolUrl` does that. */ export declare const CONDITION_PLAN_SYMBOL: Partial>; /** Resolve the plan-view whole-tooth symbol overlay URL for a condition. */ export declare function planSymbolUrl(meta: ToothMeta, assetBaseUrl: string, spec: SideSymbolSpec, target?: SymbolTarget): string; /** * How much to scale a plan overlay so its tooth registers with the base render. * `1` for everything on the standard canvas. */ export declare function planOverlayScale(url: string): number; export interface ConditionDefinition { id: string; /** Already-translated label — the CONSUMER owns custom-condition copy. */ label: string; color?: string; part?: SymbolPart; sideSymbol?: SideSymbolSpec; planSymbol?: SideSymbolSpec; planFolder?: string; /** 1–3 char mark rendered when the condition has no artwork. */ glyph?: string; } export type ConditionRegistry = readonly ConditionDefinition[]; /** * A condition's display name. Built-ins resolve to an i18n key; registry * entries carry literal text, because their copy lives in the consuming app. */ export type ConditionLabel = { kind: 'key'; key: string; } | { kind: 'text'; text: string; }; /** Colour used when a condition is neither registered nor built in. */ export declare const FALLBACK_CONDITION_TOKEN = "--muted-foreground"; /** i18n key for a built-in condition label (`ui.toothScheme.condition.*`). */ export declare function conditionLabelKey(condition: ToothCondition): string; /** * Look up a registry entry. Registry ids shadow built-ins field by field, so a * practice can re-tint or re-label a shipped finding without losing its art. */ export declare function conditionDefinition(condition: ConditionId, registry?: ConditionRegistry): ConditionDefinition | undefined; export declare function conditionLabel(condition: ConditionId, registry?: ConditionRegistry): ConditionLabel; /** Paintable colour for a condition — never empty, so nothing renders unseen. */ export declare function conditionColor(condition: ConditionId, registry?: ConditionRegistry): string; /** Paint colour for ONE charted symbol — its own override wins. */ export declare function symbolColor(symbol: ToothSymbol, registry?: ConditionRegistry): string; export declare function conditionSideSymbol(condition: ConditionId, registry?: ConditionRegistry): SideSymbolSpec | undefined; export declare function conditionPlanSymbol(condition: ConditionId, registry?: ConditionRegistry): SideSymbolSpec | undefined; export declare function conditionPlanFolder(condition: ConditionId, registry?: ConditionRegistry): string | undefined; export declare function conditionPart(condition: ConditionId, registry?: ConditionRegistry): SymbolPart; /** * The 1–3 character mark that stands in when a condition ships no artwork. * Falls back to the id's initials so a practice-defined finding can be charted * the day it is registered, rather than rendering as a silent blank. */ export declare function conditionGlyph(condition: ConditionId, registry?: ConditionRegistry): string; /** * Whether a condition has artwork in a projection. When false the renderer * must fall back to the glyph badge — that seam is what lets a practice chart * a finding before a designer draws it. */ export declare function conditionHasArtwork(condition: ConditionId, projection: Projection, registry?: ConditionRegistry): boolean; /** * The findings on a tooth as a symbol list. `state.symbols` wins outright when * present (an explicit empty array therefore means "no findings"); otherwise * the legacy pair is derived faithfully — the shared `surfaces` list attaches * only to surface-part conditions, exactly as the flat renderer treated it. */ export declare function symbolsForTooth(state: ToothState | undefined, registry?: ConditionRegistry): ToothSymbol[]; /** * Clickable surface regions of a plan-view tooth, named by SCREEN position. * `zoneSurface()` resolves each to the anatomical `Surface` per quadrant. */ export type SurfaceZone = 'top' | 'bottom' | 'left' | 'right' | 'center'; /** * Which zones a tooth exposes. Molars & premolars have a central occlusal * table (5 zones); incisors & canines have no occlusal surface to mark, so * their crown splits into 4 directional zones. */ export declare function zonesForTooth(meta: ToothMeta): SurfaceZone[]; /** * Map a screen zone to the anatomical surface, QUADRANT-AWARE. Two flips make * this non-trivial (ported from the platform odontogram's `_getAreaBySide`, so * a finding always lands on the correct surface wherever the tooth sits): * * - mesial / distal (left & right zones) flip between the patient's right * quadrants {1,4} and left quadrants {2,3} — "toward the midline" reverses. * - buccal(vestibular) / lingual(oral) (top & bottom zones) flip between the * upper and lower arch. * - centre is always occlusal. * * Primary quadrants (5-8) normalise to their permanent counterpart (1-4). */ export declare function zoneSurface(meta: ToothMeta, zone: SurfaceZone): Surface; /** * SVG `d` path for a clickable surface zone within a tooth's local box. * 5-zone teeth frame a central occlusal rect with four edge trapezoids; * 4-zone teeth split the box along both diagonals into four triangles meeting * at the centre. */ export declare function zonePath(zone: SurfaceZone, rect: { x: number; y: number; w: number; h: number; }, hasCenter: boolean): string; export declare const TOOTH_GAP = 5; export declare const TOOTH_ROW_GAP = 40; export declare const TOOTH_HIT_PAD = 4; export declare const TOOTH_SLOT_WIDTH = 36; /** Facial-view slot block-size; the plan/occlusal view uses a shorter slot. */ export declare const TOOTH_SLOT_HEIGHT = 62; /** * Resolve a tooth render URL. `view` follows the arch (`top` = upper, * `bottom` = lower); `symbol` selects the artwork layer (`default` is the * healthy tooth; condition/finding overlays use their symbol folder). */ export declare function toothImageUrl(meta: ToothMeta, assetBaseUrl: string, symbol?: string, projection?: 'side' | 'plan'): string; export interface PositionedTooth { fdi: FdiId; meta: ToothMeta; x: number; y: number; } /** * Produce the positioned list of teeth for a given dentition. The layout * is a two-row grid reading viewer-left-to-right across the upper arch, * then viewer-left-to-right across the lower arch. Clinical convention * is preserved: the patient's right sits on the viewer's left in both * arches, regardless of document direction. */ export declare function layoutTeeth(dentition: Dentition, slotHeight?: number, options?: { arch?: Arch; }): { teeth: PositionedTooth[]; width: number; height: number; }; export type SpecialElementKind = 'quadrant' | 'sextant' | 'arch' | 'general'; export interface SpecialElement { /** * Stable id — a chart key and the value emitted by `onSelect`. Permanent: * `Q1`–`Q4`, `S1`–`S6`, `AS`, `AI`, `general`. Primary mirrors with an `m` * prefix: `mQ1`…, `mS1`…, `mAS`, `mAI`, `mGeneral`. */ id: string; kind: SpecialElementKind; arch: Arch; dentition: 'permanent' | 'primary'; /** i18n token under `toothScheme.special.*` for the accessible name. */ labelKey: 'quadrant' | 'sextant' | 'archUpper' | 'archLower' | 'general'; /** Interpolated into the `{{n}}` of quadrant / sextant labels (e.g. `1`). */ labelIndex?: number; /** Short code shown in the slot, e.g. `Q1`, `S3`, `AS`, `Gen.`. */ code: string; /** * Member teeth (FDI) — informational for consumers that highlight or expand a * region. Selection itself keys on `id`; the platform expands membership * server-side. Sextants follow the WHO/CPITN convention. */ teeth: FdiId[]; /** * Column within the special-element block. Quadrant and its paired sextant * share a column so they stack (Q1↕S1, Q2↕S2, AS↕S3 / S4↕Q4, S5↕Q3, S6↕AI). */ column: number; /** Row the element attaches to: side (quadrant/arch/general) or plan (sextant). */ row: Projection; } /** Every special element, flat — permanent then primary. */ export declare const SPECIAL_ELEMENTS: SpecialElement[]; /** Lookup a special element by its id (`Q1`, `mS3`, `AS`, `general`…). */ export declare const SPECIAL_BY_ID: Record; /** True when an id names a special element rather than a tooth. */ export declare function isSpecialElement(id: string): boolean; /** * The special-element SLOTS that attach to a given row, ordered by column. * Quadrant / arch live on the `side` row; sextants on the `plan` row. The * whole-mouth `general` element is excluded — it renders as a switch beneath the * chart (see `generalElement`), not as a column. Mixed dentition reuses the * permanent set (the platform has no mixed view). */ export declare function specialsForRow(dentition: Dentition, arch: Arch, projection: Projection): SpecialElement[]; /** * The whole-mouth `general` element for a dentition. It is rendered as a switch * beneath the chart rather than as a cramped icon column (the platform's vertical * "GENERALE" glyph is illegible at column width). */ export declare function generalElement(dentition: Dentition): SpecialElement; /** A special-element slot placed in an SVG, to the inline-end of the teeth. */ export interface PositionedSpecial { element: SpecialElement; x: number; y: number; } /** Special-element slot block-size — matches a tooth slot so icons read clearly. */ export declare const SPECIAL_SLOT_WIDTH = 36; /** * Resolve the photoreal icon URL for a special element, or `null` when it has * none (sextants are label-only — the platform ships no sextant icon). Icons * live in the `global/` sibling of the `teeth/` render folder; quadrants and * arches reuse their code as the file stem (`Q1`, `AS`, …), general resolves to * `general`, and primary elements reuse the permanent art. */ export declare function specialIconUrl(element: SpecialElement, assetBaseUrl: string): string | null; /** Gap between the last tooth and the first special-element column. */ export declare const SPECIAL_GAP = 8; /** Column pitch within the special-element block. */ export declare const SPECIAL_PITCH: number; /** * Place a row's special elements after the teeth (inline-end) and report the * total row width. Columns are derived from `element.column`, so a quadrant and * its paired sextant share an x and stack across the two rows. */ export declare function layoutSpecials(specials: SpecialElement[], teethWidth: number): { specials: PositionedSpecial[]; width: number; }; export declare function labelFor(id: FdiId, numbering: Numbering): string; export declare function emptyChart(): ToothChart; export declare function chartFromConditions(conditions: Partial>): ToothChart; /** * Build a chart from the richer per-symbol shape. Each entry also projects its * built-in conditions and surfaces back onto the legacy pair so readers that * predate `symbols` (the first-condition `data-condition`, the periodontal * chart, the care-plan card) still see the tooth. Registry ids are omitted from * that projection — a legacy reader does not know the custom vocabulary. */ export declare function chartFromSymbols(symbols: Partial>): ToothChart; //# sourceMappingURL=tooth-data.d.ts.map