@mailwoman/geographic-model 10.0.0 → 10.2.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 +32 -32
- package/data/geographic-model.json +5 -5
- package/data/model/concepts.json +5 -5
- package/lib/artifact.ts +37 -65
- package/lib/compile.ts +55 -70
- package/lib/index.ts +22 -35
- package/lib/load.ts +60 -72
- package/lib/lookup.ts +12 -28
- package/lib/packaged.ts +50 -0
- package/lib/schema.ts +104 -97
- package/lib/validate.ts +32 -56
- package/lib/validation/issues.ts +32 -36
- package/lib/validation/references.ts +24 -27
- package/out/artifact.d.ts +28 -45
- package/out/artifact.d.ts.map +1 -1
- package/out/artifact.js +25 -52
- package/out/artifact.js.map +1 -1
- package/out/compile.d.ts +27 -41
- package/out/compile.d.ts.map +1 -1
- package/out/compile.js +42 -64
- package/out/compile.js.map +1 -1
- package/out/index.d.ts +22 -35
- package/out/index.d.ts.map +1 -1
- package/out/index.js +22 -35
- package/out/index.js.map +1 -1
- package/out/load.d.ts +30 -39
- package/out/load.d.ts.map +1 -1
- package/out/load.js +42 -56
- package/out/load.js.map +1 -1
- package/out/lookup.d.ts +12 -28
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +4 -16
- package/out/lookup.js.map +1 -1
- package/out/packaged.d.ts +24 -0
- package/out/packaged.d.ts.map +1 -0
- package/out/packaged.js +42 -0
- package/out/packaged.js.map +1 -0
- package/out/schema.d.ts +104 -97
- package/out/schema.d.ts.map +1 -1
- package/out/schema.js +37 -52
- package/out/schema.js.map +1 -1
- package/out/tools/build-artifact.d.ts +27 -0
- package/out/tools/build-artifact.d.ts.map +1 -0
- package/out/tools/build-artifact.js +44 -0
- package/out/tools/build-artifact.js.map +1 -0
- package/out/validate.d.ts +29 -50
- package/out/validate.d.ts.map +1 -1
- package/out/validate.js +12 -35
- package/out/validate.js.map +1 -1
- package/out/validation/issues.d.ts +17 -20
- package/out/validation/issues.d.ts.map +1 -1
- package/out/validation/issues.js +25 -29
- package/out/validation/issues.js.map +1 -1
- package/out/validation/references.d.ts.map +1 -1
- package/out/validation/references.js +20 -23
- package/out/validation/references.js.map +1 -1
- package/package.json +35 -76
- package/tools/build-artifact.ts +56 -0
- package/out/scripts/build-artifact.d.ts +0 -51
- package/out/scripts/build-artifact.d.ts.map +0 -1
- package/out/scripts/build-artifact.js +0 -85
- package/out/scripts/build-artifact.js.map +0 -1
package/lib/schema.ts
CHANGED
|
@@ -3,144 +3,129 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* vocabularies
|
|
8
|
-
* observations, and derived facts.
|
|
6
|
+
* Defines the authored record schema for `@mailwoman/geographic-model`: identifiers, closed
|
|
7
|
+
* vocabularies and the shapes of concepts, relations, mappings, observations and derived facts.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
9
|
+
* The schema has no numeric fields. {@link Modality} is a vocabulary of words with no exported order,
|
|
10
|
+
* because ranking weights belong to `@mailwoman/resolver` and `@mailwoman/neural`.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* called, and ranking belongs to `@mailwoman/resolver` and `@mailwoman/neural`.
|
|
12
|
+
* Authored assertions, source observations and derived facts are separate types with separately
|
|
13
|
+
* branded identifiers. A derived fact has no {@link SourceProvenance}. Its `derivation` and `inputs`
|
|
14
|
+
* serve as its provenance.
|
|
16
15
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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).
|
|
16
|
+
* Each table has its own identifier namespace, so a {@link ConceptID} and a {@link RelationID} may hold
|
|
17
|
+
* the same string.
|
|
35
18
|
*/
|
|
36
19
|
|
|
37
20
|
import type { POICategoryID } from "@mailwoman/poi-taxonomy/types"
|
|
38
21
|
import type { Tagged } from "type-fest"
|
|
39
22
|
|
|
40
23
|
/**
|
|
41
|
-
* A concept identifier,
|
|
24
|
+
* A branded concept identifier, such as `pharmacy` or `obtain_medication`.
|
|
42
25
|
*/
|
|
43
26
|
export type ConceptID = Tagged<string, "GeographicConceptID">
|
|
44
27
|
|
|
45
28
|
/**
|
|
46
|
-
*
|
|
29
|
+
* Brands a string as a {@link ConceptID} without changing it.
|
|
47
30
|
*/
|
|
48
31
|
export function toConceptID(id: string): ConceptID {
|
|
49
32
|
return id as ConceptID
|
|
50
33
|
}
|
|
51
34
|
|
|
52
35
|
/**
|
|
53
|
-
* A relation identifier,
|
|
36
|
+
* A branded relation identifier, such as `affords`.
|
|
54
37
|
*/
|
|
55
38
|
export type RelationID = Tagged<string, "GeographicRelationID">
|
|
56
39
|
|
|
57
40
|
/**
|
|
58
|
-
*
|
|
41
|
+
* Brands a string as a {@link RelationID} without changing it.
|
|
59
42
|
*/
|
|
60
43
|
export function toRelationID(id: string): RelationID {
|
|
61
44
|
return id as RelationID
|
|
62
45
|
}
|
|
63
46
|
|
|
64
47
|
/**
|
|
65
|
-
* The identifier of one
|
|
48
|
+
* The branded identifier of one {@link RelationAssertion}.
|
|
66
49
|
*/
|
|
67
50
|
export type RuleID = Tagged<string, "GeographicRuleID">
|
|
68
51
|
|
|
69
52
|
/**
|
|
70
|
-
*
|
|
53
|
+
* Brands a string as a {@link RuleID} without changing it.
|
|
71
54
|
*/
|
|
72
55
|
export function toRuleID(id: string): RuleID {
|
|
73
56
|
return id as RuleID
|
|
74
57
|
}
|
|
75
58
|
|
|
76
59
|
/**
|
|
77
|
-
* The identifier of one {@link ExternalMappingRecord}.
|
|
60
|
+
* The branded identifier of one {@link ExternalMappingRecord}.
|
|
78
61
|
*/
|
|
79
62
|
export type MappingID = Tagged<string, "GeographicMappingID">
|
|
80
63
|
|
|
81
64
|
/**
|
|
82
|
-
*
|
|
65
|
+
* Brands a string as a {@link MappingID} without changing it.
|
|
83
66
|
*/
|
|
84
67
|
export function toMappingID(id: string): MappingID {
|
|
85
68
|
return id as MappingID
|
|
86
69
|
}
|
|
87
70
|
|
|
88
71
|
/**
|
|
89
|
-
* The identifier of one {@link SourceObservationRecord}.
|
|
72
|
+
* The branded identifier of one {@link SourceObservationRecord}.
|
|
90
73
|
*/
|
|
91
74
|
export type ObservationID = Tagged<string, "GeographicObservationID">
|
|
92
75
|
|
|
93
76
|
/**
|
|
94
|
-
*
|
|
77
|
+
* Brands a string as an {@link ObservationID} without changing it.
|
|
95
78
|
*/
|
|
96
79
|
export function toObservationID(id: string): ObservationID {
|
|
97
80
|
return id as ObservationID
|
|
98
81
|
}
|
|
99
82
|
|
|
100
83
|
/**
|
|
101
|
-
* The identifier of one {@link DerivedFactRecord}.
|
|
84
|
+
* The branded identifier of one {@link DerivedFactRecord}.
|
|
102
85
|
*/
|
|
103
86
|
export type DerivedFactID = Tagged<string, "GeographicDerivedFactID">
|
|
104
87
|
|
|
105
88
|
/**
|
|
106
|
-
*
|
|
89
|
+
* Brands a string as a {@link DerivedFactID} without changing it.
|
|
107
90
|
*/
|
|
108
91
|
export function toDerivedFactID(id: string): DerivedFactID {
|
|
109
92
|
return id as DerivedFactID
|
|
110
93
|
}
|
|
111
94
|
|
|
112
95
|
/**
|
|
113
|
-
* The
|
|
114
|
-
*
|
|
115
|
-
* which is the review cost the program accepted in exchange for refusing speculative upper-ontology breadth.
|
|
96
|
+
* The concept kinds.
|
|
97
|
+
* A new kind requires a reviewed schema revision.
|
|
116
98
|
*/
|
|
117
99
|
export const ConceptKind = {
|
|
118
100
|
/**
|
|
119
|
-
* A geographic
|
|
101
|
+
* A geographic area or point where other things are located.
|
|
120
102
|
*/
|
|
121
103
|
Place: "place",
|
|
122
104
|
/**
|
|
123
|
-
* A class of premises a person can
|
|
105
|
+
* A class of premises that a person can visit, such as `pharmacy`.
|
|
124
106
|
*/
|
|
125
107
|
Establishment: "establishment",
|
|
126
108
|
/**
|
|
127
|
-
*
|
|
128
|
-
* are owned by #1683.
|
|
109
|
+
* A kind of activity, such as `obtain_medication`.
|
|
129
110
|
*/
|
|
130
111
|
Activity: "activity",
|
|
131
112
|
} as const
|
|
132
113
|
|
|
114
|
+
/**
|
|
115
|
+
* A {@link ConceptKind} value.
|
|
116
|
+
*/
|
|
133
117
|
export type ConceptKind = (typeof ConceptKind)[keyof typeof ConceptKind]
|
|
134
118
|
|
|
135
119
|
/**
|
|
136
|
-
* How strongly an assertion,
|
|
120
|
+
* How strongly an assertion, observation or derived fact claims that its proposition holds.
|
|
137
121
|
*
|
|
138
|
-
* The
|
|
139
|
-
*
|
|
122
|
+
* The values are ordinal in meaning, but the module exports no order
|
|
123
|
+
* so callers do not convert them into weights.
|
|
140
124
|
*/
|
|
141
125
|
export const Modality = {
|
|
142
126
|
/**
|
|
143
|
-
* Holds in every instance
|
|
127
|
+
* Holds in every instance.
|
|
128
|
+
* A counterexample falsifies the record.
|
|
144
129
|
*/
|
|
145
130
|
Necessary: "necessary",
|
|
146
131
|
/**
|
|
@@ -151,13 +136,16 @@ export const Modality = {
|
|
|
151
136
|
Expected: "expected",
|
|
152
137
|
WeaklyExpected: "weakly_expected",
|
|
153
138
|
/**
|
|
154
|
-
* Consistent with the concept
|
|
139
|
+
* Consistent with the concept, with no claim about frequency.
|
|
155
140
|
*/
|
|
156
141
|
Possible: "possible",
|
|
157
142
|
Unusual: "unusual",
|
|
158
143
|
StronglyUnusual: "strongly_unusual",
|
|
159
144
|
} as const
|
|
160
145
|
|
|
146
|
+
/**
|
|
147
|
+
* A {@link Modality} value.
|
|
148
|
+
*/
|
|
161
149
|
export type Modality = (typeof Modality)[keyof typeof Modality]
|
|
162
150
|
|
|
163
151
|
/**
|
|
@@ -174,67 +162,81 @@ export const RelationSemantics = {
|
|
|
174
162
|
Defeasible: "defeasible",
|
|
175
163
|
} as const
|
|
176
164
|
|
|
165
|
+
/**
|
|
166
|
+
* A {@link RelationSemantics} value.
|
|
167
|
+
*/
|
|
177
168
|
export type RelationSemantics = (typeof RelationSemantics)[keyof typeof RelationSemantics]
|
|
178
169
|
|
|
179
170
|
/**
|
|
180
|
-
* A concept's
|
|
181
|
-
*
|
|
171
|
+
* A concept's review status.
|
|
172
|
+
* It tells consumers whether to use the record.
|
|
182
173
|
*/
|
|
183
174
|
export const ConceptStatus = {
|
|
184
175
|
/**
|
|
185
|
-
* Authored
|
|
176
|
+
* Authored but not yet reviewed.
|
|
186
177
|
*/
|
|
187
178
|
Draft: "draft",
|
|
188
179
|
Active: "active",
|
|
189
180
|
/**
|
|
190
|
-
*
|
|
181
|
+
* Retained so that existing references resolve.
|
|
182
|
+
* New references should avoid it.
|
|
191
183
|
*/
|
|
192
184
|
Deprecated: "deprecated",
|
|
193
185
|
} as const
|
|
194
186
|
|
|
187
|
+
/**
|
|
188
|
+
* A {@link ConceptStatus} value.
|
|
189
|
+
*/
|
|
195
190
|
export type ConceptStatus = (typeof ConceptStatus)[keyof typeof ConceptStatus]
|
|
196
191
|
|
|
197
192
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
193
|
+
* External vocabularies that concepts can map to.
|
|
194
|
+
*
|
|
195
|
+
* {@link ExternalMappingRecord} assumes a single vocabulary.
|
|
196
|
+
* A second vocabulary would require a union discriminated on `vocabulary`.
|
|
200
197
|
*/
|
|
201
198
|
export const ExternalVocabulary = {
|
|
202
199
|
/**
|
|
203
|
-
* `@mailwoman/poi-taxonomy
|
|
200
|
+
* Category identifiers from `@mailwoman/poi-taxonomy`.
|
|
204
201
|
*/
|
|
205
202
|
POITaxonomy: "poi-taxonomy",
|
|
206
203
|
} as const
|
|
207
204
|
|
|
205
|
+
/**
|
|
206
|
+
* An {@link ExternalVocabulary} value.
|
|
207
|
+
*/
|
|
208
208
|
export type ExternalVocabulary = (typeof ExternalVocabulary)[keyof typeof ExternalVocabulary]
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
|
-
*
|
|
212
|
-
* source
|
|
211
|
+
* The origin of a record.
|
|
212
|
+
* `source` must be non-empty.
|
|
213
213
|
*/
|
|
214
214
|
export interface SourceProvenance {
|
|
215
215
|
/**
|
|
216
|
-
* The
|
|
216
|
+
* The authority, dataset, publication or curator, such as `overture-places` or `mailwoman-curated`.
|
|
217
217
|
*/
|
|
218
218
|
source: string
|
|
219
219
|
/**
|
|
220
|
-
* The source's
|
|
220
|
+
* The source's version or release string, such as an Overture release.
|
|
221
221
|
*/
|
|
222
222
|
sourceVersion?: string
|
|
223
223
|
/**
|
|
224
|
-
* The identifier of the specific record
|
|
224
|
+
* The identifier of the specific source record that supports the claim.
|
|
225
225
|
*/
|
|
226
226
|
sourceRecord?: string
|
|
227
227
|
sourceURL?: string
|
|
228
228
|
/**
|
|
229
|
-
* ISO 8601
|
|
229
|
+
* ISO 8601 date on which the record was authored, as `YYYY-MM-DD`.
|
|
230
230
|
*/
|
|
231
231
|
authoredAt?: string
|
|
232
232
|
notes?: string
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
|
-
* One relation's definition.
|
|
237
|
-
*
|
|
236
|
+
* One relation's definition.
|
|
237
|
+
*
|
|
238
|
+
* A relation record defines meaning and the concept kinds allowed on each side.
|
|
239
|
+
* It makes no claim about any particular pair.
|
|
238
240
|
*/
|
|
239
241
|
export interface RelationRecord {
|
|
240
242
|
id: RelationID
|
|
@@ -251,16 +253,16 @@ export interface RelationRecord {
|
|
|
251
253
|
transitive: boolean
|
|
252
254
|
symmetric: boolean
|
|
253
255
|
/**
|
|
254
|
-
* The relation
|
|
255
|
-
*
|
|
256
|
+
* The relation that reads the same edge in the opposite direction.
|
|
257
|
+
*
|
|
258
|
+
* When set, it must exist and declare this relation as its own inverse.
|
|
256
259
|
*/
|
|
257
260
|
inverse?: RelationID
|
|
258
261
|
semantics: RelationSemantics
|
|
259
262
|
}
|
|
260
263
|
|
|
261
264
|
/**
|
|
262
|
-
* One
|
|
263
|
-
* what a dataset was observed to contain.
|
|
265
|
+
* One curated claim, stored on the concept that it describes.
|
|
264
266
|
*/
|
|
265
267
|
export interface RelationAssertion {
|
|
266
268
|
id: RuleID
|
|
@@ -268,15 +270,17 @@ export interface RelationAssertion {
|
|
|
268
270
|
target: ConceptID
|
|
269
271
|
modality: Modality
|
|
270
272
|
/**
|
|
271
|
-
* ISO 3166-1 alpha-2 codes the claim
|
|
272
|
-
*
|
|
273
|
+
* ISO 3166-1 alpha-2 codes that scope the claim.
|
|
274
|
+
*
|
|
275
|
+
* An absent list means the claim has no country scope.
|
|
276
|
+
* This is weaker than a claim about every country.
|
|
273
277
|
*/
|
|
274
278
|
countries?: string[]
|
|
275
279
|
provenance: SourceProvenance
|
|
276
280
|
}
|
|
277
281
|
|
|
278
282
|
/**
|
|
279
|
-
* One concept
|
|
283
|
+
* One concept and its authored assertions.
|
|
280
284
|
*/
|
|
281
285
|
export interface ConceptRecord {
|
|
282
286
|
id: ConceptID
|
|
@@ -284,7 +288,8 @@ export interface ConceptRecord {
|
|
|
284
288
|
description: string
|
|
285
289
|
kind: ConceptKind
|
|
286
290
|
/**
|
|
287
|
-
* Broader concepts this
|
|
291
|
+
* Broader concepts that this concept is a kind of.
|
|
292
|
+
* The list may be empty but must not form a cycle.
|
|
288
293
|
*/
|
|
289
294
|
isA: ConceptID[]
|
|
290
295
|
assertions: RelationAssertion[]
|
|
@@ -293,27 +298,27 @@ export interface ConceptRecord {
|
|
|
293
298
|
}
|
|
294
299
|
|
|
295
300
|
/**
|
|
296
|
-
*
|
|
297
|
-
*
|
|
301
|
+
* Maps an external vocabulary identifier to a concept.
|
|
302
|
+
* The mapping adds no semantics.
|
|
298
303
|
*/
|
|
299
304
|
export interface ExternalMappingRecord {
|
|
300
305
|
id: MappingID
|
|
301
306
|
concept: ConceptID
|
|
302
307
|
vocabulary: ExternalVocabulary
|
|
303
308
|
/**
|
|
304
|
-
* The identifier in the external vocabulary.
|
|
305
|
-
*
|
|
309
|
+
* The identifier in the external vocabulary.
|
|
310
|
+
*
|
|
311
|
+
* The type is `POICategoryID` because `poi-taxonomy` is the only external vocabulary.
|
|
306
312
|
*/
|
|
307
313
|
externalID: POICategoryID
|
|
308
314
|
provenance: SourceProvenance
|
|
309
315
|
}
|
|
310
316
|
|
|
311
317
|
/**
|
|
312
|
-
* A proposition
|
|
318
|
+
* A proposition stated by an identified external source, expressed in this model's vocabulary.
|
|
313
319
|
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* explicitly, never by the record sitting in a convenient place.
|
|
320
|
+
* Observations stay out of the concept table.
|
|
321
|
+
* An authored assertion requires an explicit curation decision with its own provenance.
|
|
317
322
|
*/
|
|
318
323
|
export interface SourceObservationRecord {
|
|
319
324
|
id: ObservationID
|
|
@@ -322,14 +327,14 @@ export interface SourceObservationRecord {
|
|
|
322
327
|
object: ConceptID
|
|
323
328
|
modality: Modality
|
|
324
329
|
/**
|
|
325
|
-
* ISO 3166-1 alpha-2 codes the source
|
|
330
|
+
* ISO 3166-1 alpha-2 codes that scope the source's statement.
|
|
326
331
|
*/
|
|
327
332
|
countries?: string[]
|
|
328
333
|
provenance: SourceProvenance
|
|
329
334
|
}
|
|
330
335
|
|
|
331
336
|
/**
|
|
332
|
-
*
|
|
337
|
+
* The table that a {@link DerivationInput} refers to.
|
|
333
338
|
*/
|
|
334
339
|
export const DerivationInputKind = {
|
|
335
340
|
Concept: "concept",
|
|
@@ -340,11 +345,14 @@ export const DerivationInputKind = {
|
|
|
340
345
|
DerivedFact: "derived_fact",
|
|
341
346
|
} as const
|
|
342
347
|
|
|
348
|
+
/**
|
|
349
|
+
* A {@link DerivationInputKind} value.
|
|
350
|
+
*/
|
|
343
351
|
export type DerivationInputKind = (typeof DerivationInputKind)[keyof typeof DerivationInputKind]
|
|
344
352
|
|
|
345
353
|
/**
|
|
346
|
-
* One record a derivation read.
|
|
347
|
-
*
|
|
354
|
+
* One record that a derivation read.
|
|
355
|
+
* The `kind` discriminant fixes the identifier's brand.
|
|
348
356
|
*/
|
|
349
357
|
export type DerivationInput =
|
|
350
358
|
| { kind: typeof DerivationInputKind.Concept; id: ConceptID }
|
|
@@ -355,21 +363,21 @@ export type DerivationInput =
|
|
|
355
363
|
| { kind: typeof DerivationInputKind.DerivedFact; id: DerivedFactID }
|
|
356
364
|
|
|
357
365
|
/**
|
|
358
|
-
* A fact
|
|
359
|
-
* validator refuses a fact whose derivation is unnamed or whose inputs do not resolve.
|
|
366
|
+
* A fact that an identified procedure computed from specific input records.
|
|
360
367
|
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
368
|
+
* The record has no provenance field.
|
|
369
|
+
* Its `derivation` and `inputs` serve as provenance.
|
|
370
|
+
* The validator rejects a fact whose inputs do not resolve.
|
|
364
371
|
*/
|
|
365
372
|
export interface DerivedFactRecord {
|
|
366
373
|
id: DerivedFactID
|
|
367
374
|
/**
|
|
368
|
-
* The deterministic procedure that produced this fact
|
|
375
|
+
* The name of the deterministic procedure that produced this fact.
|
|
369
376
|
*/
|
|
370
377
|
derivation: string
|
|
371
378
|
/**
|
|
372
|
-
* Every record the derivation read.
|
|
379
|
+
* Every record that the derivation read.
|
|
380
|
+
* The list must be non-empty and every input must resolve.
|
|
373
381
|
*/
|
|
374
382
|
inputs: DerivationInput[]
|
|
375
383
|
subject: ConceptID
|
|
@@ -380,15 +388,14 @@ export interface DerivedFactRecord {
|
|
|
380
388
|
}
|
|
381
389
|
|
|
382
390
|
/**
|
|
383
|
-
* One authored document
|
|
391
|
+
* One complete authored document.
|
|
384
392
|
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
* stand in for the second is the format where a dropped table reads as a world with no derived facts in it.
|
|
393
|
+
* Every field is required, including `derivedFacts`, so that a dropped table fails
|
|
394
|
+
* validation instead of reading as an empty one.
|
|
388
395
|
*/
|
|
389
396
|
export interface GeographicModelDocument {
|
|
390
397
|
/**
|
|
391
|
-
* The document's
|
|
398
|
+
* The document's data version.
|
|
392
399
|
*/
|
|
393
400
|
version: string
|
|
394
401
|
relations: RelationRecord[]
|
package/lib/validate.ts
CHANGED
|
@@ -3,28 +3,11 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Deterministically validate a {@link GeographicModelDocument} without I/O or external schema libraries. Report all
|
|
7
|
+
* issues in traversal order and return either the complete valid document or the full issue list.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* also never returns a partial document: either the input satisfies every rule and is returned
|
|
12
|
-
* whole, or nothing is returned and every issue is named with the path it was found at. There is no
|
|
13
|
-
* third answer in which some records were dropped quietly, because a dropped record is
|
|
14
|
-
* indistinguishable from a world that does not contain it.
|
|
15
|
-
*
|
|
16
|
-
* Two passes, in this order, and both always run:
|
|
17
|
-
*
|
|
18
|
-
* 1. **Shape.** Field presence, field types, closed-vocabulary membership, and unknown keys. An
|
|
19
|
-
* unknown key whose name announces ranking policy — a boost, a penalty, a weight, a rank, a
|
|
20
|
-
* score — is reported under its own code rather than as an anonymous stray field, because that
|
|
21
|
-
* is the one authoring mistake this package exists to refuse.
|
|
22
|
-
* 2. **Whole-table references.** Duplicate identifiers, `isA` self-reference and cycles, relation
|
|
23
|
-
* and concept resolution, relation domain and range kinds, inverse reciprocity, and derivation
|
|
24
|
-
* inputs. These are answerable only once every table has been read, which is why they are
|
|
25
|
-
* reported after the shape issues rather than interleaved with them.
|
|
26
|
-
*
|
|
27
|
-
* Consumed by #1926's compiler, which validates before it emits, and by #1927's authored document.
|
|
9
|
+
* Validate record shapes first, then whole-table references such as duplicate IDs, inheritance cycles, relation
|
|
10
|
+
* constraints, inverses and derivation inputs.
|
|
28
11
|
*/
|
|
29
12
|
|
|
30
13
|
import { isPlainObject } from "@mailwoman/core/objects"
|
|
@@ -52,8 +35,6 @@ import {
|
|
|
52
35
|
} from "#validation/issues"
|
|
53
36
|
import { checkReferences } from "#validation/references"
|
|
54
37
|
|
|
55
|
-
export { type ValidationIssue, ValidationIssueCode } from "#validation/issues"
|
|
56
|
-
|
|
57
38
|
/**
|
|
58
39
|
* The whole document, or every reason it is not one.
|
|
59
40
|
*/
|
|
@@ -96,54 +77,53 @@ const DERIVED_FACT_FIELDS = [
|
|
|
96
77
|
const DERIVATION_INPUT_FIELDS = ["kind", "id"] as const
|
|
97
78
|
|
|
98
79
|
/**
|
|
99
|
-
* ISO 3166-1 alpha-2
|
|
100
|
-
* a claim to a country nothing else in the system names.
|
|
80
|
+
* Uppercase ISO 3166-1 alpha-2 country code.
|
|
101
81
|
*/
|
|
102
82
|
const COUNTRY_PATTERN = /^[A-Z]{2}$/
|
|
103
83
|
|
|
104
84
|
export interface AssertionView {
|
|
105
85
|
path: string
|
|
106
|
-
id
|
|
107
|
-
relation
|
|
108
|
-
target
|
|
86
|
+
id: string | null
|
|
87
|
+
relation: string | null
|
|
88
|
+
target: string | null
|
|
109
89
|
}
|
|
110
90
|
|
|
111
91
|
export interface ConceptView {
|
|
112
92
|
path: string
|
|
113
|
-
id
|
|
114
|
-
kind
|
|
115
|
-
isA
|
|
93
|
+
id: string | null
|
|
94
|
+
kind: ConceptKind | null
|
|
95
|
+
isA: string[] | null
|
|
116
96
|
assertions: AssertionView[]
|
|
117
97
|
}
|
|
118
98
|
|
|
119
99
|
export interface RelationView {
|
|
120
100
|
path: string
|
|
121
|
-
id
|
|
122
|
-
domainKinds
|
|
123
|
-
rangeKinds
|
|
124
|
-
transitive
|
|
125
|
-
symmetric
|
|
126
|
-
inverse
|
|
101
|
+
id: string | null
|
|
102
|
+
domainKinds: ConceptKind[] | null
|
|
103
|
+
rangeKinds: ConceptKind[] | null
|
|
104
|
+
transitive: boolean | null
|
|
105
|
+
symmetric: boolean | null
|
|
106
|
+
inverse: string | null
|
|
127
107
|
}
|
|
128
108
|
|
|
129
109
|
export interface MappingView {
|
|
130
110
|
path: string
|
|
131
|
-
id
|
|
132
|
-
concept
|
|
111
|
+
id: string | null
|
|
112
|
+
concept: string | null
|
|
133
113
|
}
|
|
134
114
|
|
|
135
115
|
export interface TripleView {
|
|
136
116
|
path: string
|
|
137
|
-
id
|
|
138
|
-
subject
|
|
139
|
-
relation
|
|
140
|
-
object
|
|
117
|
+
id: string | null
|
|
118
|
+
subject: string | null
|
|
119
|
+
relation: string | null
|
|
120
|
+
object: string | null
|
|
141
121
|
}
|
|
142
122
|
|
|
143
123
|
export interface DerivationInputView {
|
|
144
124
|
path: string
|
|
145
|
-
kind
|
|
146
|
-
id
|
|
125
|
+
kind: DerivationInputKind | null
|
|
126
|
+
id: string | null
|
|
147
127
|
}
|
|
148
128
|
|
|
149
129
|
export interface DerivedFactView extends TripleView {
|
|
@@ -209,7 +189,7 @@ function readProvenance(issues: ValidationIssue[], path: string, container: Reco
|
|
|
209
189
|
}
|
|
210
190
|
|
|
211
191
|
/**
|
|
212
|
-
* Read
|
|
192
|
+
* Read required label and description fields.
|
|
213
193
|
*/
|
|
214
194
|
function readNarration(issues: ValidationIssue[], path: string, value: Record<string, unknown>): void {
|
|
215
195
|
readString(issues, path, value, "label", true)
|
|
@@ -450,29 +430,26 @@ function collectIssues(input: unknown): ValidationIssue[] {
|
|
|
450
430
|
/**
|
|
451
431
|
* Validate an authored geographic-model document.
|
|
452
432
|
*
|
|
453
|
-
*
|
|
454
|
-
* in table order, then whole-table reference issues — so two runs over the same input produce the same list.
|
|
433
|
+
* @returns The full document or every validation issue in deterministic order.
|
|
455
434
|
*/
|
|
456
435
|
export function validateGeographicModelDocument(input: unknown): ValidationResult {
|
|
457
436
|
const issues = collectIssues(input)
|
|
458
437
|
|
|
459
438
|
if (issues.length) return { ok: false, issues }
|
|
460
439
|
|
|
461
|
-
//
|
|
462
|
-
// where `input` is still `unknown`, is what makes it a single step rather than a cast through `unknown`.
|
|
440
|
+
// Return the original document without rewriting it.
|
|
463
441
|
return { ok: true, document: input as GeographicModelDocument }
|
|
464
442
|
}
|
|
465
443
|
|
|
466
444
|
/**
|
|
467
|
-
*
|
|
445
|
+
* Format issues as `path: message [code]`, preserving their order.
|
|
468
446
|
*/
|
|
469
447
|
export function formatValidationIssues(issues: readonly ValidationIssue[]): string {
|
|
470
448
|
return issues.map((issue) => `${issue.path}: ${issue.message} [${issue.code}]`).join("\n")
|
|
471
449
|
}
|
|
472
450
|
|
|
473
451
|
/**
|
|
474
|
-
*
|
|
475
|
-
* message, so a caller that only ever prints `error.message` still sees every violation.
|
|
452
|
+
* Validation error carrying the complete issue list in both `issues` and `message`.
|
|
476
453
|
*/
|
|
477
454
|
export class GeographicModelValidationError extends Error {
|
|
478
455
|
readonly issues: readonly ValidationIssue[]
|
|
@@ -486,9 +463,8 @@ export class GeographicModelValidationError extends Error {
|
|
|
486
463
|
}
|
|
487
464
|
|
|
488
465
|
/**
|
|
489
|
-
* Validate and return
|
|
490
|
-
*
|
|
491
|
-
* step, a test.
|
|
466
|
+
* Validate and return the document, or throw with all issues.
|
|
467
|
+
* Use when partial results are not meaningful.
|
|
492
468
|
*/
|
|
493
469
|
export function parseGeographicModelDocument(input: unknown): GeographicModelDocument {
|
|
494
470
|
const result = validateGeographicModelDocument(input)
|