@larsgw/formica 0.9.0 → 0.9.2

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/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## [0.9.2](https://github.com/identification-resources/formica/compare/v0.9.1...v0.9.2) (2026-06-05)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **catalog:** check for ID uniqueness ([eed45ad](https://github.com/identification-resources/formica/commit/eed45ad6155a03474606d84daefddac4c1c63956))
7
+
8
+ ## [0.9.1](https://github.com/identification-resources/formica/compare/v0.9.0...v0.9.1) (2026-04-21)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **resources:** fix RDF mapping of taxonomic status ([c65383e](https://github.com/identification-resources/formica/commit/c65383e4d64c674c6c3b9f69e8cae925fcdf6896))
14
+
15
+
16
+ ### Features
17
+
18
+ * **catalog:** add more validation for taxa ([2bbc017](https://github.com/identification-resources/formica/commit/2bbc0177f8d4a6909339cfee812c33873770f725))
19
+
1
20
  # [0.9.0](https://github.com/identification-resources/formica/compare/v0.8.8...v0.9.0) (2026-02-04)
2
21
 
3
22
 
@@ -11,8 +30,6 @@
11
30
  * **resources:** include cluster data in DwC files ([60a28ac](https://github.com/identification-resources/formica/commit/60a28ace18b0760696735ca416a2e8d014d1c928))
12
31
  * **resources:** include uncertainty of synonymy in DwC if specified ([03b6734](https://github.com/identification-resources/formica/commit/03b6734851dc9c331ec88fab21e90ad3f0303c3c))
13
32
 
14
-
15
-
16
33
  ## [0.8.8](https://github.com/identification-resources/formica/compare/v0.8.7...v0.8.8) (2025-12-25)
17
34
 
18
35
 
@@ -20,8 +37,6 @@
20
37
 
21
38
  * **resources:** add 'subclass' rank ([4b10405](https://github.com/identification-resources/formica/commit/4b10405b32840c1675caf7ef24bf2bbfd821a236))
22
39
 
23
-
24
-
25
40
  ## [0.8.7](https://github.com/identification-resources/formica/compare/v0.8.6...v0.8.7) (2025-11-01)
26
41
 
27
42
 
@@ -29,8 +44,6 @@
29
44
 
30
45
  * **resources:** fix handling of corrections ([49c05b4](https://github.com/identification-resources/formica/commit/49c05b499262e64514b832313360d1e6a4cc6378))
31
46
 
32
-
33
-
34
47
  ## [0.8.6](https://github.com/identification-resources/formica/compare/v0.8.5...v0.8.6) (2025-10-28)
35
48
 
36
49
 
@@ -44,8 +57,6 @@
44
57
 
45
58
  * **resources:** ignore errors in later-corrected names ([4c1f69e](https://github.com/identification-resources/formica/commit/4c1f69e81450664f122cdf00e68cb918248790e6)), closes [#5](https://github.com/identification-resources/formica/issues/5)
46
59
 
47
-
48
-
49
60
  ## [0.8.5](https://github.com/identification-resources/formica/compare/v0.8.4...v0.8.5) (2025-10-28)
50
61
 
51
62
 
@@ -63,8 +74,6 @@
63
74
  * **resources:** handle subgenus synonyms of genera ([4e61336](https://github.com/identification-resources/formica/commit/4e61336e36054ffd6df57ebad5479e7ceebcb235)), closes [#16](https://github.com/identification-resources/formica/issues/16)
64
75
  * **resources:** rework parsing of resources ([cff7730](https://github.com/identification-resources/formica/commit/cff773000c5b9e3f990c619cdd45a76a18ee39a9)), closes [#15](https://github.com/identification-resources/formica/issues/15) [#7](https://github.com/identification-resources/formica/issues/7)
65
76
 
66
-
67
-
68
77
  ## [0.8.4](https://github.com/identification-resources/formica/compare/v0.8.3...v0.8.4) (2025-09-19)
69
78
 
70
79
 
@@ -78,8 +87,6 @@
78
87
 
79
88
  * **catalog:** map additional scope value ([80c6d26](https://github.com/identification-resources/formica/commit/80c6d269a7d5b03600991fb353280e81afcefb0e))
80
89
 
81
-
82
-
83
90
  ## [0.8.3](https://github.com/identification-resources/formica/compare/v0.8.2...v0.8.3) (2025-07-15)
84
91
 
85
92
 
@@ -87,8 +94,6 @@
87
94
 
88
95
  * **catalog:** add catalog field key_characteristics ([6529cfa](https://github.com/identification-resources/formica/commit/6529cfab81f759f926b81bdd9d5a36e84b526284))
89
96
 
90
-
91
-
92
97
  ## [0.8.2](https://github.com/identification-resources/formica/compare/v0.8.1...v0.8.2) (2025-06-26)
93
98
 
94
99
 
@@ -97,8 +102,6 @@
97
102
  * **resources:** add support for documented flag ([6cb89e4](https://github.com/identification-resources/formica/commit/6cb89e428bde8665b7e8f03b5c7f47a44e15f58e))
98
103
  * **resources:** allow hyphens in biname pattern ([6a58a77](https://github.com/identification-resources/formica/commit/6a58a77757b8b6ae8c2d7d01440775776d183d05))
99
104
 
100
-
101
-
102
105
  ## [0.8.1](https://github.com/identification-resources/formica/compare/v0.8.0...v0.8.1) (2025-05-30)
103
106
 
104
107
 
@@ -112,8 +115,6 @@
112
115
 
113
116
  * **resources:** add support for "(sub)gen. nov" pattern ([d2598eb](https://github.com/identification-resources/formica/commit/d2598ebfd2f80d9c2bcc00ce145b9fc229316e5b))
114
117
 
115
-
116
-
117
118
  # [0.8.0](https://github.com/identification-resources/formica/compare/v0.7.3...v0.8.0) (2025-05-07)
118
119
 
119
120
 
@@ -126,8 +127,6 @@
126
127
 
127
128
  * **catalog:** implement generation of linked data ([334616e](https://github.com/identification-resources/formica/commit/334616e2e12c0d91e49f84f06bdd00ab7a238b8e))
128
129
 
129
-
130
-
131
130
  ## [0.7.3](https://github.com/identification-resources/formica/compare/v0.7.2...v0.7.3) (2025-04-17)
132
131
 
133
132
 
@@ -135,8 +134,6 @@
135
134
 
136
135
  * **catalog:** update validation for identifiers ([156a703](https://github.com/identification-resources/formica/commit/156a7036ab09c411d091bc9d0f628831a30ba3f5))
137
136
 
138
-
139
-
140
137
  ## [0.7.2](https://github.com/identification-resources/formica/compare/v0.7.1...v0.7.2) (2025-04-02)
141
138
 
142
139
 
@@ -144,8 +141,6 @@
144
141
 
145
142
  * **catalog:** add validation for taxa.csv ([59cfcf2](https://github.com/identification-resources/formica/commit/59cfcf28ebbef42e28e648e800c178a7d528f37a))
146
143
 
147
-
148
-
149
144
  ## [0.7.1](https://github.com/identification-resources/formica/compare/v0.7.0...v0.7.1) (2025-03-25)
150
145
 
151
146
 
@@ -153,8 +148,6 @@
153
148
 
154
149
  * **resources:** add 'subphylum' rank ([8b5243b](https://github.com/identification-resources/formica/commit/8b5243bfd79b6486c613ca1178f1e1c9ec3f43a4))
155
150
 
156
-
157
-
158
151
  # [0.7.0](https://github.com/identification-resources/formica/compare/v0.6.8...v0.7.0) (2025-03-14)
159
152
 
160
153
 
@@ -172,8 +165,6 @@
172
165
 
173
166
  * **resources:** "indet" lines now have to be prefixed with "[indet]"
174
167
 
175
-
176
-
177
168
  ## [0.6.8](https://github.com/identification-resources/formica/compare/v0.6.7...v0.6.8) (2025-03-13)
178
169
 
179
170
 
@@ -183,8 +174,6 @@
183
174
  * **resources:** create index of CoL identifiers ([3792903](https://github.com/identification-resources/formica/commit/37929035374bc0c67ed2403e775e06a904bdbd4d))
184
175
  * **resources:** support intergeneric hybrids ([0a6696c](https://github.com/identification-resources/formica/commit/0a6696c5fd0603a0366c80c2298b6544636ed0c6))
185
176
 
186
-
187
-
188
177
  ## [0.6.7](https://github.com/identification-resources/formica/compare/v0.6.6...v0.6.7) (2025-02-26)
189
178
 
190
179
 
@@ -199,8 +188,6 @@
199
188
 
200
189
  * **resources:** add support for completeness flags ([55b5427](https://github.com/identification-resources/formica/commit/55b5427125b4f1072191cadbc461ba31d37e6f2e))
201
190
 
202
-
203
-
204
191
  ## [0.6.6](https://github.com/identification-resources/formica/compare/v0.6.5...v0.6.6) (2025-02-16)
205
192
 
206
193
 
@@ -208,8 +195,6 @@
208
195
 
209
196
  * **resources:** allow other resources in version_of ([85947b0](https://github.com/identification-resources/formica/commit/85947b0dcfbdb4bfd6537c628b34087a6e6326f7))
210
197
 
211
-
212
-
213
198
  ## [0.6.5](https://github.com/identification-resources/formica/compare/v0.6.4...v0.6.5) (2024-12-20)
214
199
 
215
200
 
@@ -219,8 +204,6 @@
219
204
  * **resources:** improve (ICBN) author parsing ([d3e3faf](https://github.com/identification-resources/formica/commit/d3e3faf411d1a7dccd528a5ee78bd0823a5883b4)), closes [#13](https://github.com/identification-resources/formica/issues/13) [#14](https://github.com/identification-resources/formica/issues/14)
220
205
  * **resources:** support "et al." in author name ([82993bf](https://github.com/identification-resources/formica/commit/82993bffac5e7f3ed00d00c28c836f347b264a43))
221
206
 
222
-
223
-
224
207
  ## [0.6.4](https://github.com/identification-resources/formica/compare/v0.6.3...v0.6.4) (2024-12-16)
225
208
 
226
209
 
@@ -228,8 +211,6 @@
228
211
 
229
212
  * **resources:** fix matching of some ICBN names ([3c18458](https://github.com/identification-resources/formica/commit/3c184586b64e2a31f1eb7298bcd7c80c1d119069))
230
213
 
231
-
232
-
233
214
  ## [0.6.3](https://github.com/identification-resources/formica/compare/v0.6.2...v0.6.3) (2024-04-12)
234
215
 
235
216
 
@@ -237,8 +218,6 @@
237
218
 
238
219
  * **resources:** allow 'phylum' rank ([ff9be66](https://github.com/identification-resources/formica/commit/ff9be66abab79a86263764b54d6a2709833b14d4))
239
220
 
240
-
241
-
242
221
  ## [0.6.2](https://github.com/identification-resources/formica/compare/v0.6.1...v0.6.2) (2024-04-08)
243
222
 
244
223
 
@@ -251,8 +230,6 @@
251
230
 
252
231
  * **catalog:** add script to de-duplicate links ([b719348](https://github.com/identification-resources/formica/commit/b719348635e1b35ee80758c0cdd9938625d79ebf))
253
232
 
254
-
255
-
256
233
  ## [0.6.1](https://github.com/identification-resources/formica/compare/v0.6.0...v0.6.1) (2024-03-20)
257
234
 
258
235
 
@@ -260,8 +237,6 @@
260
237
 
261
238
  * **resources:** allow two-part hybrid names ([8e3b836](https://github.com/identification-resources/formica/commit/8e3b8368985d1db35b62d306d833121150e91865))
262
239
 
263
-
264
-
265
240
  # [0.6.0](https://github.com/identification-resources/formica/compare/v0.5.2...v0.6.0) (2024-01-05)
266
241
 
267
242
 
@@ -269,8 +244,6 @@
269
244
 
270
245
  * **catalog:** add entry type, key type values ([ed6e4fb](https://github.com/identification-resources/formica/commit/ed6e4fb545e09cddb5d466f602bffb8a8483f043))
271
246
 
272
-
273
-
274
247
  ## [0.5.2](https://github.com/identification-resources/formica/compare/v0.5.1...v0.5.2) (2023-11-27)
275
248
 
276
249
 
@@ -278,8 +251,6 @@
278
251
 
279
252
  * **resources:** fix GBIF index generation ([c88e2d2](https://github.com/identification-resources/formica/commit/c88e2d2b306b930ca0b00b978e3eb89b8adfb656))
280
253
 
281
-
282
-
283
254
  ## [0.5.1](https://github.com/identification-resources/formica/compare/v0.5.0...v0.5.1) (2023-11-03)
284
255
 
285
256
 
@@ -287,8 +258,6 @@
287
258
 
288
259
  * **resources:** fix file listing in processor ([009b980](https://github.com/identification-resources/formica/commit/009b98091b0f597e91213ace82252facbe3b5fed))
289
260
 
290
-
291
-
292
261
  # [0.5.0](https://github.com/identification-resources/formica/compare/v0.4.3...v0.5.0) (2023-10-24)
293
262
 
294
263
 
@@ -319,8 +288,6 @@
319
288
  does no longer pass validation. Use the "taxon_scope" and "scope" fields
320
289
  in "catalog" instead.
321
290
 
322
-
323
-
324
291
  ## [0.4.3](https://github.com/identification-resources/formica/compare/v0.4.2...v0.4.3) (2023-09-30)
325
292
 
326
293
 
@@ -328,8 +295,6 @@ in "catalog" instead.
328
295
 
329
296
  * **resource:** fix regression in taxon parsing ([098477b](https://github.com/identification-resources/formica/commit/098477b5914324a3c15ec885617d4cf30996f30d))
330
297
 
331
-
332
-
333
298
  ## [0.4.2](https://github.com/identification-resources/formica/compare/v0.4.1...v0.4.2) (2023-09-30)
334
299
 
335
300
 
@@ -339,8 +304,6 @@ in "catalog" instead.
339
304
  * **resources:** correct synonym rank determintation ([766ea2a](https://github.com/identification-resources/formica/commit/766ea2af14ff3aa64a7a2e19ed096836d38182cf))
340
305
  * **resources:** do not parse "indet." lines as taxa ([f4debf6](https://github.com/identification-resources/formica/commit/f4debf695b0cdb375bfb60ab5ce942fa2f7dee85)), closes [#8](https://github.com/identification-resources/formica/issues/8)
341
306
 
342
-
343
-
344
307
  ## [0.4.1](https://github.com/identification-resources/formica/compare/v0.4.0...v0.4.1) (2023-09-07)
345
308
 
346
309
 
@@ -348,8 +311,6 @@ in "catalog" instead.
348
311
 
349
312
  * **catalog:** fix typo in duplicate_of schema ([fbb6333](https://github.com/identification-resources/formica/commit/fbb6333d10dd6f5846856ea8de81d941ce539d21))
350
313
 
351
-
352
-
353
314
  # [0.4.0](https://github.com/identification-resources/formica/compare/v0.3.1...v0.4.0) (2023-09-07)
354
315
 
355
316
 
@@ -363,8 +324,6 @@ in "catalog" instead.
363
324
 
364
325
  * **catalog:** add duplicate_of field ([e4a8bbe](https://github.com/identification-resources/formica/commit/e4a8bbecef9c6a40d0f2cc5d4e8bb67685a453ff))
365
326
 
366
-
367
-
368
327
  ## [0.3.1](https://github.com/identification-resources/formica/compare/v0.3.0...v0.3.1) (2023-09-04)
369
328
 
370
329
 
@@ -374,8 +333,6 @@ in "catalog" instead.
374
333
  * **resources:** fix regression in c737d5b ([d4e52e9](https://github.com/identification-resources/formica/commit/d4e52e95ed1a35dabbd80b9f9aace1a824fdae98))
375
334
  * **resources:** handle corrections to synonyms ([21ed9b7](https://github.com/identification-resources/formica/commit/21ed9b79f77e1e6d0639d96ed9822d5212a85a2e)), closes [#4](https://github.com/identification-resources/formica/issues/4)
376
335
 
377
-
378
-
379
336
  # [0.3.0](https://github.com/identification-resources/formica/compare/v0.2.1...v0.3.0) (2023-08-20)
380
337
 
381
338
 
@@ -387,8 +344,6 @@ in "catalog" instead.
387
344
  * **resources:** improve taxon name matching ([78ff480](https://github.com/identification-resources/formica/commit/78ff480485ca42bd1d1f2893f230e61db6cb6be8)), closes [#2](https://github.com/identification-resources/formica/issues/2)
388
345
  * **resources:** test for rank mismatch ([0e69b7f](https://github.com/identification-resources/formica/commit/0e69b7f0a6654fa5255e797be7516cd06edb4df3)), closes [#2](https://github.com/identification-resources/formica/issues/2)
389
346
 
390
-
391
-
392
347
  ## [0.2.1](https://github.com/identification-resources/formica/compare/v0.2.0...v0.2.1) (2023-08-10)
393
348
 
394
349
 
@@ -397,8 +352,6 @@ in "catalog" instead.
397
352
  * **resources:** do not validate old versions ([1a8dd2e](https://github.com/identification-resources/formica/commit/1a8dd2e0e373489e287d1fd89dbb7443245c5214))
398
353
  * **resources:** fix check for missing leaf taxa ([035f1ad](https://github.com/identification-resources/formica/commit/035f1ad20efa80b388ec3828d4ace4518c7b344e))
399
354
 
400
-
401
-
402
355
  # [0.2.0](https://github.com/identification-resources/formica/compare/v0.1.1...v0.2.0) (2023-08-09)
403
356
 
404
357
 
@@ -407,8 +360,6 @@ in "catalog" instead.
407
360
  * **catalog:** add taxon_scope column ([6a06c37](https://github.com/identification-resources/formica/commit/6a06c37ec0640a8ffbc258ad6a4d81d84d35fee9))
408
361
  * **resources:** check for missing leaf taxa ([387c47d](https://github.com/identification-resources/formica/commit/387c47d10bb7f1fb09a68c10dfa869bb924b7633))
409
362
 
410
-
411
-
412
363
  ## [0.1.1](https://github.com/identification-resources/formica/compare/v0.1.0...v0.1.1) (2023-05-10)
413
364
 
414
365
 
@@ -416,8 +367,6 @@ in "catalog" instead.
416
367
 
417
368
  * **processor:** add Node hashbang ([51dee57](https://github.com/identification-resources/formica/commit/51dee57b8547afba449b2b846bd7d65e7927368f))
418
369
 
419
-
420
-
421
370
  # [0.1.0](https://github.com/identification-resources/formica/compare/fed91fd6f350c47bd067d221a4d0e2278a199dae...v0.1.0) (2023-03-01)
422
371
 
423
372
 
@@ -439,6 +388,3 @@ in "catalog" instead.
439
388
  * **resources:** add support for hybrids ([e49e96a](https://github.com/identification-resources/formica/commit/e49e96af9c3958bab2a3b0508b1fc8a018b80716))
440
389
  * **validate:** add catalog validation script ([4cdf785](https://github.com/identification-resources/formica/commit/4cdf785afb2c77320cd9b5b76483e3464c0f1cff))
441
390
  * **validate:** add script to validate resources ([246fb5d](https://github.com/identification-resources/formica/commit/246fb5dd8da03e736884850874cc51a11be9985c))
442
-
443
-
444
-
@@ -125,7 +125,8 @@ const GBIF_RANKS = ['kingdom', 'phylum', 'class', 'order', 'family', 'genus', 's
125
125
  const GBIF_VOCAB_RANKS = ['domain', 'kingdom', 'subkingdom', 'superphylum', 'phylum', 'subphylum', 'superclass', 'class', 'subclass', 'supercohort', 'cohort', 'subcohort', 'superorder', 'order', 'suborder', 'infraorder', 'superfamily', 'family', 'subfamily', 'tribe', 'subtribe', 'genus', 'subgenus', 'section', 'subsection', 'series', 'subseries', 'speciesAggregate', 'species', 'subspecificAggregate', 'subspecies', 'variety', 'subvariety', 'form', 'subform', 'cultivarGroup', 'cultivar', 'strain'];
126
126
  const STATUSES = {
127
127
  'accepted': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/accepted',
128
- 'heterotypic synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/heterotypicSynonym',
128
+ 'misapplied': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/misapplied',
129
+ 'proparte synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/proParteSynonym',
129
130
  'synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/synonym',
130
131
  };
131
132
  function getCoveringTaxon(taxa) {
@@ -284,9 +285,12 @@ function makeLinkedDataForScientificName(name) {
284
285
  if (GBIF_VOCAB_RANKS.includes(name.taxonRank)) {
285
286
  node[DWC_FIELDS.taxonRank] = makeTaxonRankUri(name.taxonRank);
286
287
  }
287
- if (name.taxonomicStatus) {
288
+ if (name.taxonomicStatus in STATUSES) {
288
289
  node[DWC_FIELDS.taxonomicStatus] = makeTaxonomicStatusUri(name.taxonomicStatus);
289
290
  }
291
+ else {
292
+ console.error('Unmapped taxonomic status:', name.taxonomicStatus);
293
+ }
290
294
  if (name.acceptedNameUsageID) {
291
295
  node['dwc:acceptedNameUsageID'] = makeScientificNameUri(name.acceptedNameUsageID);
292
296
  }
@@ -46,12 +46,80 @@ Object.defineProperty(exports, "__esModule", { value: true });
46
46
  const fs_1 = require("fs");
47
47
  const path = __importStar(require("path"));
48
48
  const index_1 = require("../index");
49
+ class TaxaValidator {
50
+ constructor(taxa) {
51
+ this.taxa = Array.from(taxa).filter(taxon => taxon.has('ancestors_gbif'));
52
+ this.errors = [];
53
+ this.parentIndex = {};
54
+ }
55
+ validate() {
56
+ for (const taxon of this.taxa) {
57
+ const parents = taxon.get('ancestors_gbif');
58
+ if (parents.length > 1) {
59
+ const id = taxon.get('id');
60
+ for (let i = 1; i < parents.length; i++) {
61
+ this.addToIndex(id, 'ancestors_gbif', parents[i], parents[i - 1]);
62
+ }
63
+ }
64
+ }
65
+ for (const taxon of this.taxa) {
66
+ if (taxon.has('gbif')) {
67
+ const id = taxon.get('id');
68
+ const parents = taxon.get('ancestors_gbif');
69
+ const parent = parents[parents.length - 1];
70
+ this.addToIndex(id, 'gbif', taxon.get('gbif'), parent);
71
+ }
72
+ }
73
+ for (const taxon of this.taxa) {
74
+ if (taxon.has('children_gbif')) {
75
+ const id = taxon.get('id');
76
+ const parents = taxon.get('ancestors_gbif');
77
+ const parent = parents[parents.length - 1];
78
+ const childIds = taxon.get('children_gbif');
79
+ for (const childId of childIds) {
80
+ this.addToIndex(id, 'gbif', childId, parent);
81
+ }
82
+ }
83
+ }
84
+ return this.errors;
85
+ }
86
+ addToIndex(entity, field, childId, parentId) {
87
+ const actualParentId = this.parentIndex[childId];
88
+ if (!actualParentId) {
89
+ this.parentIndex[childId] = parentId;
90
+ }
91
+ else if (actualParentId !== parentId) {
92
+ const error = `Inconsistent ancestor of ${childId}, expected ${actualParentId} but got ${parentId}`;
93
+ this.errors.push({ entity, field, error });
94
+ }
95
+ }
96
+ }
49
97
  function validateFile(arg) {
50
98
  return __awaiter(this, void 0, void 0, function* () {
51
99
  const filePath = path.resolve(arg);
52
100
  const file = yield fs_1.promises.readFile(filePath, 'utf8');
53
101
  const sheet = path.basename(filePath, '.csv');
54
- return index_1.catalog.loadData(file, sheet).validate();
102
+ const entities = index_1.catalog.loadData(file, sheet);
103
+ const errors = entities.validate();
104
+ const ids = new Set();
105
+ for (const entity of entities) {
106
+ const id = entity.get('id');
107
+ if (ids.has(id)) {
108
+ errors.push({
109
+ entity: id,
110
+ field: 'id',
111
+ error: `ID "${id}" repeated`
112
+ });
113
+ }
114
+ else {
115
+ ids.add(id);
116
+ }
117
+ }
118
+ if (sheet === 'taxa') {
119
+ const validator = new TaxaValidator(entities);
120
+ errors.push(...validator.validate());
121
+ }
122
+ return errors;
55
123
  });
56
124
  }
57
125
  function main(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larsgw/formica",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "SDK and tools for data from the Library of Identification Resources",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -34,7 +34,7 @@
34
34
  "dependencies": {
35
35
  "ietf-language-tag-regex": "^0.0.5",
36
36
  "js-yaml": "^4.1.0",
37
- "jsonld": "^8.3.3",
37
+ "jsonld": "^9.0.0",
38
38
  "n3": "^1.25.2",
39
39
  "spdx-license-list": "^6.6.0"
40
40
  },
@@ -106,7 +106,8 @@ const GBIF_RANKS = ['kingdom', 'phylum', 'class', 'order', 'family', 'genus', 's
106
106
  const GBIF_VOCAB_RANKS = ['domain', 'kingdom', 'subkingdom', 'superphylum', 'phylum', 'subphylum', 'superclass', 'class', 'subclass', 'supercohort', 'cohort', 'subcohort', 'superorder', 'order', 'suborder', 'infraorder', 'superfamily', 'family', 'subfamily', 'tribe', 'subtribe', 'genus', 'subgenus', 'section', 'subsection', 'series', 'subseries', 'speciesAggregate', 'species', 'subspecificAggregate', 'subspecies', 'variety', 'subvariety', 'form', 'subform', 'cultivarGroup', 'cultivar', 'strain']
107
107
  const STATUSES: Record<string, string> = {
108
108
  'accepted': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/accepted',
109
- 'heterotypic synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/heterotypicSynonym',
109
+ 'misapplied': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/misapplied',
110
+ 'proparte synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/proParteSynonym',
110
111
  'synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/synonym',
111
112
  }
112
113
 
@@ -182,8 +183,8 @@ function makeTaxonRankUri (rank: string): NodeObject|string {
182
183
  }
183
184
  }
184
185
 
185
- function makeTaxonomicStatusUri (status: string): NodeObject {
186
- return { '@id': STATUSES[status] as string }
186
+ function makeTaxonomicStatusUri (status: keyof typeof STATUSES): NodeObject {
187
+ return { '@id': STATUSES[status] }
187
188
  }
188
189
 
189
190
  function makeLinkedDataForAuthor (author: catalog.Entity): NodeObject {
@@ -313,8 +314,10 @@ function makeLinkedDataForScientificName (name: AmendedTaxon): NodeObject {
313
314
  node[DWC_FIELDS.taxonRank] = makeTaxonRankUri(name.taxonRank)
314
315
  }
315
316
 
316
- if (name.taxonomicStatus) {
317
+ if (name.taxonomicStatus in STATUSES) {
317
318
  node[DWC_FIELDS.taxonomicStatus] = makeTaxonomicStatusUri(name.taxonomicStatus)
319
+ } else {
320
+ console.error('Unmapped taxonomic status:', name.taxonomicStatus)
318
321
  }
319
322
 
320
323
  if (name.acceptedNameUsageID) {
@@ -4,11 +4,96 @@ import { promises as fs } from 'fs'
4
4
  import * as path from 'path'
5
5
  import { catalog } from '../index'
6
6
 
7
+ class TaxaValidator {
8
+ taxa: catalog.Entity[];
9
+ errors: WorkError[];
10
+ parentIndex: Record<string, string>;
11
+
12
+ constructor (taxa: catalog.Entities) {
13
+ this.taxa = Array.from(taxa).filter(taxon => taxon.has('ancestors_gbif'))
14
+ this.errors = []
15
+ this.parentIndex = {}
16
+ }
17
+
18
+ validate (): WorkError[] {
19
+ for (const taxon of this.taxa) {
20
+ const parents = taxon.get('ancestors_gbif') as string[]
21
+
22
+ if (parents.length > 1) {
23
+ const id = taxon.get('id') as string
24
+
25
+ for (let i = 1; i < parents.length; i++) {
26
+ this.addToIndex(id, 'ancestors_gbif', parents[i], parents[i - 1])
27
+ }
28
+ }
29
+ }
30
+
31
+ for (const taxon of this.taxa) {
32
+ if (taxon.has('gbif')) {
33
+ const id = taxon.get('id') as string
34
+ const parents = taxon.get('ancestors_gbif') as string[]
35
+ const parent = parents[parents.length - 1] as string
36
+
37
+ this.addToIndex(id, 'gbif', taxon.get('gbif') as string, parent)
38
+ }
39
+ }
40
+
41
+ for (const taxon of this.taxa) {
42
+ if (taxon.has('children_gbif')) {
43
+ const id = taxon.get('id') as string
44
+ const parents = taxon.get('ancestors_gbif') as string[]
45
+ const parent = parents[parents.length - 1] as string
46
+
47
+ const childIds = taxon.get('children_gbif') as string[]
48
+ for (const childId of childIds) {
49
+ this.addToIndex(id, 'gbif', childId, parent)
50
+ }
51
+ }
52
+ }
53
+
54
+ return this.errors
55
+ }
56
+
57
+ addToIndex (entity: string, field: string, childId: string, parentId: string) {
58
+ const actualParentId = this.parentIndex[childId]
59
+
60
+ if (!actualParentId) {
61
+ this.parentIndex[childId] = parentId
62
+ } else if (actualParentId !== parentId) {
63
+ const error = `Inconsistent ancestor of ${childId}, expected ${actualParentId} but got ${parentId}`
64
+ this.errors.push({ entity, field, error })
65
+ }
66
+ }
67
+ }
68
+
7
69
  async function validateFile (arg: string): Promise<WorkError[]> {
8
70
  const filePath = path.resolve(arg)
9
71
  const file = await fs.readFile(filePath, 'utf8')
10
72
  const sheet = path.basename(filePath, '.csv')
11
- return catalog.loadData(file, sheet).validate()
73
+ const entities = catalog.loadData(file, sheet)
74
+ const errors = entities.validate()
75
+
76
+ const ids = new Set()
77
+ for (const entity of entities) {
78
+ const id = entity.get('id') as string
79
+
80
+ if (ids.has(id)) {
81
+ errors.push({
82
+ entity: id,
83
+ field: 'id',
84
+ error: `ID "${id}" repeated`
85
+ })
86
+ } else {
87
+ ids.add(id)
88
+ }
89
+ }
90
+
91
+ if (sheet === 'taxa') {
92
+ const validator = new TaxaValidator(entities)
93
+ errors.push(...validator.validate())
94
+ }
95
+
96
+ return errors
12
97
  }
13
98
 
14
99
  async function main (args: string[]): Promise<void> {