when-am-i / files / src / types / index.ts
index.ts
| 1 | // Shared types for "When am I?" (the calendar app). |
| 2 | // |
| 3 | // Data flow (see plan.md): click (lat,lng) + date |
| 4 | // → find all regions containing the point |
| 5 | // → find all eras (may overlap) covering the date |
| 6 | // → convert the date into each era's calendar (or fall back to descriptive-only) |
| 7 | // → render the full list of Answers. |
| 8 | |
| 9 | // ── GeoJSON geometry ──────────────────────────────────────────────────────── |
| 10 | // We only need area geometries for point-in-region tests. GeoJSON coordinates |
| 11 | // are [longitude, latitude] (note the order — easy to get backwards). |
| 12 | |
| 13 | /** A single linear ring: an array of [lng, lat] positions. */ |
| 14 | export type LinearRing = [number, number][]; |
| 15 | |
| 16 | export interface Polygon { |
| 17 | type: 'Polygon'; |
| 18 | /** First ring is the outer boundary; any further rings are holes. */ |
| 19 | coordinates: LinearRing[]; |
| 20 | } |
| 21 | |
| 22 | export interface MultiPolygon { |
| 23 | type: 'MultiPolygon'; |
| 24 | /** An array of polygons, each itself an array of rings. */ |
| 25 | coordinates: LinearRing[][]; |
| 26 | } |
| 27 | |
| 28 | export type Geometry = Polygon | MultiPolygon; |
| 29 | |
| 30 | // ── Region & era (the on-disk JSON shape) ───────────────────────────────────── |
| 31 | // One JSON file in src/data/calendars/ deserializes to a CalendarRegion. |
| 32 | |
| 33 | export interface Region { |
| 34 | name: string; |
| 35 | geo: Geometry; |
| 36 | } |
| 37 | |
| 38 | /** |
| 39 | * One calendar era used in a region over some span of time. Eras MAY overlap in |
| 40 | * time — parallel calendars coexisting at the same place and time is expected |
| 41 | * (e.g. the colonial Julian and the Massachusett lunar reckoning both apply in |
| 42 | * 1700 Massachusetts Bay), whether the overlap is within one region's eras or |
| 43 | * across two overlapping regions. Not an error. |
| 44 | */ |
| 45 | export interface Era { |
| 46 | name: string; |
| 47 | /** Proleptic-Gregorian YYYY-MM-DD, or null for "since the earliest time". */ |
| 48 | start: string | null; |
| 49 | /** Proleptic-Gregorian YYYY-MM-DD, or null for "up to the present". */ |
| 50 | end: string | null; |
| 51 | /** Calendar system name, e.g. "Gregorian", "Julian", "Islamic". */ |
| 52 | calendar: string; |
| 53 | /** Whose answer this is, e.g. "British colonists", "Massachusett people". */ |
| 54 | usedBy?: string; |
| 55 | description?: string; |
| 56 | /** URLs or citations backing this era. */ |
| 57 | sources?: string[]; |
| 58 | /** |
| 59 | * Documented events a local reckons time from (for calendars with no |
| 60 | * numbered year). The engine reports the most recent one at/before the date |
| 61 | * as "about N winters since <event>". Optional and back-compatible. |
| 62 | */ |
| 63 | anchorEvents?: AnchorEvent[]; |
| 64 | /** |
| 65 | * When true, this era opts its region out of the modern global Gregorian |
| 66 | * default (see resolve.ts's DEFAULT_MODERN_ERA) for any date this era |
| 67 | * covers — for the rare place where no outside civil calendar applies at |
| 68 | * all (e.g. North Sentinel Island), not just one with its own different |
| 69 | * calendar. Defaults to false; almost no region should set this. |
| 70 | */ |
| 71 | suppressModernDefault?: boolean; |
| 72 | } |
| 73 | |
| 74 | /** |
| 75 | * A documented, dateable event a local could reckon time *from* — e.g. "the |
| 76 | * great dying" or a remembered eclipse. Used by calendars with no continuous |
| 77 | * numbered year to answer "When am I?" as "about N winters since <event>". |
| 78 | * Anchors are per-region curated data: each must be a real, sourced event — the |
| 79 | * engine only counts the winters, it never invents the event (see plan.md). |
| 80 | */ |
| 81 | export interface AnchorEvent { |
| 82 | /** Proleptic-Gregorian YYYY-MM-DD the event happened (or began). */ |
| 83 | date: string; |
| 84 | /** How a local would name it, e.g. "the great dying". */ |
| 85 | name: string; |
| 86 | /** URLs or citations documenting the event. Required: anchors must be sourced. */ |
| 87 | sources: string[]; |
| 88 | } |
| 89 | |
| 90 | export interface CalendarRegion { |
| 91 | region: Region; |
| 92 | eras: Era[]; |
| 93 | /** Repo-relative path of the JSON file this region was loaded from. */ |
| 94 | sourceFile?: string; |
| 95 | } |
| 96 | |
| 97 | // ── Engine input ────────────────────────────────────────────────────────────── |
| 98 | |
| 99 | /** |
| 100 | * A date in the proleptic Gregorian calendar using astronomical year numbering: |
| 101 | * year 0 = 1 BCE, year -1 = 2 BCE, etc. This is the engine-internal form; the |
| 102 | * UI translates a BCE/CE picker into it. Comparisons go through a Julian Day |
| 103 | * Number so BCE/CE and pre-1582 dates order correctly (see plan.md, Q2). |
| 104 | */ |
| 105 | export interface HistoricalDate { |
| 106 | year: number; |
| 107 | /** 1–12. */ |
| 108 | month: number; |
| 109 | /** 1–31. */ |
| 110 | day: number; |
| 111 | } |
| 112 | |
| 113 | // ── Engine output ───────────────────────────────────────────────────────────── |
| 114 | |
| 115 | /** The result of converting a date into one calendar system. */ |
| 116 | export interface CalendarConversion { |
| 117 | /** The calendar system name (echoes Era.calendar). */ |
| 118 | system: string; |
| 119 | /** Human-readable local year, e.g. "1700 AD", "AH 1111", "5460". */ |
| 120 | localYearLabel: string; |
| 121 | /** Optional fuller local date when meaningful. */ |
| 122 | localDateLabel?: string; |
| 123 | /** |
| 124 | * Whether a deterministic epoch+rule produced a numeric local year. |
| 125 | * `false` ⇒ descriptive-only: no continuous year count exists (e.g. an |
| 126 | * observational lunar calendar). The UI then shows description + sources + |
| 127 | * the Gregorian reference year instead of a converted number. |
| 128 | */ |
| 129 | convertible: boolean; |
| 130 | /** Optional clarifying note, esp. for descriptive-only calendars. */ |
| 131 | note?: string; |
| 132 | /** |
| 133 | * Optional "about N winters since <event>" reckoning, computed from the |
| 134 | * era's `anchorEvents`. Present only when an anchor event precedes the date. |
| 135 | * Mainly enriches descriptive-only calendars, which have no numbered year. |
| 136 | */ |
| 137 | reckoning?: { label: string; sources: string[] }; |
| 138 | } |
| 139 | |
| 140 | /** One possible reply a local could give to "When am I?". */ |
| 141 | export interface Answer { |
| 142 | region: Region; |
| 143 | era: Era; |
| 144 | conversion: CalendarConversion; |
| 145 | /** Repo-relative path of the source JSON file, for citation links. */ |
| 146 | sourceFile?: string; |
| 147 | } |
| 148 | |
| 149 | /** Why the engine produced no answers. */ |
| 150 | export type MissReason = 'no-region' | 'no-era'; |
| 151 | |
| 152 | /** |
| 153 | * The engine's full result: every valid answer a local could give. `answers` |
| 154 | * may be empty, in which case `missReason` explains why (no region covers the |
| 155 | * point, or a region matched but no era covers the date). |
| 156 | */ |
| 157 | export interface Resolution { |
| 158 | answers: Answer[]; |
| 159 | missReason?: MissReason; |
| 160 | } |