@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.
- package/README.md +155 -0
- package/artifact.ts +198 -0
- package/compile.ts +350 -0
- package/data/geographic-model.json +172 -0
- package/data/model/concepts.json +114 -0
- package/data/model/mappings.json +18 -0
- package/data/model/model.json +3 -0
- package/data/model/relations.json +14 -0
- package/index.ts +51 -0
- package/load.ts +396 -0
- package/lookup.ts +129 -0
- package/out/artifact.d.ts +106 -0
- package/out/artifact.d.ts.map +1 -0
- package/out/artifact.js +135 -0
- package/out/artifact.js.map +1 -0
- package/out/compile.d.ts +84 -0
- package/out/compile.d.ts.map +1 -0
- package/out/compile.js +259 -0
- package/out/compile.js.map +1 -0
- package/out/index.d.ts +51 -0
- package/out/index.d.ts.map +1 -0
- package/out/index.js +51 -0
- package/out/index.js.map +1 -0
- package/out/load.d.ts +122 -0
- package/out/load.d.ts.map +1 -0
- package/out/load.js +269 -0
- package/out/load.js.map +1 -0
- package/out/lookup.d.ts +64 -0
- package/out/lookup.d.ts.map +1 -0
- package/out/lookup.js +68 -0
- package/out/lookup.js.map +1 -0
- package/out/schema.d.ts +366 -0
- package/out/schema.d.ts.map +1 -0
- package/out/schema.js +166 -0
- package/out/schema.js.map +1 -0
- package/out/scripts/build-artifact.d.ts +51 -0
- package/out/scripts/build-artifact.d.ts.map +1 -0
- package/out/scripts/build-artifact.js +78 -0
- package/out/scripts/build-artifact.js.map +1 -0
- package/out/validate.d.ts +67 -0
- package/out/validate.d.ts.map +1 -0
- package/out/validate.js +465 -0
- package/out/validate.js.map +1 -0
- package/out/validation-issues.d.ts +84 -0
- package/out/validation-issues.d.ts.map +1 -0
- package/out/validation-issues.js +190 -0
- package/out/validation-issues.js.map +1 -0
- package/package.json +120 -0
- package/schema.ts +399 -0
- package/validate.ts +845 -0
- 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
|
+
}
|