@larsgw/formica 0.6.6 → 0.6.7

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,19 @@
1
+ ## [0.6.7](https://github.com/identification-resources/formica/compare/v0.6.6...v0.6.7) (2025-02-26)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **resources:** keep uncorrected name in verbatimIdentification ([dc4d5d1](https://github.com/identification-resources/formica/commit/dc4d5d17e7a412a94538f6162dfd839d808cbc30))
7
+ * **resources:** update heuristics for recognizing author citations ([bca6a7f](https://github.com/identification-resources/formica/commit/bca6a7f5a6c2dc3dc421d885f6270f5464fc5ea5))
8
+ * **resources:** use corrected generic name in output ([b472384](https://github.com/identification-resources/formica/commit/b4723842e87515f076230c603246082bc3bbec9d))
9
+
10
+
11
+ ### Features
12
+
13
+ * **resources:** add support for completeness flags ([55b5427](https://github.com/identification-resources/formica/commit/55b5427125b4f1072191cadbc461ba31d37e6f2e))
14
+
15
+
16
+
1
17
  ## [0.6.6](https://github.com/identification-resources/formica/compare/v0.6.5...v0.6.6) (2025-02-16)
2
18
 
3
19
 
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __assign = (this && this.__assign) || function () {
4
+ __assign = Object.assign || function(t) {
5
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
6
+ s = arguments[i];
7
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
8
+ t[p] = s[p];
9
+ }
10
+ return t;
11
+ };
12
+ return __assign.apply(this, arguments);
13
+ };
14
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
15
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
16
+ return new (P || (P = Promise))(function (resolve, reject) {
17
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
18
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
19
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
20
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
21
+ });
22
+ };
23
+ var __generator = (this && this.__generator) || function (thisArg, body) {
24
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
25
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
26
+ function verb(n) { return function (v) { return step([n, v]); }; }
27
+ function step(op) {
28
+ if (f) throw new TypeError("Generator is already executing.");
29
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
30
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
31
+ if (y = 0, t) op = [op[0] & 2, t.value];
32
+ switch (op[0]) {
33
+ case 0: case 1: t = op; break;
34
+ case 4: _.label++; return { value: op[1], done: false };
35
+ case 5: _.label++; y = op[1]; op = [0]; continue;
36
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
37
+ default:
38
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
39
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
40
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
41
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
42
+ if (t[2]) _.ops.pop();
43
+ _.trys.pop(); continue;
44
+ }
45
+ op = body.call(thisArg, _);
46
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
47
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
48
+ }
49
+ };
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ var fs_1 = require("fs");
52
+ var path = require("path");
53
+ var util = require("util");
54
+ var index_1 = require("../index");
55
+ var GBIF_RANKS = [
56
+ 'kingdom',
57
+ 'phyllum',
58
+ 'class',
59
+ 'order',
60
+ 'family',
61
+ 'genus',
62
+ 'species',
63
+ 'subspecies',
64
+ 'variety'
65
+ ];
66
+ var DISPLAY_FIELDS = [
67
+ 'scientificNameID',
68
+ 'taxonRank',
69
+ 'scientificName',
70
+ 'taxonomicStatus',
71
+ 'taxonRemarks',
72
+ 'colTaxonID',
73
+ 'gbifTaxonID'
74
+ ];
75
+ var ResourceProblemsProcessor = /** @class */ (function () {
76
+ function ResourceProblemsProcessor(collectionPath) {
77
+ this.DIR_ROOT = path.resolve(collectionPath);
78
+ this.DIR_DWC = path.join(this.DIR_ROOT, 'dwc');
79
+ this.FILE_INDEX = path.join(this.DIR_ROOT, 'index.json');
80
+ this.FILE_PROBLEMS = path.join(this.DIR_ROOT, 'problems.csv');
81
+ }
82
+ ResourceProblemsProcessor.prototype.run = function () {
83
+ return __awaiter(this, void 0, void 0, function () {
84
+ var problems, _a, _b, resources, _c, _d, _i, problems_1, problem, _work, resourceId, _problemText, resource, missing;
85
+ return __generator(this, function (_e) {
86
+ switch (_e.label) {
87
+ case 0:
88
+ _b = (_a = index_1.csv).parseCsv;
89
+ return [4 /*yield*/, fs_1.promises.readFile(this.FILE_PROBLEMS, 'utf8')];
90
+ case 1:
91
+ problems = _b.apply(_a, [_e.sent()]).slice(1);
92
+ _d = (_c = JSON).parse;
93
+ return [4 /*yield*/, fs_1.promises.readFile(this.FILE_INDEX, 'utf8')];
94
+ case 2:
95
+ resources = _d.apply(_c, [_e.sent()]);
96
+ _i = 0, problems_1 = problems;
97
+ _e.label = 3;
98
+ case 3:
99
+ if (!(_i < problems_1.length)) return [3 /*break*/, 6];
100
+ problem = problems_1[_i];
101
+ _work = problem[0], resourceId = problem[1], _problemText = problem[2];
102
+ return [4 /*yield*/, this.readAmendedResource(resourceId, resources)];
103
+ case 4:
104
+ resource = _e.sent();
105
+ missing = this.getMissingMappings(resource);
106
+ if (missing.length === 0) {
107
+ this.removeProblem(resourceId);
108
+ }
109
+ _e.label = 5;
110
+ case 5:
111
+ _i++;
112
+ return [3 /*break*/, 3];
113
+ case 6: return [2 /*return*/];
114
+ }
115
+ });
116
+ });
117
+ };
118
+ ResourceProblemsProcessor.prototype.readAmendedResource = function (resourceId, resources) {
119
+ return __awaiter(this, void 0, void 0, function () {
120
+ var resource, resourceFileId, resourceFile, _a, header, taxonRows, _b, _c, taxa, _i, taxonRows_1, row, taxon;
121
+ return __generator(this, function (_d) {
122
+ switch (_d.label) {
123
+ case 0:
124
+ resource = resources[resourceId];
125
+ resourceFileId = resourceId.split(':').join('-');
126
+ resourceFile = path.join(this.DIR_DWC, "".concat(resourceFileId, ".csv"));
127
+ _c = (_b = index_1.csv).parseCsv;
128
+ return [4 /*yield*/, fs_1.promises.readFile(resourceFile, 'utf8')];
129
+ case 1:
130
+ _a = _c.apply(_b, [_d.sent()]), header = _a[0], taxonRows = _a.slice(1);
131
+ taxa = {};
132
+ for (_i = 0, taxonRows_1 = taxonRows; _i < taxonRows_1.length; _i++) {
133
+ row = taxonRows_1[_i];
134
+ taxon = row.reduce(function (taxon, value, index) {
135
+ taxon[header[index]] = value;
136
+ return taxon;
137
+ }, {});
138
+ taxa[taxon.scientificNameID] = taxon;
139
+ }
140
+ return [2 /*return*/, __assign(__assign({}, resource), { taxa: taxa })];
141
+ }
142
+ });
143
+ });
144
+ };
145
+ ResourceProblemsProcessor.prototype.getMissingMappings = function (resource) {
146
+ var missing = [];
147
+ for (var id in resource.taxa) {
148
+ var taxon = resource.taxa[id];
149
+ if (taxon.taxonomicStatus !== 'accepted') {
150
+ continue;
151
+ }
152
+ var missingCol = false; // !taxon.colTaxonID
153
+ var missingGbif = GBIF_RANKS.includes(taxon.taxonRank) && !taxon.gbifTaxonID;
154
+ if (missingCol || missingGbif) {
155
+ missing.push(taxon);
156
+ }
157
+ }
158
+ return missing;
159
+ };
160
+ ResourceProblemsProcessor.prototype.removeProblem = function (resourceId) {
161
+ resourceId;
162
+ //
163
+ };
164
+ return ResourceProblemsProcessor;
165
+ }());
166
+ function main() {
167
+ var args = util.parseArgs({ allowPositionals: true });
168
+ var processor = new ResourceProblemsProcessor(args.positionals[0]);
169
+ process.on('exit', function () {
170
+ process.stdout.write('\n');
171
+ });
172
+ processor.run().catch(function (error) {
173
+ console.error(error);
174
+ process.exit(1);
175
+ });
176
+ }
177
+ main();
@@ -64,6 +64,11 @@ var DWC_RANKS = [
64
64
  'genus',
65
65
  'subgenus'
66
66
  ];
67
+ var FLAGS = [
68
+ 'MISSING_TAXA',
69
+ 'MISSING_PARENT_TAXA',
70
+ 'MISSING_SYNONYMS'
71
+ ];
67
72
  var TAXONOMIC_STATUS = {
68
73
  '>': 'incorrect',
69
74
  '+': 'heterotypic synonym',
@@ -103,7 +108,7 @@ var RANK_LABELS_REVERSE = {
103
108
  * 2. Followed by a capitalized word
104
109
  * 3. Optionally, followed by "et al."
105
110
  */
106
- var LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van'].join('|');
111
+ var LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van', 'von'].join('|');
107
112
  var SIMPLE_AUTHOR_PATTERN = '(?:(?:\\p{Lu}\\S*|&|in|ex|' + LOWERCASE_NAME_PARTICLES + ')\\s*)*\\p{Lu}\\S+(?:\\s+et\\s+al\\.)?';
108
113
  var NAME_PATTERN = new RegExp('^' +
109
114
  // $1 main name part
@@ -111,7 +116,7 @@ var NAME_PATTERN = new RegExp('^' +
111
116
  // $2 optional author citation
112
117
  '(?: ' +
113
118
  // but not auct(t)., etc.
114
- '(?!auctt?\\.|(?:syn|comb|sp|spec)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.))' +
119
+ '(?!auctt?\\.|(?:syn|comb|sp|spec|nom)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
115
120
  '(' +
116
121
  // $2.1 anything in parentheses, followed by optional revising author(s)
117
122
  '\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
@@ -326,17 +331,6 @@ function parseName(name, rank, parent) {
326
331
  if (item.scientificNameAuthorship) {
327
332
  item.scientificName += " ".concat(item.scientificNameAuthorship);
328
333
  }
329
- // Amend "parent" with corrections
330
- if (item.taxonomicStatus === 'incorrect') {
331
- var itemAsObject = item;
332
- var parentAsObject = parent;
333
- parent.incorrect = __assign({}, parent);
334
- for (var key in item) {
335
- if (key !== 'taxonomicStatus') {
336
- parentAsObject[key] = itemAsObject[key];
337
- }
338
- }
339
- }
340
334
  return item;
341
335
  }
342
336
  function parseHeader(header) {
@@ -401,6 +395,16 @@ function parseHeader(header) {
401
395
  metadata.catalog[key] = work.get(key);
402
396
  }
403
397
  }
398
+ if ('flags' in config) {
399
+ if (!Array.isArray(config.flags)) {
400
+ throw new SyntaxError('"flags" should be an array if present');
401
+ }
402
+ var invalidFlags = config.flags.filter(function (flag) { return !FLAGS.includes(flag); });
403
+ if (invalidFlags.length) {
404
+ throw new SyntaxError("\"flags\" contains invalid values: ".concat(invalidFlags.join(', ')));
405
+ }
406
+ metadata.flags = config.flags;
407
+ }
404
408
  return metadata;
405
409
  }
406
410
  function validateResource(config, content) {
@@ -480,27 +484,11 @@ function parseResourceContent(content, resource, oldIds) {
480
484
  var rank = resource.metadata.levels[groupIndent / 2];
481
485
  var item = parseName(name_1, rank, parent_1);
482
486
  var isSynonym = item.taxonomicStatus !== 'accepted';
483
- if (item.taxonomicStatus === 'incorrect') {
484
- return "continue";
485
- }
486
- if (type === diff_resource_1.ResourceDiffType.Added) {
487
- newIdOffset++;
488
- item.scientificNameID = idBase + newIdOffset.toString();
489
- }
490
- else {
491
- id++;
492
- item.scientificNameID = idBase + (oldIds[id - 1] || id).toString();
493
- }
494
- previousId = item.scientificNameID;
495
- item.parentNameUsageID = isSynonym ? undefined : parent_1.scientificNameID;
496
- item.parentNameUsage = isSynonym ? undefined : parent_1.scientificName;
497
- item.acceptedNameUsageID = isSynonym ? parent_1.scientificNameID : undefined;
498
- item.acceptedNameUsage = isSynonym ? parent_1.scientificName : undefined;
499
- item.collectionCode = idBase.slice(0, -1);
487
+ // Add higher classification info
488
+ var itemAsObject = item;
489
+ var parentAsObject = parent_1;
500
490
  for (var _b = 0, DWC_RANKS_1 = DWC_RANKS; _b < DWC_RANKS_1.length; _b++) {
501
491
  var rank_1 = DWC_RANKS_1[_b];
502
- var itemAsObject = item;
503
- var parentAsObject = parent_1;
504
492
  itemAsObject[rank_1] = undefined;
505
493
  if (parentAsObject[rank_1]) {
506
494
  itemAsObject[rank_1] = parentAsObject[rank_1];
@@ -524,6 +512,31 @@ function parseResourceContent(content, resource, oldIds) {
524
512
  else if (parentId) {
525
513
  item.higherClassification = parent_1.scientificNameOnly;
526
514
  }
515
+ // Amend "parent" with corrections
516
+ if (item.taxonomicStatus === 'incorrect') {
517
+ parent_1.incorrect = __assign({}, parent_1);
518
+ for (var key in item) {
519
+ if (key !== 'taxonomicStatus' && key !== 'verbatimIdentification') {
520
+ parentAsObject[key] = itemAsObject[key];
521
+ }
522
+ }
523
+ return "continue";
524
+ }
525
+ // Set identifiers
526
+ if (type === diff_resource_1.ResourceDiffType.Added) {
527
+ newIdOffset++;
528
+ item.scientificNameID = idBase + newIdOffset.toString();
529
+ }
530
+ else {
531
+ id++;
532
+ item.scientificNameID = idBase + (oldIds[id - 1] || id).toString();
533
+ }
534
+ previousId = item.scientificNameID;
535
+ item.parentNameUsageID = isSynonym ? undefined : parent_1.scientificNameID;
536
+ item.parentNameUsage = isSynonym ? undefined : parent_1.scientificName;
537
+ item.acceptedNameUsageID = isSynonym ? parent_1.scientificNameID : undefined;
538
+ item.acceptedNameUsage = isSynonym ? parent_1.scientificName : undefined;
539
+ item.collectionCode = idBase.slice(0, -1);
527
540
  data[item.scientificNameID] = item;
528
541
  };
529
542
  for (var _i = 0, content_1 = content; _i < content_1.length; _i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larsgw/formica",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
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",
@@ -175,7 +175,6 @@ class ResourceProcessor {
175
175
  const skip = await this.shouldBeSkipped(resource.id)
176
176
 
177
177
  if (!skip) {
178
- // TODO const correct = checkResults(results, classifications)
179
178
  const correct = this.checkResults(results)
180
179
  if (!correct) {
181
180
  const choice = await promptForAnswers(
package/src/module.d.ts CHANGED
@@ -71,8 +71,11 @@ interface Taxon extends WorkingTaxon {
71
71
  verbatimIdentification: string
72
72
  }
73
73
 
74
+ type ResourceFlag = string;
75
+
74
76
  interface ResourceMetadata {
75
77
  levels: Rank[],
78
+ flags?: ResourceFlag[],
76
79
  catalog?: Record<string, Value>
77
80
  }
78
81
 
@@ -54,6 +54,12 @@ const DWC_RANKS: DwcRank[] = [
54
54
  'subgenus'
55
55
  ]
56
56
 
57
+ const FLAGS: ResourceFlag[] = [
58
+ 'MISSING_TAXA',
59
+ 'MISSING_PARENT_TAXA',
60
+ 'MISSING_SYNONYMS'
61
+ ]
62
+
57
63
  const TAXONOMIC_STATUS: Record<string, TaxonStatus> = {
58
64
  '>': 'incorrect',
59
65
  '+': 'heterotypic synonym',
@@ -97,7 +103,7 @@ const RANK_LABELS_REVERSE: Record<string, Rank> = {
97
103
  * 2. Followed by a capitalized word
98
104
  * 3. Optionally, followed by "et al."
99
105
  */
100
- const LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van'].join('|')
106
+ const LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van', 'von'].join('|')
101
107
  const SIMPLE_AUTHOR_PATTERN = '(?:(?:\\p{Lu}\\S*|&|in|ex|' + LOWERCASE_NAME_PARTICLES + ')\\s*)*\\p{Lu}\\S+(?:\\s+et\\s+al\\.)?'
102
108
 
103
109
  const NAME_PATTERN = new RegExp(
@@ -107,7 +113,7 @@ const NAME_PATTERN = new RegExp(
107
113
  // $2 optional author citation
108
114
  '(?: ' +
109
115
  // but not auct(t)., etc.
110
- '(?!auctt?\\.|(?:syn|comb|sp|spec)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.))' +
116
+ '(?!auctt?\\.|(?:syn|comb|sp|spec|nom)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
111
117
  '(' +
112
118
  // $2.1 anything in parentheses, followed by optional revising author(s)
113
119
  '\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
@@ -342,19 +348,6 @@ function parseName (name: string, rank: Rank, parent: WorkingTaxon): WorkingTaxo
342
348
  item.scientificName += ` ${item.scientificNameAuthorship}`
343
349
  }
344
350
 
345
- // Amend "parent" with corrections
346
- if (item.taxonomicStatus === 'incorrect') {
347
- const itemAsObject = item as { [index: string]: unknown }
348
- const parentAsObject = parent as { [index: string]: unknown }
349
-
350
- parent.incorrect = { ...parent }
351
- for (const key in item) {
352
- if (key !== 'taxonomicStatus') {
353
- parentAsObject[key] = itemAsObject[key]
354
- }
355
- }
356
- }
357
-
358
351
  return item
359
352
  }
360
353
 
@@ -418,6 +411,19 @@ function parseHeader (header: string): ResourceMetadata {
418
411
  }
419
412
  }
420
413
 
414
+ if ('flags' in config) {
415
+ if (!Array.isArray(config.flags)) {
416
+ throw new SyntaxError('"flags" should be an array if present')
417
+ }
418
+
419
+ const invalidFlags = config.flags.filter(flag => !FLAGS.includes(flag))
420
+ if (invalidFlags.length) {
421
+ throw new SyntaxError(`"flags" contains invalid values: ${invalidFlags.join(', ')}`)
422
+ }
423
+
424
+ metadata.flags = config.flags
425
+ }
426
+
421
427
  return metadata
422
428
  }
423
429
 
@@ -510,29 +516,10 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
510
516
  const item = parseName(name, rank, parent)
511
517
  const isSynonym = item.taxonomicStatus !== 'accepted'
512
518
 
513
- if (item.taxonomicStatus === 'incorrect') {
514
- continue
515
- }
516
-
517
- if (type === ResourceDiffType.Added) {
518
- newIdOffset++
519
- item.scientificNameID = idBase + newIdOffset.toString()
520
- } else {
521
- id++
522
- item.scientificNameID = idBase + (oldIds[id - 1] || id).toString()
523
- }
524
- previousId = item.scientificNameID
525
-
526
- item.parentNameUsageID = isSynonym ? undefined : parent.scientificNameID
527
- item.parentNameUsage = isSynonym ? undefined : parent.scientificName
528
- item.acceptedNameUsageID = isSynonym ? parent.scientificNameID : undefined
529
- item.acceptedNameUsage = isSynonym ? parent.scientificName : undefined
530
- item.collectionCode = idBase.slice(0, -1)
531
-
519
+ // Add higher classification info
520
+ const itemAsObject = item as { [index: string]: unknown }
521
+ const parentAsObject = parent as { [index: string]: unknown }
532
522
  for (const rank of DWC_RANKS) {
533
- const itemAsObject = item as { [index: string]: unknown }
534
- const parentAsObject = parent as { [index: string]: unknown }
535
-
536
523
  itemAsObject[rank] = undefined
537
524
  if (parentAsObject[rank]) {
538
525
  itemAsObject[rank] = parentAsObject[rank]
@@ -557,6 +544,33 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
557
544
  item.higherClassification = parent.scientificNameOnly
558
545
  }
559
546
 
547
+ // Amend "parent" with corrections
548
+ if (item.taxonomicStatus === 'incorrect') {
549
+ parent.incorrect = { ...parent }
550
+ for (const key in item) {
551
+ if (key !== 'taxonomicStatus' && key !== 'verbatimIdentification') {
552
+ parentAsObject[key] = itemAsObject[key]
553
+ }
554
+ }
555
+ continue
556
+ }
557
+
558
+ // Set identifiers
559
+ if (type === ResourceDiffType.Added) {
560
+ newIdOffset++
561
+ item.scientificNameID = idBase + newIdOffset.toString()
562
+ } else {
563
+ id++
564
+ item.scientificNameID = idBase + (oldIds[id - 1] || id).toString()
565
+ }
566
+ previousId = item.scientificNameID
567
+
568
+ item.parentNameUsageID = isSynonym ? undefined : parent.scientificNameID
569
+ item.parentNameUsage = isSynonym ? undefined : parent.scientificName
570
+ item.acceptedNameUsageID = isSynonym ? parent.scientificNameID : undefined
571
+ item.acceptedNameUsage = isSynonym ? parent.scientificName : undefined
572
+ item.collectionCode = idBase.slice(0, -1)
573
+
560
574
  data[item.scientificNameID] = item
561
575
  }
562
576
 
package/test/resources.js CHANGED
@@ -136,4 +136,17 @@ Tilia x vulgaris
136
136
  assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Rumex conglomeratus×maritimus')
137
137
  assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Tilia ×vulgaris')
138
138
  })
139
+
140
+ await t.test('outputs corrected generic names', (t) => {
141
+ const [resource] = resources.parseTextFile(`---
142
+ levels: [genus, species]
143
+ ---
144
+
145
+ Bogdania Kerzhner, 1964
146
+ > Bogdiana Kerzhner, 1964
147
+ myrmica Kerzhner, 1964
148
+ `, 'T1')
149
+ assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Bogdiana Kerzhner, 1964')
150
+ assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Bogdiana myrmica Kerzhner, 1964')
151
+ })
139
152
  })