@mailwoman/corpus 10.5.0 → 10.7.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 (232) hide show
  1. package/data/PROVENANCE.md +24 -0
  2. package/data/address-source-register.json +18948 -8757
  3. package/data/corpus-source-inputs.json +4756 -0
  4. package/data/license-decisions.json +7260 -0
  5. package/data/licenses/whosonfirst-licenses.md +3 -2
  6. package/data/reviewed-ve-postcode-tuples.json +5 -72
  7. package/data/source-additions.json +2450 -0
  8. package/data/source-resolutions.json +87 -0
  9. package/data/training-manifests/v0.7.10-gb-county.effective.json +2335 -0
  10. package/data/training-manifests/v0.7.10-gb-county.json +1132 -0
  11. package/data/training-manifests/v0.7.2-address-systems.effective.json +3 -2
  12. package/data/training-manifests/v0.7.7-no-ppd.json +550 -0
  13. package/data/training-manifests/v0.7.8-rights-clean.json +538 -0
  14. package/data/training-manifests/v0.7.9-country-gap.json +1132 -0
  15. package/data/us-situs-sources.json +128 -0
  16. package/lib/adapters/cordis/adapter.ts +1 -1
  17. package/lib/adapters/libpostal-dictionary.ts +23 -3
  18. package/lib/adapters/osm/adapter.ts +42 -11
  19. package/lib/adapters.ts +14 -0
  20. package/lib/az/adapters/opendata/adapter.ts +389 -0
  21. package/lib/bs/adapters/gbpa/adapter.ts +248 -0
  22. package/lib/build/types.ts +16 -0
  23. package/lib/build.ts +21 -0
  24. package/lib/ee/adapters/ads/adapter.ts +18 -0
  25. package/lib/fo/adapters/adressur/adapter.ts +242 -0
  26. package/lib/gb/adapters/os-open/adapter.ts +14 -2
  27. package/lib/gb/county.ts +59 -0
  28. package/lib/gb/os-open/{open-names.ts → open/names.ts} +41 -0
  29. package/lib/gu/adapters/bsp/adapter.ts +434 -0
  30. package/lib/im/adapters/land-registry/adapter.ts +340 -0
  31. package/lib/international/recipes/locale.ts +164 -19
  32. package/lib/li/adapters/llv/adapter.ts +246 -0
  33. package/lib/recipes/bare/postcode.ts +10 -34
  34. package/lib/recipes/country-balanced.ts +95 -78
  35. package/lib/recipes/intersection.ts +22 -30
  36. package/lib/recipes/po/box/cedex/recipe.ts +8 -5
  37. package/lib/recipes/po/box/cedex/sources.ts +2 -23
  38. package/lib/recipes/scaffold.ts +28 -3
  39. package/lib/recipes/sources.ts +112 -1
  40. package/lib/recipes/street/affix.ts +17 -29
  41. package/lib/recipes/sub/venue/context.ts +33 -9
  42. package/lib/recipes/sub/venue/sources.ts +54 -13
  43. package/lib/recipes/sub/venue.ts +68 -23
  44. package/lib/recipes/unit.ts +20 -7
  45. package/lib/registers.ts +33 -0
  46. package/lib/source-register/corpus-inputs.ts +528 -0
  47. package/lib/source-register/effective-manifest.ts +37 -5
  48. package/lib/source-register/types.ts +20 -0
  49. package/lib/south-asia/recipes/register.ts +1 -1
  50. package/lib/tools/overlay/manifest.ts +109 -1
  51. package/lib/tools/source-register/build.ts +47 -1
  52. package/lib/tools/variety-sample.ts +633 -0
  53. package/lib/us/adapters/usgov/nad/adapter.ts +39 -10
  54. package/lib/us/recipes/skeleton-sources.ts +156 -0
  55. package/lib/us/tools/nad-skeletons.ts +395 -0
  56. package/lib/uy/adapters/ide/adapter.ts +275 -0
  57. package/lib/ve/recipes/reviewed-postcode-tail.ts +29 -6
  58. package/out/adapters/cordis/adapter.d.ts +1 -1
  59. package/out/adapters/cordis/adapter.js +1 -1
  60. package/out/adapters/libpostal-dictionary.d.ts +9 -0
  61. package/out/adapters/libpostal-dictionary.d.ts.map +1 -1
  62. package/out/adapters/libpostal-dictionary.js +19 -3
  63. package/out/adapters/libpostal-dictionary.js.map +1 -1
  64. package/out/adapters/osm/adapter.d.ts +12 -1
  65. package/out/adapters/osm/adapter.d.ts.map +1 -1
  66. package/out/adapters/osm/adapter.js +30 -13
  67. package/out/adapters/osm/adapter.js.map +1 -1
  68. package/out/adapters.d.ts.map +1 -1
  69. package/out/adapters.js +14 -0
  70. package/out/adapters.js.map +1 -1
  71. package/out/az/adapters/opendata/adapter.d.ts +88 -0
  72. package/out/az/adapters/opendata/adapter.d.ts.map +1 -0
  73. package/out/az/adapters/opendata/adapter.js +282 -0
  74. package/out/az/adapters/opendata/adapter.js.map +1 -0
  75. package/out/bs/adapters/gbpa/adapter.d.ts +64 -0
  76. package/out/bs/adapters/gbpa/adapter.d.ts.map +1 -0
  77. package/out/bs/adapters/gbpa/adapter.js +187 -0
  78. package/out/bs/adapters/gbpa/adapter.js.map +1 -0
  79. package/out/build/types.d.ts +15 -0
  80. package/out/build/types.d.ts.map +1 -1
  81. package/out/build/types.js.map +1 -1
  82. package/out/build.d.ts.map +1 -1
  83. package/out/build.js +17 -0
  84. package/out/build.js.map +1 -1
  85. package/out/ee/adapters/ads/adapter.d.ts +6 -0
  86. package/out/ee/adapters/ads/adapter.d.ts.map +1 -1
  87. package/out/ee/adapters/ads/adapter.js +16 -0
  88. package/out/ee/adapters/ads/adapter.js.map +1 -1
  89. package/out/fo/adapters/adressur/adapter.d.ts +63 -0
  90. package/out/fo/adapters/adressur/adapter.d.ts.map +1 -0
  91. package/out/fo/adapters/adressur/adapter.js +177 -0
  92. package/out/fo/adapters/adressur/adapter.js.map +1 -0
  93. package/out/gb/adapters/os-open/adapter.d.ts +10 -2
  94. package/out/gb/adapters/os-open/adapter.d.ts.map +1 -1
  95. package/out/gb/adapters/os-open/adapter.js +13 -2
  96. package/out/gb/adapters/os-open/adapter.js.map +1 -1
  97. package/out/gb/county.d.ts +30 -0
  98. package/out/gb/county.d.ts.map +1 -0
  99. package/out/gb/county.js +51 -0
  100. package/out/gb/county.js.map +1 -0
  101. package/out/gb/os-open/{open-names.d.ts → open/names.d.ts} +18 -1
  102. package/out/gb/os-open/open/names.d.ts.map +1 -0
  103. package/out/gb/os-open/{open-names.js → open/names.js} +20 -1
  104. package/out/gb/os-open/open/names.js.map +1 -0
  105. package/out/gb/os-open/{open-roads.d.ts → open/roads.d.ts} +1 -1
  106. package/out/gb/os-open/open/roads.d.ts.map +1 -0
  107. package/out/gb/os-open/{open-roads.js → open/roads.js} +1 -1
  108. package/out/gb/os-open/open/roads.js.map +1 -0
  109. package/out/gu/adapters/bsp/adapter.d.ts +83 -0
  110. package/out/gu/adapters/bsp/adapter.d.ts.map +1 -0
  111. package/out/gu/adapters/bsp/adapter.js +346 -0
  112. package/out/gu/adapters/bsp/adapter.js.map +1 -0
  113. package/out/im/adapters/land-registry/adapter.d.ts +68 -0
  114. package/out/im/adapters/land-registry/adapter.d.ts.map +1 -0
  115. package/out/im/adapters/land-registry/adapter.js +261 -0
  116. package/out/im/adapters/land-registry/adapter.js.map +1 -0
  117. package/out/international/recipes/locale.d.ts +24 -4
  118. package/out/international/recipes/locale.d.ts.map +1 -1
  119. package/out/international/recipes/locale.js +107 -15
  120. package/out/international/recipes/locale.js.map +1 -1
  121. package/out/li/adapters/llv/adapter.d.ts +69 -0
  122. package/out/li/adapters/llv/adapter.d.ts.map +1 -0
  123. package/out/li/adapters/llv/adapter.js +181 -0
  124. package/out/li/adapters/llv/adapter.js.map +1 -0
  125. package/out/recipes/bare/postcode.d.ts.map +1 -1
  126. package/out/recipes/bare/postcode.js +9 -30
  127. package/out/recipes/bare/postcode.js.map +1 -1
  128. package/out/recipes/country-balanced.d.ts.map +1 -1
  129. package/out/recipes/country-balanced.js +82 -73
  130. package/out/recipes/country-balanced.js.map +1 -1
  131. package/out/recipes/intersection.d.ts +3 -2
  132. package/out/recipes/intersection.d.ts.map +1 -1
  133. package/out/recipes/intersection.js +20 -26
  134. package/out/recipes/intersection.js.map +1 -1
  135. package/out/recipes/po/box/cedex/recipe.d.ts.map +1 -1
  136. package/out/recipes/po/box/cedex/recipe.js +5 -4
  137. package/out/recipes/po/box/cedex/recipe.js.map +1 -1
  138. package/out/recipes/po/box/cedex/sources.d.ts +2 -45
  139. package/out/recipes/po/box/cedex/sources.d.ts.map +1 -1
  140. package/out/recipes/po/box/cedex/sources.js +0 -20
  141. package/out/recipes/po/box/cedex/sources.js.map +1 -1
  142. package/out/recipes/scaffold.d.ts +15 -2
  143. package/out/recipes/scaffold.d.ts.map +1 -1
  144. package/out/recipes/scaffold.js +21 -1
  145. package/out/recipes/scaffold.js.map +1 -1
  146. package/out/recipes/sources.d.ts.map +1 -1
  147. package/out/recipes/sources.js +112 -1
  148. package/out/recipes/sources.js.map +1 -1
  149. package/out/recipes/street/affix.d.ts.map +1 -1
  150. package/out/recipes/street/affix.js +8 -20
  151. package/out/recipes/street/affix.js.map +1 -1
  152. package/out/recipes/sub/venue/context.d.ts +14 -3
  153. package/out/recipes/sub/venue/context.d.ts.map +1 -1
  154. package/out/recipes/sub/venue/context.js +29 -9
  155. package/out/recipes/sub/venue/context.js.map +1 -1
  156. package/out/recipes/sub/venue/sources.d.ts +41 -8
  157. package/out/recipes/sub/venue/sources.d.ts.map +1 -1
  158. package/out/recipes/sub/venue/sources.js +26 -7
  159. package/out/recipes/sub/venue/sources.js.map +1 -1
  160. package/out/recipes/sub/venue.d.ts.map +1 -1
  161. package/out/recipes/sub/venue.js +35 -14
  162. package/out/recipes/sub/venue.js.map +1 -1
  163. package/out/recipes/unit.d.ts +4 -0
  164. package/out/recipes/unit.d.ts.map +1 -1
  165. package/out/recipes/unit.js +7 -6
  166. package/out/recipes/unit.js.map +1 -1
  167. package/out/registers.d.ts +33 -0
  168. package/out/registers.d.ts.map +1 -1
  169. package/out/registers.js +33 -0
  170. package/out/registers.js.map +1 -1
  171. package/out/source-register/corpus-inputs.d.ts +240 -0
  172. package/out/source-register/corpus-inputs.d.ts.map +1 -0
  173. package/out/source-register/corpus-inputs.js +275 -0
  174. package/out/source-register/corpus-inputs.js.map +1 -0
  175. package/out/source-register/effective-manifest.d.ts +2 -0
  176. package/out/source-register/effective-manifest.d.ts.map +1 -1
  177. package/out/source-register/effective-manifest.js +33 -4
  178. package/out/source-register/effective-manifest.js.map +1 -1
  179. package/out/source-register/types.d.ts +21 -1
  180. package/out/source-register/types.d.ts.map +1 -1
  181. package/out/source-register/types.js +20 -0
  182. package/out/source-register/types.js.map +1 -1
  183. package/out/south-asia/recipes/register.js +1 -1
  184. package/out/south-asia/recipes/register.js.map +1 -1
  185. package/out/tools/overlay/manifest.d.ts +7 -0
  186. package/out/tools/overlay/manifest.d.ts.map +1 -1
  187. package/out/tools/overlay/manifest.js +72 -1
  188. package/out/tools/overlay/manifest.js.map +1 -1
  189. package/out/tools/source-register/build.d.ts +9 -0
  190. package/out/tools/source-register/build.d.ts.map +1 -1
  191. package/out/tools/source-register/build.js +25 -3
  192. package/out/tools/source-register/build.js.map +1 -1
  193. package/out/tools/variety-sample.d.ts +196 -0
  194. package/out/tools/variety-sample.d.ts.map +1 -0
  195. package/out/tools/variety-sample.js +417 -0
  196. package/out/tools/variety-sample.js.map +1 -0
  197. package/out/us/adapters/usgov/nad/adapter.d.ts +80 -0
  198. package/out/us/adapters/usgov/nad/adapter.d.ts.map +1 -1
  199. package/out/us/adapters/usgov/nad/adapter.js +27 -8
  200. package/out/us/adapters/usgov/nad/adapter.js.map +1 -1
  201. package/out/us/recipes/skeleton-sources.d.ts +92 -0
  202. package/out/us/recipes/skeleton-sources.d.ts.map +1 -0
  203. package/out/us/recipes/skeleton-sources.js +121 -0
  204. package/out/us/recipes/skeleton-sources.js.map +1 -0
  205. package/out/us/tools/nad-skeletons.d.ts +120 -0
  206. package/out/us/tools/nad-skeletons.d.ts.map +1 -0
  207. package/out/us/tools/nad-skeletons.js +262 -0
  208. package/out/us/tools/nad-skeletons.js.map +1 -0
  209. package/out/uy/adapters/ide/adapter.d.ts +64 -0
  210. package/out/uy/adapters/ide/adapter.d.ts.map +1 -0
  211. package/out/uy/adapters/ide/adapter.js +206 -0
  212. package/out/uy/adapters/ide/adapter.js.map +1 -0
  213. package/out/ve/recipes/reviewed-postcode-tail.d.ts +6 -2
  214. package/out/ve/recipes/reviewed-postcode-tail.d.ts.map +1 -1
  215. package/out/ve/recipes/reviewed-postcode-tail.js +16 -5
  216. package/out/ve/recipes/reviewed-postcode-tail.js.map +1 -1
  217. package/package.json +14 -14
  218. package/lib/recipes/unit/sources.ts +0 -44
  219. package/lib/tools/fetch/ppd.ts +0 -121
  220. package/out/gb/os-open/open-names.d.ts.map +0 -1
  221. package/out/gb/os-open/open-names.js.map +0 -1
  222. package/out/gb/os-open/open-roads.d.ts.map +0 -1
  223. package/out/gb/os-open/open-roads.js.map +0 -1
  224. package/out/recipes/unit/sources.d.ts +0 -28
  225. package/out/recipes/unit/sources.d.ts.map +0 -1
  226. package/out/recipes/unit/sources.js +0 -30
  227. package/out/recipes/unit/sources.js.map +0 -1
  228. package/out/tools/fetch/ppd.d.ts +0 -15
  229. package/out/tools/fetch/ppd.d.ts.map +0 -1
  230. package/out/tools/fetch/ppd.js +0 -86
  231. package/out/tools/fetch/ppd.js.map +0 -1
  232. /package/lib/gb/os-open/{open-roads.ts → open/roads.ts} +0 -0
@@ -0,0 +1,262 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file Samples National Address Database rows into OpenAddresses-layout CSV skeletons, measures the address
6
+ * shapes each state's rows carry, and tallies county ZIP-to-city tables.
7
+ *
8
+ * The OA-skeleton recipes read a zipped CSV with OpenAddresses' columns, so a NAD sample written in that
9
+ * layout reaches them through the same reader as the OpenAddresses extracts they also read.
10
+ */
11
+ import { matchLeadingDesignator } from "@mailwoman/codex/us";
12
+ import { pathExists } from "@mailwoman/core/fs/readers/stat";
13
+ import { writeLocalBuffer, writeLocalJSONFile, writeLocalTextFile } from "@mailwoman/core/fs/writers";
14
+ import { sha256Hex } from "@mailwoman/core/hash";
15
+ import { tryParsingJSON } from "@mailwoman/core/json";
16
+ import { mulberry32 } from "@mailwoman/core/random";
17
+ import ADMZip from "adm-zip";
18
+ import { resolvePathBuilder } from "path-ts";
19
+ import { TextSpliterator } from "spliterator";
20
+ import { Globerator } from "spliterator/node/fs";
21
+ import { composeNADLocality, decomposeNADStreet, nadHouseNumber } from "#us/adapters/usgov/nad/adapter";
22
+ import { nadSkeletonFile, nadZipCityPath } from "#us/recipes/skeleton-sources";
23
+ /**
24
+ * The OpenAddresses columns a skeleton CSV carries, in OpenAddresses' order.
25
+ */
26
+ export const NAD_SKELETON_COLUMNS = [
27
+ "NUMBER",
28
+ "STREET",
29
+ "UNIT",
30
+ "CITY",
31
+ "DISTRICT",
32
+ "REGION",
33
+ "POSTCODE",
34
+ "ID",
35
+ ];
36
+ const RURAL_ROUTE = /^(RR|R R|RURAL ROUTE|HC|HCR|HIGHWAY CONTRACT)\b/i;
37
+ const text = (value) => (value == null ? "" : String(value).trim());
38
+ /**
39
+ * The unit identifier of a NAD row without its designator, as OpenAddresses' `UNIT` column mostly states it.
40
+ *
41
+ * NAD writes `APT 6`, `UNIT D` or a bare `200`; the recipes add their own designator in
42
+ * front of the identifier, so the designator NAD wrote is dropped rather than doubled.
43
+ */
44
+ export function nadUnitIdentifier(record) {
45
+ const unit = text(record.Unit) || text(record.Room);
46
+ if (!unit)
47
+ return "";
48
+ const designator = matchLeadingDesignator(unit);
49
+ const identifier = designator ? unit.slice(unit.indexOf(designator.matched) + designator.matched.length) : unit;
50
+ return identifier.replace(/^\s*#\s*/, "").trim();
51
+ }
52
+ /**
53
+ * Projects one NAD record onto the OpenAddresses columns, or returns null
54
+ * when it lacks a house number, street, locality, state or five-digit ZIP.
55
+ *
56
+ * A street whose name NAD publishes in capitals is written wholly in capitals,
57
+ * because NAD spells its structured types in title case (`East MAIN Street`)
58
+ * and a recipe would otherwise teach that mixed case.
59
+ */
60
+ export function nadSkeletonRow(record) {
61
+ const number = nadHouseNumber(record);
62
+ const street = decomposeNADStreet(record);
63
+ const city = composeNADLocality(record);
64
+ const region = text(record.State).toUpperCase();
65
+ const postcode = text(record.Zip_Code);
66
+ if (!number || !street?.full || !city || !region || !/^\d{5}$/.test(postcode))
67
+ return null;
68
+ const name = street.street ?? street.full;
69
+ const capitalized = /[A-Z]/.test(name) && name === name.toUpperCase();
70
+ return {
71
+ NUMBER: number,
72
+ STREET: capitalized ? street.full.toUpperCase() : street.full,
73
+ UNIT: nadUnitIdentifier(record),
74
+ CITY: city,
75
+ DISTRICT: text(record.County),
76
+ REGION: region,
77
+ POSTCODE: postcode,
78
+ ID: text(record.UUID) || text(record.OBJECTID),
79
+ };
80
+ }
81
+ function csvCell(value) {
82
+ return /[",\r\n]/.test(value) ? `"${value.replaceAll('"', '""')}"` : value;
83
+ }
84
+ const newTally = () => ({
85
+ usable: 0,
86
+ unit: 0,
87
+ types: new Set(),
88
+ zips: new Set(),
89
+ prefixType: 0,
90
+ numericName: 0,
91
+ postDirectional: 0,
92
+ ruralRoute: 0,
93
+ hyphenated: 0,
94
+ grid: 0,
95
+ });
96
+ function tallyShape(tally, record, row) {
97
+ tally.usable++;
98
+ if (row.UNIT) {
99
+ tally.unit++;
100
+ }
101
+ const postType = text(record.St_PosTyp).toUpperCase();
102
+ const preType = text(record.St_PreTyp).toUpperCase();
103
+ if (postType) {
104
+ tally.types.add(postType);
105
+ }
106
+ if (preType) {
107
+ tally.types.add(preType);
108
+ tally.prefixType++;
109
+ }
110
+ tally.zips.add(row.POSTCODE);
111
+ if (/^\d+$/.test(text(record.St_Name))) {
112
+ tally.numericName++;
113
+ }
114
+ if (text(record.St_PosDir)) {
115
+ tally.postDirectional++;
116
+ }
117
+ if (RURAL_ROUTE.test(row.STREET)) {
118
+ tally.ruralRoute++;
119
+ }
120
+ if (/^\d+-\d+/.test(row.NUMBER)) {
121
+ tally.hyphenated++;
122
+ }
123
+ if (/^[NSEW]\d/i.test(row.NUMBER)) {
124
+ tally.grid++;
125
+ }
126
+ }
127
+ function shapeOf(region, t) {
128
+ const share = (n) => (t.usable ? n / t.usable : 0);
129
+ return {
130
+ region,
131
+ usable: t.usable,
132
+ unitShare: share(t.unit),
133
+ streetTypes: t.types.size,
134
+ zips: t.zips.size,
135
+ prefixTypeShare: share(t.prefixType),
136
+ numericNameShare: share(t.numericName),
137
+ postDirectionalShare: share(t.postDirectional),
138
+ ruralRouteShare: share(t.ruralRoute),
139
+ hyphenatedNumberShare: share(t.hyphenated),
140
+ gridNumberShare: share(t.grid),
141
+ };
142
+ }
143
+ /**
144
+ * Reads every NAD page once, measures every state's address shapes,
145
+ * keeps a uniform reservoir sample of at most `cap` rows for each requested state,
146
+ * and tallies the majority city of each ZIP in each requested county.
147
+ *
148
+ * The sample is written in reservoir order rather than page order, so a reader that
149
+ * takes the first N rows still draws across the whole state.
150
+ * It refuses to replace a file that already exists.
151
+ */
152
+ export async function buildNADSkeletons(options) {
153
+ const { inputDirectory, cap, seed } = options;
154
+ const states = new Set(options.states.map((s) => s.toUpperCase()));
155
+ const outputs = [
156
+ ...[...states].map((region) => nadSkeletonFile(region).zip),
157
+ ...options.zipCityCounties.map(nadZipCityPath),
158
+ ];
159
+ for (const output of outputs) {
160
+ if (await pathExists(output))
161
+ throw new Error(`${output} already exists; a skeleton artifact is never replaced`);
162
+ }
163
+ const random = mulberry32(seed);
164
+ const tallies = new Map();
165
+ const reservoirs = new Map();
166
+ const zipCity = new Map();
167
+ const countyKey = (region, county) => `${region}|${county.toLowerCase()}`;
168
+ const wantedCounties = new Set(options.zipCityCounties.map((c) => countyKey(c.region, c.county)));
169
+ const pages = await Globerator.files("ndjson", { cwd: inputDirectory, absolute: false, recursive: false }).toSorted();
170
+ let pagesRead = 0;
171
+ for (const page of pages) {
172
+ for await (const line of TextSpliterator.fromAsync(resolvePathBuilder(inputDirectory, page))) {
173
+ if (!line)
174
+ continue;
175
+ const record = tryParsingJSON(line);
176
+ if (!record)
177
+ continue;
178
+ const row = nadSkeletonRow(record);
179
+ if (!row)
180
+ continue;
181
+ let tally = tallies.get(row.REGION);
182
+ if (!tally) {
183
+ tallies.set(row.REGION, (tally = newTally()));
184
+ }
185
+ tallyShape(tally, record, row);
186
+ if (states.has(row.REGION)) {
187
+ let reservoir = reservoirs.get(row.REGION);
188
+ if (!reservoir) {
189
+ reservoirs.set(row.REGION, (reservoir = { seen: 0, rows: [] }));
190
+ }
191
+ reservoir.seen++;
192
+ if (reservoir.rows.length < cap) {
193
+ reservoir.rows.push(row);
194
+ }
195
+ else {
196
+ const slot = Math.floor(random() * reservoir.seen);
197
+ if (slot < cap) {
198
+ reservoir.rows[slot] = row;
199
+ }
200
+ }
201
+ }
202
+ const key = countyKey(row.REGION, row.DISTRICT);
203
+ if (wantedCounties.has(key)) {
204
+ let byZip = zipCity.get(key);
205
+ if (!byZip) {
206
+ zipCity.set(key, (byZip = new Map()));
207
+ }
208
+ let byCity = byZip.get(row.POSTCODE);
209
+ if (!byCity) {
210
+ byZip.set(row.POSTCODE, (byCity = new Map()));
211
+ }
212
+ byCity.set(row.CITY, (byCity.get(row.CITY) ?? 0) + 1);
213
+ }
214
+ }
215
+ options.onPage?.(++pagesRead, pages.length);
216
+ }
217
+ const summary = {
218
+ pages: pages.length,
219
+ cap,
220
+ seed,
221
+ shapes: [...tallies].map(([region, t]) => shapeOf(region, t)).toSorted((a, b) => b.usable - a.usable),
222
+ samples: [],
223
+ zipCity: [],
224
+ };
225
+ for (const region of states) {
226
+ const reservoir = reservoirs.get(region);
227
+ if (!reservoir?.rows.length)
228
+ throw new Error(`NAD holds no usable row for ${region}`);
229
+ const { zip, csv } = nadSkeletonFile(region);
230
+ const body = [
231
+ NAD_SKELETON_COLUMNS.join(","),
232
+ ...reservoir.rows.map((row) => NAD_SKELETON_COLUMNS.map((column) => csvCell(row[column])).join(",")),
233
+ "",
234
+ ].join("\n");
235
+ const archive = new ADMZip();
236
+ archive.addFile(csv, Buffer.from(body, "utf8"));
237
+ await writeLocalBuffer(archive.toBuffer(), zip);
238
+ summary.samples.push({ region, rows: reservoir.rows.length, zip: String(zip), csv, csvSHA256: sha256Hex(body) });
239
+ }
240
+ for (const county of options.zipCityCounties) {
241
+ const byZip = zipCity.get(countyKey(county.region, county.county));
242
+ if (!byZip?.size)
243
+ throw new Error(`NAD holds no usable row for ${county.county} County, ${county.region}`);
244
+ const lines = ["ZIP,CITY,ROWS"];
245
+ for (const [zip, byCity] of [...byZip].toSorted(([a], [b]) => a.localeCompare(b))) {
246
+ const [city, rows] = [...byCity].toSorted((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0];
247
+ lines.push([zip, csvCell(city), String(rows)].join(","));
248
+ }
249
+ const body = `${lines.join("\n")}\n`;
250
+ const path = nadZipCityPath(county);
251
+ await writeLocalTextFile(body, path);
252
+ summary.zipCity.push({ ...county, zips: byZip.size, path: String(path), sha256: sha256Hex(body) });
253
+ }
254
+ return summary;
255
+ }
256
+ /**
257
+ * Writes the summary beside the skeletons it describes.
258
+ */
259
+ export function writeNADSkeletonSummary(summary, path) {
260
+ return writeLocalJSONFile(summary, path);
261
+ }
262
+ //# sourceMappingURL=nad-skeletons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nad-skeletons.js","sourceRoot":"","sources":["../../../lib/us/tools/nad-skeletons.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `ide-uy`: the national address points of Uruguay, `Direcciones Geográficas del Uruguay`, which the
7
+ * Infraestructura de Datos Espaciales (IDE) publishes on catalogodatos.gub.uy as one CSV per department.
8
+ *
9
+ * The CSVs are Windows-1252 (`ORDOÑEZ` reads `ORDO\xd1EZ`), and quote a street name that holds a
10
+ * nickname in doubled quotes (`"AVENIDA JOSE ""PEPE"" D' ELIA"`). Absent values read `N/A`.
11
+ *
12
+ * The adapter reads `nombre_via` as the street, `num_puerta` with `letra_puerta` as the house number,
13
+ * `codigo_postal` as the postcode, `localidad` as the locality, `departamento` as the region and
14
+ * `nombre_inmueble` as a venue. A census locality named `<DEPARTAMENTO> RURAL` names the department's rural
15
+ * remainder rather than a place, so such a row keeps the department and no locality. A street the register
16
+ * leaves unnamed carries a generated code (`UYARMQA_SN_012`) and is dropped, as is a point located only by a
17
+ * route kilometer, which has no door number to render.
18
+ *
19
+ * The IDE publishes the dataset under the Licencia de Datos Abiertos – Uruguay, which grants adaptation and
20
+ * commercial use and requires the note of origin to name the provider, the license and the dataset, and to
21
+ * mention changes. Every row records that license.
22
+ */
23
+ import { type AdapterOptions, type CanonicalRow, type CorpusAdapter } from "#types";
24
+ /**
25
+ * Registry id for this adapter, stamped into every row it emits.
26
+ */
27
+ export declare const IDE_UY_ADAPTER_ID = "ide-uy";
28
+ /**
29
+ * The license the IDE publishes the dataset under.
30
+ * It has no SPDX identifier.
31
+ */
32
+ export declare const IDE_UY_LICENSE = "LicenseRef-Licencia-Datos-Abiertos-Uruguay-0.1";
33
+ /**
34
+ * Every jurisdiction this adapter emits, checked against a caller's `--country`.
35
+ */
36
+ export declare const IDE_UY_COUNTRIES: readonly string[];
37
+ /**
38
+ * The columns the adapter reads from each department's CSV.
39
+ */
40
+ export interface IDEUruguayRecord {
41
+ codigo_postal: string;
42
+ codigo_via: string;
43
+ nombre_via: string;
44
+ num_puerta: string;
45
+ letra_puerta: string;
46
+ km: string;
47
+ nombre_inmueble: string;
48
+ localidad: string;
49
+ departamento: string;
50
+ latitud: string;
51
+ longitud: string;
52
+ }
53
+ /**
54
+ * Maps one record onto the components the corpus asserts, or returns null when the row is refused.
55
+ *
56
+ * Each refusal increments its reason in `dropped`.
57
+ */
58
+ export declare function componentsForIDERecord(record: IDEUruguayRecord, dropped?: Pick<AdapterOptions, "dropped">): CanonicalRow["components"] | null;
59
+ export declare function createIDEUruguayAdapter(): CorpusAdapter;
60
+ /**
61
+ * The configured adapter instance registered with the corpus builder.
62
+ */
63
+ export declare const ideUruguayAdapter: CorpusAdapter;
64
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../lib/uy/adapters/ide/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAeH,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,aAAa,EAGlB,MAAM,QAAQ,CAAA;AAEf;;GAEG;AACH,eAAO,MAAM,iBAAiB,WAAW,CAAA;AAEzC;;;GAGG;AACH,eAAO,MAAM,cAAc,mDAAmD,CAAA;AAE9E;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAAW,CAAA;AAEzD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;CAChB;AA2BD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACrC,MAAM,EAAE,gBAAgB,EACxB,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GACvC,YAAY,CAAC,YAAY,CAAC,GAAG,IAAI,CA6EnC;AAeD,wBAAgB,uBAAuB,IAAI,aAAa,CAkEvD;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,eAA4B,CAAA"}
@@ -0,0 +1,206 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `ide-uy`: the national address points of Uruguay, `Direcciones Geográficas del Uruguay`, which the
7
+ * Infraestructura de Datos Espaciales (IDE) publishes on catalogodatos.gub.uy as one CSV per department.
8
+ *
9
+ * The CSVs are Windows-1252 (`ORDOÑEZ` reads `ORDO\xd1EZ`), and quote a street name that holds a
10
+ * nickname in doubled quotes (`"AVENIDA JOSE ""PEPE"" D' ELIA"`). Absent values read `N/A`.
11
+ *
12
+ * The adapter reads `nombre_via` as the street, `num_puerta` with `letra_puerta` as the house number,
13
+ * `codigo_postal` as the postcode, `localidad` as the locality, `departamento` as the region and
14
+ * `nombre_inmueble` as a venue. A census locality named `<DEPARTAMENTO> RURAL` names the department's rural
15
+ * remainder rather than a place, so such a row keeps the department and no locality. A street the register
16
+ * leaves unnamed carries a generated code (`UYARMQA_SN_012`) and is dropped, as is a point located only by a
17
+ * route kilometer, which has no door number to render.
18
+ *
19
+ * The IDE publishes the dataset under the Licencia de Datos Abiertos – Uruguay, which grants adaptation and
20
+ * commercial use and requires the note of origin to name the provider, the license and the dataset, and to
21
+ * mention changes. Every row records that license.
22
+ */
23
+ import { formatAddressRow } from "@mailwoman/codex/address/format";
24
+ import { matchCountryPostcode } from "@mailwoman/codex/postcode/country-formats";
25
+ import { readLocalBuffer } from "@mailwoman/core/fs/readers";
26
+ import { tryStat } from "@mailwoman/core/fs/readers/stat";
27
+ import { decodeBytes } from "@mailwoman/core/fs/streams";
28
+ import { resolvePath } from "path-ts";
29
+ import { CSVSpliterator } from "spliterator";
30
+ import { Globerator } from "spliterator/node/fs";
31
+ import { UnsupportedCountryError } from "#adapters/errors";
32
+ import { stableSourceIDFromParts } from "#adapters/source-id";
33
+ import { composeHouseNumber } from "#adapters/street-line";
34
+ import { SourceRegister } from "#registers";
35
+ import { AddressRole, countDropped, SurfaceOrigin, } from "#types";
36
+ /**
37
+ * Registry id for this adapter, stamped into every row it emits.
38
+ */
39
+ export const IDE_UY_ADAPTER_ID = "ide-uy";
40
+ /**
41
+ * The license the IDE publishes the dataset under.
42
+ * It has no SPDX identifier.
43
+ */
44
+ export const IDE_UY_LICENSE = "LicenseRef-Licencia-Datos-Abiertos-Uruguay-0.1";
45
+ /**
46
+ * Every jurisdiction this adapter emits, checked against a caller's `--country`.
47
+ */
48
+ export const IDE_UY_COUNTRIES = ["UY"];
49
+ const ABSENT = "N/A";
50
+ /**
51
+ * A value, or null when the IDE writes it as absent.
52
+ */
53
+ function present(value) {
54
+ const trimmed = value?.trim() ?? "";
55
+ return trimmed && trimmed !== ABSENT ? trimmed : null;
56
+ }
57
+ /**
58
+ * The house-number suffixes `letra_puerta` holds that a door number is written with:
59
+ * `BIS` and a single letter.
60
+ *
61
+ * The two-letter values (`DU`, `CC`, `TR`) are codes the dataset's metadata
62
+ * does not define, so they are left off.
63
+ */
64
+ const LETTER_SUFFIX = /^(?:BIS|[A-Z])$/u;
65
+ /**
66
+ * A street the register leaves unnamed carries a generated code, such as `UYARMQA_SN_012`.
67
+ */
68
+ const GENERATED_STREET = /_SN_|^[A-Z0-9]+_[A-Z0-9_]+$/u;
69
+ /**
70
+ * Maps one record onto the components the corpus asserts, or returns null when the row is refused.
71
+ *
72
+ * Each refusal increments its reason in `dropped`.
73
+ */
74
+ export function componentsForIDERecord(record, dropped) {
75
+ const tally = dropped ?? {};
76
+ const street = present(record.nombre_via);
77
+ if (!street) {
78
+ countDropped(tally, "row:no-street");
79
+ return null;
80
+ }
81
+ if (GENERATED_STREET.test(street)) {
82
+ countDropped(tally, "row:street-generated-code");
83
+ return null;
84
+ }
85
+ const number = present(record.num_puerta);
86
+ if (!number) {
87
+ countDropped(tally, present(record.km) ? "row:route-kilometer-without-door-number" : "row:no-door-number");
88
+ return null;
89
+ }
90
+ const letter = present(record.letra_puerta);
91
+ const suffix = letter && LETTER_SUFFIX.test(letter) ? letter : "";
92
+ if (letter && !suffix) {
93
+ countDropped(tally, "component:letra_puerta:code");
94
+ }
95
+ const components = {
96
+ house_number: composeHouseNumber(number, suffix, " "),
97
+ street,
98
+ };
99
+ const venue = present(record.nombre_inmueble);
100
+ // A post office's distribution agent is listed under the agent's own name (`Agente` and a person's names),
101
+ // and a code such as `CH109` names no place, so neither becomes a venue.
102
+ if (venue && /^AGENTE\b/iu.test(venue)) {
103
+ countDropped(tally, "component:venue:names-a-person");
104
+ }
105
+ else if (venue && !/\p{L}{3}/u.test(venue)) {
106
+ countDropped(tally, "component:venue:code");
107
+ }
108
+ else if (venue) {
109
+ components.venue = venue;
110
+ }
111
+ const postcode = present(record.codigo_postal);
112
+ const matched = postcode ? matchCountryPostcode(postcode, "UY") : null;
113
+ if (matched) {
114
+ components.postcode = matched;
115
+ }
116
+ else if (postcode) {
117
+ countDropped(tally, "component:postcode:not-country-format");
118
+ }
119
+ const locality = present(record.localidad);
120
+ const region = present(record.departamento);
121
+ if (locality && !locality.endsWith(" RURAL")) {
122
+ components.locality = locality;
123
+ }
124
+ else if (locality) {
125
+ countDropped(tally, "component:locality:rural-remainder");
126
+ }
127
+ if (region && region !== components.locality) {
128
+ components.region = region;
129
+ }
130
+ if (!components.locality && !components.postcode) {
131
+ countDropped(tally, "row:no-locality-or-postcode");
132
+ return null;
133
+ }
134
+ return components;
135
+ }
136
+ /**
137
+ * The department CSVs an input names: the file itself, or every `.csv` in a directory, in name order.
138
+ */
139
+ async function departmentFiles(inputPath) {
140
+ const stat = await tryStat(inputPath);
141
+ if (!stat?.isDirectory())
142
+ return [inputPath];
143
+ const names = await Globerator.from("*.csv", { cwd: inputPath, absolute: false }).toSorted();
144
+ return names.map((name) => `${inputPath}/${name}`);
145
+ }
146
+ export function createIDEUruguayAdapter() {
147
+ return {
148
+ id: IDE_UY_ADAPTER_ID,
149
+ defaultLicense: IDE_UY_LICENSE,
150
+ addressRole: AddressRole.Premise,
151
+ register: SourceRegister.UruguayIDEDirecciones,
152
+ surface: SurfaceOrigin.Rendered,
153
+ description: "Direcciones Geográficas del Uruguay (IDE Uruguay): national address points, one CSV per department.",
154
+ async *rows(opts) {
155
+ if (opts.country && !IDE_UY_COUNTRIES.includes(opts.country)) {
156
+ throw new UnsupportedCountryError(IDE_UY_ADAPTER_ID, IDE_UY_COUNTRIES, opts.country);
157
+ }
158
+ let emitted = 0;
159
+ for (const file of await departmentFiles(resolvePath(opts.inputPath))) {
160
+ // The largest department file is 65 MB, and the publisher fixes the partition, so each is decoded whole.
161
+ const text = decodeBytes(await readLocalBuffer(file), "windows-1252");
162
+ const records = CSVSpliterator.from(Buffer.from(text, "utf8"), { normalizeKeys: false });
163
+ for (const record of records) {
164
+ if (opts.signal?.aborted)
165
+ return;
166
+ if (opts.limit !== undefined && emitted >= opts.limit)
167
+ return;
168
+ const components = componentsForIDERecord(record, opts);
169
+ if (!components)
170
+ continue;
171
+ const rendered = formatAddressRow(components, "UY", { singleLine: true });
172
+ if (!rendered) {
173
+ countDropped(opts, "row:render-failed");
174
+ continue;
175
+ }
176
+ for (const tag of rendered.unplaced) {
177
+ countDropped(opts, `component:${tag}:not-rendered`);
178
+ }
179
+ const { raw, components: aligned } = rendered;
180
+ yield {
181
+ raw,
182
+ components: aligned,
183
+ country: "UY",
184
+ locale: "es-UY",
185
+ source: IDE_UY_ADAPTER_ID,
186
+ // The point's coordinates separate two doors the rendered components cannot,
187
+ // such as the several access points one `num_puerta` has on a corner lot.
188
+ source_id: stableSourceIDFromParts(IDE_UY_ADAPTER_ID, {
189
+ ...aligned,
190
+ lat: record.latitud ?? "",
191
+ lon: record.longitud ?? "",
192
+ }),
193
+ corpus_version: "",
194
+ license: IDE_UY_LICENSE,
195
+ };
196
+ emitted++;
197
+ }
198
+ }
199
+ },
200
+ };
201
+ }
202
+ /**
203
+ * The configured adapter instance registered with the corpus builder.
204
+ */
205
+ export const ideUruguayAdapter = createIDEUruguayAdapter();
206
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../../lib/uy/adapters/ide/adapter.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,10 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * Renders four hand-reviewed Venezuelan postcode facts in the `locality postcode, region` order.
6
+ * Renders hand-reviewed Venezuelan postcode facts in the `locality postcode, region` order.
7
+ *
8
+ * A fact is kept in the data file only when its citation reaches a register row whose elected decision permits
9
+ * transform and train. None does today, so the file holds no tuple and the recipe refuses to run.
7
10
  */
8
11
  import type { PathBuilderLike } from "path-ts";
9
12
  import { type CorpusRecipe } from "#recipes/scaffold";
@@ -47,7 +50,8 @@ export declare function defaultReviewedPostcodeTuplePath(): string;
47
50
  /**
48
51
  * Reads and validates the reviewed tuples.
49
52
  *
50
- * @throws When the file does not hold exactly four complete, unique, reviewed tuples.
53
+ * @throws When the file does not hold as many complete, unique, reviewed
54
+ * tuples as its `tupleCount` declares.
51
55
  */
52
56
  export declare function readReviewedPostcodeTuples(path?: PathBuilderLike): Promise<ReviewedPostcodeTuple[]>;
53
57
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"reviewed-postcode-tail.d.ts","sourceRoot":"","sources":["../../../lib/ve/recipes/reviewed-postcode-tail.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,OAAO,EAAiB,KAAK,YAAY,EAAkB,MAAM,mBAAmB,CAAA;AAKpF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,QAAsD,CAAA;AAIhG,UAAU,uBAAuB;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,UAAU,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,WAAW,CAAA;IACpB,EAAE,EAAE,IAAI,CAAA;IACR,MAAM,EAAE,OAAO,CAAA;IACf,iBAAiB,EAAE,gBAAgB,CAAA;IACnC,UAAU,EAAE,uBAAuB,CAAA;CACnC;AAQD,KAAK,SAAS,GACX,mBAAmB,GACnB,sBAAsB,GACtB,YAAY,GACZ,WAAW,GACX,cAAc,GACd,eAAe,CAAA;AAElB,UAAU,OAAO;IAChB,EAAE,EAAE,SAAS,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC;AAID;;GAEG;AACH,wBAAgB,gCAAgC,IAAI,MAAM,CAEzD;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAC/C,IAAI,GAAE,eAAoD,GACxD,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAsClC;AAID;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,EAAE,CAkCpF;AAED;;GAEG;AACH,eAAO,MAAM,0BAA0B,EAAE,YA2CxC,CAAA"}
1
+ {"version":3,"file":"reviewed-postcode-tail.d.ts","sourceRoot":"","sources":["../../../lib/ve/recipes/reviewed-postcode-tail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,OAAO,EAAiB,KAAK,YAAY,EAAkB,MAAM,mBAAmB,CAAA;AAKpF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,QAAsD,CAAA;AAEhG,UAAU,uBAAuB;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,UAAU,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,WAAW,CAAA;IACpB,EAAE,EAAE,IAAI,CAAA;IACR,MAAM,EAAE,OAAO,CAAA;IACf,iBAAiB,EAAE,gBAAgB,CAAA;IACnC,UAAU,EAAE,uBAAuB,CAAA;CACnC;AAaD,KAAK,SAAS,GACX,mBAAmB,GACnB,sBAAsB,GACtB,YAAY,GACZ,WAAW,GACX,cAAc,GACd,eAAe,CAAA;AAElB,UAAU,OAAO;IAChB,EAAE,EAAE,SAAS,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC;AAID;;GAEG;AACH,wBAAgB,gCAAgC,IAAI,MAAM,CAEzD;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC/C,IAAI,GAAE,eAAoD,GACxD,OAAO,CAAC,qBAAqB,EAAE,CAAC,CA8ClC;AAID;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,EAAE,CAkCpF;AAED;;GAEG;AACH,eAAO,MAAM,0BAA0B,EAAE,YAmDxC,CAAA"}
@@ -3,7 +3,10 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * Renders four hand-reviewed Venezuelan postcode facts in the `locality postcode, region` order.
6
+ * Renders hand-reviewed Venezuelan postcode facts in the `locality postcode, region` order.
7
+ *
8
+ * A fact is kept in the data file only when its citation reaches a register row whose elected decision permits
9
+ * transform and train. None does today, so the file holds no tuple and the recipe refuses to run.
7
10
  */
8
11
  import { readLocalJSONFile } from "@mailwoman/core/fs/readers";
9
12
  import { resolveModulePath } from "@mailwoman/core/module/resolvers";
@@ -18,7 +21,6 @@ import { SurfaceOrigin } from "#types";
18
21
  * The rows get their own sampler bucket so an eval can measure them apart from other postcode placements.
19
22
  */
20
23
  export const REVIEWED_POSTCODE_TAIL_SOURCE = defaultRecipeSource("synth-reviewed-postcode-tail");
21
- const REVIEWED_TUPLE_COUNT = 4;
22
24
  const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
23
25
  /**
24
26
  * Returns the path of the committed reviewed-tuple data file.
@@ -29,12 +31,17 @@ export function defaultReviewedPostcodeTuplePath() {
29
31
  /**
30
32
  * Reads and validates the reviewed tuples.
31
33
  *
32
- * @throws When the file does not hold exactly four complete, unique, reviewed tuples.
34
+ * @throws When the file does not hold as many complete, unique, reviewed
35
+ * tuples as its `tupleCount` declares.
33
36
  */
34
37
  export async function readReviewedPostcodeTuples(path = defaultReviewedPostcodeTuplePath()) {
35
38
  const document = await readLocalJSONFile(path);
36
- if (!document.version || !ISO_DATE.test(document.reviewedAt) || document.tuples.length !== REVIEWED_TUPLE_COUNT) {
37
- throw new Error("reviewed postcode tuple file must declare a version, review date, and exactly four tuples");
39
+ if (!document.version ||
40
+ !ISO_DATE.test(document.reviewedAt) ||
41
+ !Number.isInteger(document.tupleCount) ||
42
+ document.tuples.length !== document.tupleCount) {
43
+ throw new Error(`reviewed postcode tuple file must declare a version, a review date, and a tupleCount matching its ` +
44
+ `${document.tuples?.length ?? 0} tuples`);
38
45
  }
39
46
  const ids = new Set();
40
47
  const facts = new Set();
@@ -110,6 +117,10 @@ export const reviewedPostcodeTailRecipe = {
110
117
  mode: "generate",
111
118
  async run(opts, write) {
112
119
  const tuples = await readReviewedPostcodeTuples(opts.input);
120
+ if (!tuples.length) {
121
+ throw new Error("reviewed-postcode-tail has no tuple to render: no reviewed fact's citation reaches a register row " +
122
+ "permitting transform and train");
123
+ }
113
124
  let emitted = 0;
114
125
  let skipped = 0;
115
126
  for (const tuple of tuples) {