@mailwoman/geographic-model 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +155 -0
  2. package/artifact.ts +198 -0
  3. package/compile.ts +350 -0
  4. package/data/geographic-model.json +172 -0
  5. package/data/model/concepts.json +114 -0
  6. package/data/model/mappings.json +18 -0
  7. package/data/model/model.json +3 -0
  8. package/data/model/relations.json +14 -0
  9. package/index.ts +51 -0
  10. package/load.ts +396 -0
  11. package/lookup.ts +129 -0
  12. package/out/artifact.d.ts +106 -0
  13. package/out/artifact.d.ts.map +1 -0
  14. package/out/artifact.js +135 -0
  15. package/out/artifact.js.map +1 -0
  16. package/out/compile.d.ts +84 -0
  17. package/out/compile.d.ts.map +1 -0
  18. package/out/compile.js +259 -0
  19. package/out/compile.js.map +1 -0
  20. package/out/index.d.ts +51 -0
  21. package/out/index.d.ts.map +1 -0
  22. package/out/index.js +51 -0
  23. package/out/index.js.map +1 -0
  24. package/out/load.d.ts +122 -0
  25. package/out/load.d.ts.map +1 -0
  26. package/out/load.js +269 -0
  27. package/out/load.js.map +1 -0
  28. package/out/lookup.d.ts +64 -0
  29. package/out/lookup.d.ts.map +1 -0
  30. package/out/lookup.js +68 -0
  31. package/out/lookup.js.map +1 -0
  32. package/out/schema.d.ts +366 -0
  33. package/out/schema.d.ts.map +1 -0
  34. package/out/schema.js +166 -0
  35. package/out/schema.js.map +1 -0
  36. package/out/scripts/build-artifact.d.ts +51 -0
  37. package/out/scripts/build-artifact.d.ts.map +1 -0
  38. package/out/scripts/build-artifact.js +78 -0
  39. package/out/scripts/build-artifact.js.map +1 -0
  40. package/out/validate.d.ts +67 -0
  41. package/out/validate.d.ts.map +1 -0
  42. package/out/validate.js +465 -0
  43. package/out/validate.js.map +1 -0
  44. package/out/validation-issues.d.ts +84 -0
  45. package/out/validation-issues.d.ts.map +1 -0
  46. package/out/validation-issues.js +190 -0
  47. package/out/validation-issues.js.map +1 -0
  48. package/package.json +120 -0
  49. package/schema.ts +399 -0
  50. package/validate.ts +845 -0
  51. package/validation-issues.ts +305 -0
package/schema.ts ADDED
@@ -0,0 +1,399 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The authored-record schema for `@mailwoman/geographic-model`: the stable identifiers, the closed
7
+ * vocabularies, and the record shapes for concepts, relations, external-vocabulary mappings, source
8
+ * observations, and derived facts.
9
+ *
10
+ * Three properties hold by construction, and they are why the file reads the way it does.
11
+ *
12
+ * **No numeric field exists anywhere in this schema** — not a strength, not a confidence, not a
13
+ * count. {@link Modality} is an ordinal vocabulary of WORDS and this module exports no order over
14
+ * it, because a number attached to an authored relationship is a ranking weight whatever it is
15
+ * called, and ranking belongs to `@mailwoman/resolver` and `@mailwoman/neural`.
16
+ *
17
+ * **An authored assertion, a source observation, and a derived fact are three different types.** A
18
+ * {@link RelationAssertion} is authored by a curator and lives on the concept it is about; a
19
+ * {@link SourceObservationRecord} records what a named external source states and never enters the
20
+ * concept table; a {@link DerivedFactRecord} names the procedure that produced it and every input
21
+ * that procedure read. Their identifiers are separately branded, so one is not assignable where
22
+ * another is expected.
23
+ *
24
+ * **Every record carries provenance, and a derived fact carries it structurally.** A derived fact
25
+ * has no {@link SourceProvenance} of its own: its `derivation` plus its resolved `inputs` ARE its
26
+ * provenance, and each input carries source provenance in turn.
27
+ *
28
+ * Identifier namespaces are per-table. A {@link ConceptID} and a {@link RelationID} may read the
29
+ * same string without colliding; the brands are what keep them apart in a consumer.
30
+ *
31
+ * Consumed by `./validate.ts` (which refuses a document violating any rule above), by #1926's
32
+ * deterministic compiler, and by the first authored document in #1927.
33
+ *
34
+ * Boundary record: `docs/superpowers/specs/2026-08-26-geographic-model-boundaries.md` (#1917).
35
+ */
36
+
37
+ import type { POICategoryID } from "@mailwoman/poi-taxonomy/types"
38
+ import type { Tagged } from "type-fest"
39
+
40
+ /**
41
+ * A concept identifier, e.g. `pharmacy`, `obtain_medication`. Branded — convert via {@link toConceptID}.
42
+ */
43
+ export type ConceptID = Tagged<string, "GeographicConceptID">
44
+
45
+ /**
46
+ * Brand a raw string as a {@link ConceptID}. Purely a compile-time assertion; the string is unchanged.
47
+ */
48
+ export function toConceptID(id: string): ConceptID {
49
+ return id as ConceptID
50
+ }
51
+
52
+ /**
53
+ * A relation identifier, e.g. `affords`. Branded — convert via {@link toRelationID}.
54
+ */
55
+ export type RelationID = Tagged<string, "GeographicRelationID">
56
+
57
+ /**
58
+ * Brand a raw string as a {@link RelationID}. Purely a compile-time assertion; the string is unchanged.
59
+ */
60
+ export function toRelationID(id: string): RelationID {
61
+ return id as RelationID
62
+ }
63
+
64
+ /**
65
+ * The identifier of one authored {@link RelationAssertion}. Branded — convert via {@link toRuleID}.
66
+ */
67
+ export type RuleID = Tagged<string, "GeographicRuleID">
68
+
69
+ /**
70
+ * Brand a raw string as a {@link RuleID}. Purely a compile-time assertion; the string is unchanged.
71
+ */
72
+ export function toRuleID(id: string): RuleID {
73
+ return id as RuleID
74
+ }
75
+
76
+ /**
77
+ * The identifier of one {@link ExternalMappingRecord}. Branded — convert via {@link toMappingID}.
78
+ */
79
+ export type MappingID = Tagged<string, "GeographicMappingID">
80
+
81
+ /**
82
+ * Brand a raw string as a {@link MappingID}. Purely a compile-time assertion; the string is unchanged.
83
+ */
84
+ export function toMappingID(id: string): MappingID {
85
+ return id as MappingID
86
+ }
87
+
88
+ /**
89
+ * The identifier of one {@link SourceObservationRecord}. Branded — convert via {@link toObservationID}.
90
+ */
91
+ export type ObservationID = Tagged<string, "GeographicObservationID">
92
+
93
+ /**
94
+ * Brand a raw string as an {@link ObservationID}. Purely a compile-time assertion; the string is unchanged.
95
+ */
96
+ export function toObservationID(id: string): ObservationID {
97
+ return id as ObservationID
98
+ }
99
+
100
+ /**
101
+ * The identifier of one {@link DerivedFactRecord}. Branded — convert via {@link toDerivedFactID}.
102
+ */
103
+ export type DerivedFactID = Tagged<string, "GeographicDerivedFactID">
104
+
105
+ /**
106
+ * Brand a raw string as a {@link DerivedFactID}. Purely a compile-time assertion; the string is unchanged.
107
+ */
108
+ export function toDerivedFactID(id: string): DerivedFactID {
109
+ return id as DerivedFactID
110
+ }
111
+
112
+ /**
113
+ * The complete set of concept kinds. Three, deliberately: the first executable slice needs an establishment class, an
114
+ * activity, and the place class an establishment is sited in. Widening this vocabulary is a reviewed schema revision,
115
+ * which is the review cost the program accepted in exchange for refusing speculative upper-ontology breadth.
116
+ */
117
+ export const ConceptKind = {
118
+ /**
119
+ * A geographic place — an area or a point that other things are sited in or near.
120
+ */
121
+ Place: "place",
122
+ /**
123
+ * A class of premises a person can go to, e.g. `pharmacy`.
124
+ */
125
+ Establishment: "establishment",
126
+ /**
127
+ * Something a person does, e.g. `obtain_medication`. The identifier is owned here; any statistics fitted against it
128
+ * are owned by #1683.
129
+ */
130
+ Activity: "activity",
131
+ } as const
132
+
133
+ export type ConceptKind = (typeof ConceptKind)[keyof typeof ConceptKind]
134
+
135
+ /**
136
+ * How strongly an assertion, an observation, or a derived fact claims its proposition holds.
137
+ *
138
+ * The vocabulary is ordinal in meaning and this module exports no order over it, on purpose. An exported rank would be
139
+ * one arithmetic step from a weight, and an authored weight is the single thing the package boundary forbids.
140
+ */
141
+ export const Modality = {
142
+ /**
143
+ * Holds in every instance; a counter-example falsifies the record rather than qualifying it.
144
+ */
145
+ Necessary: "necessary",
146
+ /**
147
+ * Never holds.
148
+ */
149
+ Prohibited: "prohibited",
150
+ StronglyExpected: "strongly_expected",
151
+ Expected: "expected",
152
+ WeaklyExpected: "weakly_expected",
153
+ /**
154
+ * Consistent with the concept and asserts nothing about how often it holds.
155
+ */
156
+ Possible: "possible",
157
+ Unusual: "unusual",
158
+ StronglyUnusual: "strongly_unusual",
159
+ } as const
160
+
161
+ export type Modality = (typeof Modality)[keyof typeof Modality]
162
+
163
+ /**
164
+ * Whether a relation's assertions admit exceptions.
165
+ */
166
+ export const RelationSemantics = {
167
+ /**
168
+ * An exception is a defect in the record set.
169
+ */
170
+ Hard: "hard",
171
+ /**
172
+ * An exception is expected and does not falsify the relation.
173
+ */
174
+ Defeasible: "defeasible",
175
+ } as const
176
+
177
+ export type RelationSemantics = (typeof RelationSemantics)[keyof typeof RelationSemantics]
178
+
179
+ /**
180
+ * A concept's authoring lifecycle. It says whether a consumer should read the record, and nothing about how much the
181
+ * record is worth.
182
+ */
183
+ export const ConceptStatus = {
184
+ /**
185
+ * Authored, not yet reviewed. A compiler may refuse to emit it.
186
+ */
187
+ Draft: "draft",
188
+ Active: "active",
189
+ /**
190
+ * Kept so existing references resolve; no new reference should be authored against it.
191
+ */
192
+ Deprecated: "deprecated",
193
+ } as const
194
+
195
+ export type ConceptStatus = (typeof ConceptStatus)[keyof typeof ConceptStatus]
196
+
197
+ /**
198
+ * The external vocabularies a concept can be mapped into. One today, and the mapping record below is typed against it
199
+ * directly; a second vocabulary turns {@link ExternalMappingRecord} into a union discriminated on `vocabulary`.
200
+ */
201
+ export const ExternalVocabulary = {
202
+ /**
203
+ * `@mailwoman/poi-taxonomy` category identifiers — the owner of external and curated POI categories.
204
+ */
205
+ POITaxonomy: "poi-taxonomy",
206
+ } as const
207
+
208
+ export type ExternalVocabulary = (typeof ExternalVocabulary)[keyof typeof ExternalVocabulary]
209
+
210
+ /**
211
+ * Where a record came from. `source` is required and non-empty on every record that carries provenance: a record whose
212
+ * source is blank is indistinguishable from a record nobody stands behind.
213
+ */
214
+ export interface SourceProvenance {
215
+ /**
216
+ * The naming authority, dataset, publication, or curator, e.g. `overture-places` or `mailwoman-curated`.
217
+ */
218
+ source: string
219
+ /**
220
+ * The source's own version or release string, when it has one, e.g. an Overture release.
221
+ */
222
+ sourceVersion?: string
223
+ /**
224
+ * The identifier of the specific record within the source, when the claim is traceable to one.
225
+ */
226
+ sourceRecord?: string
227
+ sourceURL?: string
228
+ /**
229
+ * ISO 8601 calendar date the record was authored, `YYYY-MM-DD`.
230
+ */
231
+ authoredAt?: string
232
+ notes?: string
233
+ }
234
+
235
+ /**
236
+ * One relation's definition. Relations are vocabulary, not claims: the record says what the relation MEANS and which
237
+ * concept kinds may stand on either side of it, and asserts nothing about any particular pair.
238
+ */
239
+ export interface RelationRecord {
240
+ id: RelationID
241
+ label: string
242
+ description: string
243
+ /**
244
+ * The concept kinds allowed on the asserting side.
245
+ */
246
+ domainKinds: ConceptKind[]
247
+ /**
248
+ * The concept kinds allowed on the target side.
249
+ */
250
+ rangeKinds: ConceptKind[]
251
+ transitive: boolean
252
+ symmetric: boolean
253
+ /**
254
+ * The relation reading the same edge in the other direction. When present it must resolve, and the relation it names
255
+ * must name this one back.
256
+ */
257
+ inverse?: RelationID
258
+ semantics: RelationSemantics
259
+ }
260
+
261
+ /**
262
+ * One authored claim, attached to the concept it is about. This is curated semantics — what a curator states holds, not
263
+ * what a dataset was observed to contain.
264
+ */
265
+ export interface RelationAssertion {
266
+ id: RuleID
267
+ relation: RelationID
268
+ target: ConceptID
269
+ modality: Modality
270
+ /**
271
+ * ISO 3166-1 alpha-2 codes the claim is scoped to. Absent means the curator scoped it to nowhere in particular, which
272
+ * is a weaker statement than scoping it to everywhere.
273
+ */
274
+ countries?: string[]
275
+ provenance: SourceProvenance
276
+ }
277
+
278
+ /**
279
+ * One concept, and everything authored about it.
280
+ */
281
+ export interface ConceptRecord {
282
+ id: ConceptID
283
+ label: string
284
+ description: string
285
+ kind: ConceptKind
286
+ /**
287
+ * Broader concepts this one is a kind of. May be empty; may not name this concept, directly or around a cycle.
288
+ */
289
+ isA: ConceptID[]
290
+ assertions: RelationAssertion[]
291
+ provenance: SourceProvenance
292
+ status: ConceptStatus
293
+ }
294
+
295
+ /**
296
+ * A translation from an external vocabulary's identifier into a concept owned here. It carries no semantics of its own:
297
+ * it says which external identifier names the same thing, and on whose authority.
298
+ */
299
+ export interface ExternalMappingRecord {
300
+ id: MappingID
301
+ concept: ConceptID
302
+ vocabulary: ExternalVocabulary
303
+ /**
304
+ * The identifier in the external vocabulary. Typed as a `POICategoryID` because `poi-taxonomy` is the only member of
305
+ * {@link ExternalVocabulary}; a second member makes this field a per-vocabulary type.
306
+ */
307
+ externalID: POICategoryID
308
+ provenance: SourceProvenance
309
+ }
310
+
311
+ /**
312
+ * A proposition a named external source states, recorded in this model's vocabulary.
313
+ *
314
+ * It is kept out of the concept table on purpose. An observation is evidence about the world that someone else
315
+ * collected; promoting one into an authored assertion is a curation decision that has to be made and provenanced
316
+ * explicitly, never by the record sitting in a convenient place.
317
+ */
318
+ export interface SourceObservationRecord {
319
+ id: ObservationID
320
+ subject: ConceptID
321
+ relation: RelationID
322
+ object: ConceptID
323
+ modality: Modality
324
+ /**
325
+ * ISO 3166-1 alpha-2 codes the source scoped its statement to.
326
+ */
327
+ countries?: string[]
328
+ provenance: SourceProvenance
329
+ }
330
+
331
+ /**
332
+ * Which table a {@link DerivationInput} points into.
333
+ */
334
+ export const DerivationInputKind = {
335
+ Concept: "concept",
336
+ Relation: "relation",
337
+ Assertion: "assertion",
338
+ Mapping: "mapping",
339
+ Observation: "observation",
340
+ DerivedFact: "derived_fact",
341
+ } as const
342
+
343
+ export type DerivationInputKind = (typeof DerivationInputKind)[keyof typeof DerivationInputKind]
344
+
345
+ /**
346
+ * One record a derivation read. The union is discriminated on `kind` so the identifier's brand matches the table it
347
+ * resolves against.
348
+ */
349
+ export type DerivationInput =
350
+ | { kind: typeof DerivationInputKind.Concept; id: ConceptID }
351
+ | { kind: typeof DerivationInputKind.Relation; id: RelationID }
352
+ | { kind: typeof DerivationInputKind.Assertion; id: RuleID }
353
+ | { kind: typeof DerivationInputKind.Mapping; id: MappingID }
354
+ | { kind: typeof DerivationInputKind.Observation; id: ObservationID }
355
+ | { kind: typeof DerivationInputKind.DerivedFact; id: DerivedFactID }
356
+
357
+ /**
358
+ * A fact a named procedure computed from named inputs. Never hand-authored: #1926's compiler writes this table, and the
359
+ * validator refuses a fact whose derivation is unnamed or whose inputs do not resolve.
360
+ *
361
+ * There is no provenance field. The derivation plus the inputs is the provenance, and it is the stronger kind — a
362
+ * source string can be copied onto a record that did not come from it, while an input list either resolves or the
363
+ * document does not validate.
364
+ */
365
+ export interface DerivedFactRecord {
366
+ id: DerivedFactID
367
+ /**
368
+ * The deterministic procedure that produced this fact, named so a reader can re-run it.
369
+ */
370
+ derivation: string
371
+ /**
372
+ * Every record the derivation read. At least one, and each must resolve.
373
+ */
374
+ inputs: DerivationInput[]
375
+ subject: ConceptID
376
+ relation: RelationID
377
+ object: ConceptID
378
+ modality: Modality
379
+ countries?: string[]
380
+ }
381
+
382
+ /**
383
+ * One authored document: the whole record set a validator, and later a compiler, reads at once.
384
+ *
385
+ * All six fields are required, `derivedFacts` included. A hand-authored file therefore writes `"derivedFacts": []`,
386
+ * which is the point — an absent table and an empty table are different claims, and the format that allows the first to
387
+ * stand in for the second is the format where a dropped table reads as a world with no derived facts in it.
388
+ */
389
+ export interface GeographicModelDocument {
390
+ /**
391
+ * The document's own schema/data version.
392
+ */
393
+ version: string
394
+ relations: RelationRecord[]
395
+ concepts: ConceptRecord[]
396
+ mappings: ExternalMappingRecord[]
397
+ observations: SourceObservationRecord[]
398
+ derivedFacts: DerivedFactRecord[]
399
+ }