gedcom-ts 2026.5.1 → 2026.5.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 +13 -1
- package/README.md +188 -119
- package/dist/geocode/index.d.ts +1 -1
- package/dist/geocode/place-city-utils.d.ts +7 -3
- package/dist/geocode/place-clusters.d.ts +44 -5
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes of gedcom-ts
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [2026.5.2] - 2026-05-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Regroupement des lieux** : `groupActsBySimilarCity`, `actsNeedingGeocodeForCity` (alias géoloc), `similarCityKey` ; harmonisation et carte alignées sur `citiesAreSimilar` (plus de divergence `normalizeCityKey` seul).
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **`findHarmonizationClustersFromActs`** : union-find sur `citiesAreSimilar` (corrige le cas Pleurtuit 29+11 actes après géoloc complète).
|
|
14
|
+
- **`clusterKeyForCity`** : clé carte basée sur la localité principale (segment avant la virgule), alignée avec le regroupement par similarité.
|
|
15
|
+
- **README** : guide d’intégration géoloc restructuré (section dédiée, tableaux par cas d’usage) ; retrait de la documentation développement interne.
|
|
16
|
+
|
|
17
|
+
## [2026.5.1] - 2026-05-15
|
|
6
18
|
|
|
7
19
|
### Added
|
|
8
20
|
|
package/README.md
CHANGED
|
@@ -11,11 +11,18 @@
|
|
|
11
11
|
|
|
12
12
|
A graphical demo showcasing the public API (import a `.ged` / `.zip`, browse the typed model, export back) is available at **[https://gedcomts.jaunet.me](https://gedcomts.jaunet.me)**
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Contents
|
|
15
15
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
16
|
+
- [Installation](#installation)
|
|
17
|
+
- [Quick start](#quick-start)
|
|
18
|
+
- [Geocoding places](#geocoding-places)
|
|
19
|
+
- [API reference](#api-reference)
|
|
20
|
+
- [Error handling](#error-handling)
|
|
21
|
+
|
|
22
|
+
## Package
|
|
23
|
+
|
|
24
|
+
- NPM: [gedcom-ts](https://www.npmjs.com/package/gedcom-ts)
|
|
25
|
+
- Version format **CalVer** `AAAA.M.micro` (e.g. `2026.5.2` = May 2026). See [CHANGELOG.md](CHANGELOG.md).
|
|
19
26
|
|
|
20
27
|
## Installation
|
|
21
28
|
|
|
@@ -51,9 +58,134 @@ Typical workflow:
|
|
|
51
58
|
2. read / mutate the typed `Person`, `Act`, `Place`, `Note`, `MultimediaFile` objects (directly or through `editPerson` / `editAct` / `editPlace` / …)
|
|
52
59
|
3. export as `.ged` (`ExportGedcomFile`) or `.zip` (`ExportGedzipFile`)
|
|
53
60
|
|
|
54
|
-
##
|
|
61
|
+
## Geocoding places
|
|
62
|
+
|
|
63
|
+
Use this section when your app needs **GPS coordinates** on event places, a **list of cities still without coordinates**, **map markers**, or a **data-quality** view for inconsistent place names.
|
|
64
|
+
|
|
65
|
+
You work with a `ReadGed` (after import) and a flat `Act[]` built from every person’s events (see [Prepare act list](#prepare-act-list)).
|
|
66
|
+
|
|
67
|
+
### How city names are matched
|
|
68
|
+
|
|
69
|
+
The library groups variants of the same place with **`citiesAreSimilar`**, not exact string equality.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
| Label A | Label B | Same place? |
|
|
73
|
+
| ----------- | ------------------------------------ | --------------------------- |
|
|
74
|
+
| `Pleurtuit` | `Pleurtuit, Ille-et-Vilaine, France` | Yes |
|
|
75
|
+
| `Paris` | `Paris, TX, USA` | Depends on similarity rules |
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
Use the functions in the tables below for grouping, geocoding, and maps. Do **not** compare raw strings yourself, and do **not** use `normalizeCityKey` for grouping (spelling normalization only).
|
|
79
|
+
|
|
80
|
+
### Prepare act list
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import type { ReadGed, Act } from "gedcom-ts";
|
|
84
|
+
|
|
85
|
+
function collectAllActs(ged: ReadGed): Act[] {
|
|
86
|
+
const acts: Act[] = [];
|
|
87
|
+
for (const person of ged.persons) {
|
|
88
|
+
for (const act of person.acts.list) acts.push(act);
|
|
89
|
+
}
|
|
90
|
+
return acts;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Geocode one city (typical flow)
|
|
95
|
+
|
|
96
|
+
When the user picks a city and a Nominatim result, update **every similar act that still has no GPS** — not only acts with the exact same label.
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
| Step | Function |
|
|
100
|
+
| ---------------------------------------------- | ----------------------------------------------------------- |
|
|
101
|
+
| 1. Context from the tree (countries, centroid) | `inferGeocodeContext(ged)` |
|
|
102
|
+
| 2. Search OpenStreetMap | `searchPlacesWithContext(cityName, context)` |
|
|
103
|
+
| 3. Acts to update | `actsNeedingGeocodeForCity(allActs, cityName)` |
|
|
104
|
+
| 4. Write coordinates | `applyGeocodeCandidateToActs(targets, candidate, cityName)` |
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import {
|
|
109
|
+
inferGeocodeContext,
|
|
110
|
+
searchPlacesWithContext,
|
|
111
|
+
actsNeedingGeocodeForCity,
|
|
112
|
+
applyGeocodeCandidateToActs,
|
|
113
|
+
} from "gedcom-ts";
|
|
114
|
+
import type { ReadGed, Act } from "gedcom-ts";
|
|
115
|
+
|
|
116
|
+
async function geocodeCity(ged: ReadGed, allActs: Act[], cityName: string) {
|
|
117
|
+
const context = inferGeocodeContext(ged);
|
|
118
|
+
const candidates = await searchPlacesWithContext(cityName, context);
|
|
119
|
+
const chosen = candidates[0];
|
|
120
|
+
if (!chosen) return;
|
|
121
|
+
|
|
122
|
+
const targets = actsNeedingGeocodeForCity(allActs, cityName);
|
|
123
|
+
applyGeocodeCandidateToActs(targets, chosen, cityName);
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> **Tip:** Do not pass only acts from a harmonization cluster to `applyGeocodeCandidateToActs`. Longer labels (e.g. `Pleurtuit, Ille-et-Vilaine, France`) would stay without GPS. Always use `actsNeedingGeocodeForCity`.
|
|
128
|
+
|
|
129
|
+
### List cities missing coordinates
|
|
130
|
+
|
|
131
|
+
One row per city; `withoutCoordCount` is how many acts still need GPS.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { groupActsBySimilarCity } from "gedcom-ts";
|
|
135
|
+
|
|
136
|
+
const groups = groupActsBySimilarCity(allActs, { onlyWithoutCoordinates: true });
|
|
137
|
+
|
|
138
|
+
for (const group of groups) {
|
|
139
|
+
console.log(group.cityLabel, group.withoutCoordCount);
|
|
140
|
+
// group.acts — acts in this group without GPS
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Largest groups first (`group.acts.length`).
|
|
145
|
+
|
|
146
|
+
### Map: one marker per city
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { clusterKeyForCity } from "gedcom-ts";
|
|
150
|
+
|
|
151
|
+
const markerKey = clusterKeyForCity(act.place?.city ?? "");
|
|
152
|
+
// merge markers that share the same markerKey
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Harmonization (data quality, optional)
|
|
156
|
+
|
|
157
|
+
Use when spellings, GPS positions, or “some acts with / without GPS” disagree for the same similar city.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { findHarmonizationClusters } from "gedcom-ts";
|
|
161
|
+
|
|
162
|
+
for (const cluster of findHarmonizationClusters(ged)) {
|
|
163
|
+
console.log(cluster.labels, cluster.coordVariants, cluster.actsWithoutCoord);
|
|
164
|
+
}
|
|
165
|
+
```
|
|
55
166
|
|
|
56
|
-
|
|
167
|
+
After a full geocode via `actsNeedingGeocodeForCity`, you should not get a cluster that only reports missing GPS for that city.
|
|
168
|
+
|
|
169
|
+
### Geocoding API cheat sheet
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
| Goal | Call |
|
|
173
|
+
| ------------------------- | ---------------------------------------------------------------- |
|
|
174
|
+
| Search | `searchPlacesWithContext(city, inferGeocodeContext(ged))` |
|
|
175
|
+
| Acts to update on confirm | `actsNeedingGeocodeForCity(acts, city)` |
|
|
176
|
+
| Apply lat/lng | `applyGeocodeCandidateToActs(targets, candidate, city)` |
|
|
177
|
+
| Cities without GPS | `groupActsBySimilarCity(acts, { onlyWithoutCoordinates: true })` |
|
|
178
|
+
| Map marker id | `clusterKeyForCity(city)` |
|
|
179
|
+
| Inconsistencies | `findHarmonizationClusters(ged)` |
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
Network: `GET https://nominatim.openstreetmap.org/search?q=…&format=jsonv2` with a `User-Agent` (`gedcom-ts/<version> (genealogy library)`). For offline or mocked search, pass `fetchFn` in `searchPlaces` / `searchPlacesWithContext` options.
|
|
183
|
+
|
|
184
|
+
The legacy callback `getCityCoordinates` is deprecated — use the flow above.
|
|
185
|
+
|
|
186
|
+
## API reference
|
|
187
|
+
|
|
188
|
+
Short description and a minimal snippet for each public export.
|
|
57
189
|
|
|
58
190
|
### Importing a file
|
|
59
191
|
|
|
@@ -104,22 +236,24 @@ Result of an import. Top-level GEDCOM records not modeled into the typed graph (
|
|
|
104
236
|
|
|
105
237
|
Notable members:
|
|
106
238
|
|
|
107
|
-
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
239
|
+
|
|
240
|
+
| Member | Description |
|
|
241
|
+
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
242
|
+
| `persons: Person[]` | Imported individuals. |
|
|
243
|
+
| `mapPersons: Map<number, Person>` | `INDI` (integer) → `Person`. |
|
|
244
|
+
| `partnersMap: Map<number, Person[]>` | Family id → spouses. |
|
|
245
|
+
| `childsMap: Map<number, Person[]>` | Family id → children. |
|
|
246
|
+
| `placesMap: Map<string, Place>` | City name → `Place` (first occurrence wins). |
|
|
247
|
+
| `mapFiles: Map<string, File>` | Relative path → media `File` (for ZIP imports). |
|
|
248
|
+
| `datasetVersion: GedcomDatasetVersion` | `"7.0"`, `"5.5"` or `"unknown"`. |
|
|
249
|
+
| `preservedTopLevelRecords: string[]` | Raw blocks for partial round-trip. |
|
|
250
|
+
| `resolveIndividualPointer(raw)` | Resolves `@I12@`, `I12`, `@Homer_Simpson@`, `Homer_Simpson` to a `Person`. |
|
|
251
|
+
| `getChildrenForParent(parent)` | All children attached to a parent (via `FAMS`). |
|
|
252
|
+
| `getChildrenOfFamily(familyId)` | Children of a single family. |
|
|
253
|
+
| `groupPartners()` | Rebuilds `partnersMap` / `childsMap` after editing links. |
|
|
254
|
+
| `generateUniqueIndi()` | Next free `INDI` number for new persons. |
|
|
255
|
+
| `rehydratePlacesFromActs()` | Rebuilds `placesMap` from act places (e.g. after manual graph edits). Use `editReadGed(readGed).addPerson(...)` to register persons so maps stay coherent. |
|
|
256
|
+
|
|
123
257
|
|
|
124
258
|
```ts
|
|
125
259
|
import { ReadGed } from "gedcom-ts";
|
|
@@ -177,13 +311,15 @@ async function exportZip(persons: Person[]) {
|
|
|
177
311
|
|
|
178
312
|
Options shared by both exporters:
|
|
179
313
|
|
|
180
|
-
|
|
181
|
-
|
|
|
314
|
+
|
|
315
|
+
| Option | Effect |
|
|
316
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
182
317
|
| `extraTopLevelRecords` | Raw `0 …` blocks re-emitted before `SUBM` / `TRLR` (round-trip with `readGed.preservedTopLevelRecords`). |
|
|
183
|
-
| `headLanguageTag`
|
|
184
|
-
| `headCopyright`
|
|
185
|
-
| `headDestination`
|
|
186
|
-
| `headSchemaTagDefs`
|
|
318
|
+
| `headLanguageTag` | BCP 47 tag for `HEAD`.`LANG` (defaults to `en-US`). |
|
|
319
|
+
| `headCopyright` | One-line `1 COPR` notice. |
|
|
320
|
+
| `headDestination` | Value of `HEAD`.`DEST` (target app / URI). |
|
|
321
|
+
| `headSchemaTagDefs` | Extension tag definitions emitted as `HEAD`.`SCHMA` / `2 TAG …`. |
|
|
322
|
+
|
|
187
323
|
|
|
188
324
|
### Domain model
|
|
189
325
|
|
|
@@ -394,14 +530,16 @@ editPerson(person)
|
|
|
394
530
|
editDateAct(person.acts.list[0].dateAct!).setExactDate(1900, "JAN", 1);
|
|
395
531
|
```
|
|
396
532
|
|
|
397
|
-
|
|
398
|
-
|
|
|
399
|
-
|
|
|
400
|
-
| `
|
|
401
|
-
| `
|
|
402
|
-
| `
|
|
403
|
-
| `
|
|
404
|
-
| `
|
|
533
|
+
|
|
534
|
+
| Helper / class | Purpose |
|
|
535
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
536
|
+
| `editPerson(person)` / `PersonEdit` | Identity, `FAMS` / `FAMC` / `removeFams*`, `nameVariants()` / `attributes()` / `multimedia()`, bulk `clear*`, entry points to `acts()` and `notes()`. |
|
|
537
|
+
| `editActs(acts)` / `ActsEdit` | `add`, `addNew`, `insertAt`, `insertNewAt`, `replaceAt`, `removeAt`, `removeAct`, `removeLast`, `clear`, `sortByDate`, `at`, `indexOfAct`. |
|
|
538
|
+
| `editAct(act)` / `ActEdit` | `setType`, `setIndis`, dates, place, EVEN fields, preserved lines, `notes()`, `multimedia()`, `clearNotes`, `clearMultimedia`. |
|
|
539
|
+
| `editDateAct(dateAct)` / `DateActEdit` | `clear`, `applyGedcomPayload`, `setExactDate`, `setQualified`, `setBetween`, `setFromTo`, `setTime`, `setDatePhrase` / `appendDatePhrase`, `setVerbatimPayload`. |
|
|
540
|
+
| `editPlace(place)` / `PlaceEdit` | `setFromGedcom7Payload`, `setCity`, `setCounty`, `setState`, `setCountry`, `setPlacPhrase` / `appendPlacPhrase` / `clearPlacPhrase`, `setCoordinates` / `clearCoordinates` / `replaceCoordinateModel`, `clearStructured`. |
|
|
541
|
+
| `editNotes(notes)` / `NotesEdit` | `add`, `addNew`, `insertAt`, `insertNewAt`, `replaceAt`, `removeAt`, `removeNote`, `removeLast`, `clear`, `at`, `indexOfNote`. |
|
|
542
|
+
|
|
405
543
|
|
|
406
544
|
### Dataset editing: `ReadGed`, graph, clone, export options, validation
|
|
407
545
|
|
|
@@ -459,15 +597,17 @@ validateReadGed(readGed, {
|
|
|
459
597
|
|
|
460
598
|
`tryAddPersonToReadGed`, `tryRemovePersonFromReadGedByIndi`, `tryLinkChildToFamily`, `tryUnlinkChildFromFamily`, and `tryCreateMarriageFamily` return a `CommandResult` (`ok` + `issues` or `value` + optional `warnings`). `editReadGed(...).addPerson` uses these checks internally (throws on blocking errors). For UI or transactional flows, prefer the `try*` APIs and inspect `commandBlockingIssues`.
|
|
461
599
|
|
|
462
|
-
|
|
463
|
-
|
|
|
464
|
-
|
|
|
465
|
-
| `
|
|
466
|
-
| `
|
|
467
|
-
| `
|
|
468
|
-
| `
|
|
469
|
-
| `
|
|
470
|
-
| `
|
|
600
|
+
|
|
601
|
+
| Export | Role |
|
|
602
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
603
|
+
| `editReadGed(readGed)` / `ReadGedEdit` | `addPerson`, `removePersonByIndi`, `preserved()` → `PreservedTopLevelEdit` (`append`, `insertAt`, `replaceAt`, `removeAt`, `clear`) on `preservedTopLevelRecords`. Low-level helpers: `addPersonToReadGed`, `removePersonFromReadGedByIndi`. |
|
|
604
|
+
| `editGedcomExportOptions(opts)` / `GedcomExportOptionsEdit` | Fluent setters for `extraTopLevelRecords`, `headLanguageTag`, `headCopyright`, `headDestination`, `headSchemaTagDefs`. |
|
|
605
|
+
| `clonePerson(person, newIndi)` / `cloneAct(act)` / `person.clone` / `act.clone` | Deep copies for templates or undo stacks. |
|
|
606
|
+
| `nextFamilyId(persons)` | Next internal family id `F`. |
|
|
607
|
+
| `createMarriageFamily`, `linkChildToFamily`, `unlinkChildFromFamily`, `removeFamilyReferencesFromDataset` | Create family `F` and spouse acts; optional `{ eventTag }` (default `MARR`, also `ENGA`, banns, contract, `EVEN` + `CreateActInit`, …). See `GEDCOM_7_PAIR_UNION_EVENT_TAGS`. |
|
|
608
|
+
| `validateReadGed`, `assertReadGedConsistent`, `validatePerson`, `assertPersonConsistent` | Typed `code` / `severity` (`error` \| `warn`). Options: `checkMarrParticipants`, `checkDuplicateIndis`, `checkFamcWithoutSpouses`, `checkFamsWithoutSpouses`, `checkDuplicateFamsEntries`, `checkAncestorCycles`. Assertions: `failOn` (default: `error`). |
|
|
609
|
+
| `tryAddPersonToReadGed`, `tryRemovePersonFromReadGedByIndi`, `tryLinkChildToFamily`, `tryUnlinkChildFromFamily`, `tryCreateMarriageFamily`, `commandBlockingIssues` | Command layer with `CommandResult` / `validate*Command` prechecks. |
|
|
610
|
+
|
|
471
611
|
|
|
472
612
|
### Utilities
|
|
473
613
|
|
|
@@ -492,31 +632,9 @@ import { remainingTypesAct, Acts } from "gedcom-ts";
|
|
|
492
632
|
const available = remainingTypesAct(new Acts());
|
|
493
633
|
```
|
|
494
634
|
|
|
495
|
-
#### Geocoding (Nominatim)
|
|
496
|
-
|
|
497
|
-
Place search uses the official Nominatim endpoint `GET https://nominatim.openstreetmap.org/search` with `q`, `format=jsonv2`, and `addressdetails=1` (not the legacy `search.php?city=` API). A `User-Agent` header is required (`gedcom-ts/<version> (genealogy library)` by default).
|
|
498
|
-
|
|
499
|
-
```ts
|
|
500
|
-
import {
|
|
501
|
-
inferGeocodeContext,
|
|
502
|
-
searchPlacesWithContext,
|
|
503
|
-
rankGeocodeCandidates,
|
|
504
|
-
findHarmonizationClusters,
|
|
505
|
-
applyGeocodeCandidateToActs,
|
|
506
|
-
} from "gedcom-ts";
|
|
507
|
-
|
|
508
|
-
const context = inferGeocodeContext(ged);
|
|
509
|
-
const candidates = await searchPlacesWithContext("Valence", context);
|
|
510
|
-
// candidates are ranked (French tree → Valence FR before ES)
|
|
511
|
-
|
|
512
|
-
const clusters = findHarmonizationClusters(ged);
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
For tests or Node without a global `fetch`, pass `fetchFn` in options. Low-level API: `searchPlaces(query, { countryCodes, limit, userAgent, fetchFn })`.
|
|
516
|
-
|
|
517
635
|
#### `getCityCoordinates(cityName, callback)` (deprecated)
|
|
518
636
|
|
|
519
|
-
Legacy callback API
|
|
637
|
+
Legacy callback API. Use [Geocoding places](#geocoding-places) instead.
|
|
520
638
|
|
|
521
639
|
#### `resolveDatasetVersion(headerLines)` / `GedcomDatasetVersion`
|
|
522
640
|
|
|
@@ -561,7 +679,7 @@ const primary = selectPrimaryNameVariant(person.nameVariants);
|
|
|
561
679
|
|
|
562
680
|
#### `GEDCOM_LIBRARY_VERSION`
|
|
563
681
|
|
|
564
|
-
CalVer string
|
|
682
|
+
CalVer string written in exported `HEAD`.`SOUR`.`VERS` (same value as the npm package version, e.g. `2026.5.2`).
|
|
565
683
|
|
|
566
684
|
```ts
|
|
567
685
|
import { GEDCOM_LIBRARY_VERSION } from "gedcom-ts";
|
|
@@ -569,56 +687,6 @@ import { GEDCOM_LIBRARY_VERSION } from "gedcom-ts";
|
|
|
569
687
|
console.log(`gedcom-ts ${GEDCOM_LIBRARY_VERSION}`);
|
|
570
688
|
```
|
|
571
689
|
|
|
572
|
-
## End-to-end example
|
|
573
|
-
|
|
574
|
-
```ts
|
|
575
|
-
import {
|
|
576
|
-
importGedFile,
|
|
577
|
-
ExportGedzipFile,
|
|
578
|
-
editPerson,
|
|
579
|
-
DateAct,
|
|
580
|
-
Identifier,
|
|
581
|
-
} from "gedcom-ts";
|
|
582
|
-
|
|
583
|
-
async function importModifyExport(file: File) {
|
|
584
|
-
const readGed = await importGedFile(file);
|
|
585
|
-
const persons = readGed.persons;
|
|
586
|
-
|
|
587
|
-
if (persons.length > 0) {
|
|
588
|
-
editPerson(persons[0])
|
|
589
|
-
.setLastname(persons[0].lastname.toUpperCase())
|
|
590
|
-
.acts()
|
|
591
|
-
.at(0)
|
|
592
|
-
.setDateAct(new DateAct("1 JAN 1900"));
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
await new ExportGedzipFile("updated-tree", persons).download();
|
|
596
|
-
}
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
## Development
|
|
600
|
-
|
|
601
|
-
### Local scripts
|
|
602
|
-
|
|
603
|
-
| Script | Role |
|
|
604
|
-
| --- | --- |
|
|
605
|
-
| `npm run lint` | ESLint on the codebase |
|
|
606
|
-
| `npm run test` | Vitest test suite |
|
|
607
|
-
| `npm run build` | Production bundle + `.d.ts` |
|
|
608
|
-
| `npm run tgz` | `build` then `npm pack` (local `.tgz`) |
|
|
609
|
-
|
|
610
|
-
### GitLab CI (`.gitlab-ci.yml`)
|
|
611
|
-
|
|
612
|
-
Every pipeline runs **`InstallDependencies`** (`npm ci`, `node_modules` artifact) — required by all other jobs.
|
|
613
|
-
|
|
614
|
-
| Job | When it runs |
|
|
615
|
-
| --- | --- |
|
|
616
|
-
| `InstallDependencies` | Always |
|
|
617
|
-
| `Lint`, `Test` | Merge requests and branch pushes (not on `master` after a merge commit titled `Merge…`) |
|
|
618
|
-
| `BuildTgz`, `PublishNpm` | Push to `master` after merge only (`Merge…` commit title) |
|
|
619
|
-
|
|
620
|
-
Typical flow: open an MR from a release branch (e.g. `2026.5.0`) → lint + test run there → after merge to `master`, only package build and npm publish run (quality checks are not repeated).
|
|
621
|
-
|
|
622
690
|
## Error handling
|
|
623
691
|
|
|
624
692
|
```ts
|
|
@@ -635,3 +703,4 @@ try {
|
|
|
635
703
|
}
|
|
636
704
|
}
|
|
637
705
|
```
|
|
706
|
+
|
package/dist/geocode/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { normalizeCityKey, tidyCityDisplay, findCanonicalCityLabel, } from "./place-city-utils";
|
|
2
2
|
export { type GeocodeCandidate, type GeocodeContext, type GeocodeFetchFn, type GeocodeSearchOptions, NOMINATIM_SEARCH_URL, defaultGeocodeUserAgent, parseCityQuery, countryLabelToIso, isoToCountryLabel, inferGeocodeContext, buildGeocodeQuery, geocodeContextWithHint, rankGeocodeCandidates, searchPlaces, searchPlacesWithContext, } from "./place-geocode";
|
|
3
|
-
export { type CoordVariant, type CityHarmonizationCluster, levenshteinDistance, citiesAreSimilar, clusterKeyForCity, findHarmonizationClustersFromActs, findHarmonizationClusters, applyCoordinatesToActs, applyGeocodeCandidateToActs, } from "./place-clusters";
|
|
3
|
+
export { type CoordVariant, type CityHarmonizationCluster, type SimilarCityActGroup, type GroupActsBySimilarCityOptions, type FilterActsBySimilarCityOptions, levenshteinDistance, citiesAreSimilar, clusterKeyForCity, similarCityKey, groupActsBySimilarCity, filterActsBySimilarCity, actsNeedingGeocodeForCity, findHarmonizationClustersFromActs, findHarmonizationClusters, applyCoordinatesToActs, applyGeocodeCandidateToActs, } from "./place-clusters";
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { Act } from "../commons/Act";
|
|
2
2
|
import type { ReadGed } from "../import/ReadGed";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Égalité stricte de libellé (casse / espaces / NFKC uniquement).
|
|
5
|
+
* Pour regrouper des variantes (« Pleurtuit » vs « Pleurtuit, Ille-et-Vilaine »),
|
|
6
|
+
* utiliser {@link citiesAreSimilar}, {@link clusterKeyForCity} ou {@link groupActsBySimilarCity}.
|
|
7
|
+
*/
|
|
4
8
|
export declare function normalizeCityKey(city: string): string;
|
|
5
9
|
/** Libellé affiché : trim + espaces internes unifiés (sans forcer la casse). */
|
|
6
10
|
export declare function tidyCityDisplay(city: string): string;
|
|
7
11
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
12
|
+
* Libellé canonique par **égalité stricte** {@link normalizeCityKey} (pas `citiesAreSimilar`).
|
|
13
|
+
* Pour une ville « proche », préférer le `cityLabel` d’un {@link groupActsBySimilarCity}.
|
|
10
14
|
*/
|
|
11
15
|
export declare function findCanonicalCityLabel(ged: ReadGed, cityInput: string, excludeAct: Act | null): string;
|
|
@@ -14,13 +14,54 @@ export interface CityHarmonizationCluster {
|
|
|
14
14
|
readonly acts: readonly Act[];
|
|
15
15
|
readonly actsWithoutCoord: number;
|
|
16
16
|
}
|
|
17
|
+
/** Groupe d’actes partageant une même ville au sens {@link citiesAreSimilar}. */
|
|
18
|
+
export interface SimilarCityActGroup {
|
|
19
|
+
readonly cityLabel: string;
|
|
20
|
+
/** Même clé que {@link clusterKeyForCity} / carte. */
|
|
21
|
+
readonly clusterKey: string;
|
|
22
|
+
readonly acts: readonly Act[];
|
|
23
|
+
readonly withoutCoordCount: number;
|
|
24
|
+
}
|
|
25
|
+
export interface GroupActsBySimilarCityOptions {
|
|
26
|
+
/** Ne retient que les actes sans coordonnées GPS utilisables. */
|
|
27
|
+
readonly onlyWithoutCoordinates?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface FilterActsBySimilarCityOptions {
|
|
30
|
+
readonly onlyWithoutCoordinates?: boolean;
|
|
31
|
+
}
|
|
17
32
|
/** Distance de Levenshtein (petites chaînes de noms de villes). */
|
|
18
33
|
export declare function levenshteinDistance(a: string, b: string): number;
|
|
19
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Ville proche : même clé décorée, faute légère, ou inclusion évidente (Saint-X / St-X).
|
|
36
|
+
* **Règle unique** pour regrouper actes, carte et harmonisation.
|
|
37
|
+
*/
|
|
20
38
|
export declare function citiesAreSimilar(a: string, b: string): boolean;
|
|
21
39
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
40
|
+
* Clé stable pour la carte et les listes « par ville ».
|
|
41
|
+
* Utilise la localité principale (segment avant la première virgule), comme le regroupement
|
|
42
|
+
* par {@link citiesAreSimilar}, afin que « Pleurtuit » et « Pleurtuit, Ille-et-Vilaine, France »
|
|
43
|
+
* partagent la même clé.
|
|
44
|
+
*/
|
|
45
|
+
export declare function clusterKeyForCity(city: string): string;
|
|
46
|
+
/** Alias explicite de {@link clusterKeyForCity}. */
|
|
47
|
+
export declare const similarCityKey: typeof clusterKeyForCity;
|
|
48
|
+
/**
|
|
49
|
+
* Regroupe les actes par ville similaire (tri décroissant par nombre d’actes).
|
|
50
|
+
* Utiliser pour une UI « lieux sans position » (`onlyWithoutCoordinates: true`).
|
|
51
|
+
*/
|
|
52
|
+
export declare function groupActsBySimilarCity(acts: readonly Act[], options?: GroupActsBySimilarCityOptions): SimilarCityActGroup[];
|
|
53
|
+
/**
|
|
54
|
+
* Actes dont le lieu est similaire à `cityRef` (même logique que l’harmonisation / la carte).
|
|
55
|
+
*/
|
|
56
|
+
export declare function filterActsBySimilarCity(acts: readonly Act[], cityRef: string, options?: FilterActsBySimilarCityOptions): Act[];
|
|
57
|
+
/**
|
|
58
|
+
* Actes similaires à `cityRef` sans coordonnées GPS — à géolocaliser en une fois.
|
|
59
|
+
* Alias de `filterActsBySimilarCity(..., { onlyWithoutCoordinates: true })`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function actsNeedingGeocodeForCity(acts: readonly Act[], cityRef: string): Act[];
|
|
62
|
+
/**
|
|
63
|
+
* Conflits d’harmonisation : libellés multiples, positions GPS divergentes,
|
|
64
|
+
* ou mixte avec/sans coordonnées sur la même ville similaire.
|
|
24
65
|
*/
|
|
25
66
|
export declare function findHarmonizationClustersFromActs(acts: readonly Act[]): CityHarmonizationCluster[];
|
|
26
67
|
/** Analyse toute l’arbre (préférer {@link findHarmonizationClustersFromActs} sur un sous-ensemble). */
|
|
@@ -30,5 +71,3 @@ export declare function applyCoordinatesToActs(acts: readonly Act[], lat: number
|
|
|
30
71
|
country?: string | null;
|
|
31
72
|
}): void;
|
|
32
73
|
export declare function applyGeocodeCandidateToActs(acts: readonly Act[], candidate: GeocodeCandidate, preferredCityLabel?: string): void;
|
|
33
|
-
/** Clé de regroupement carte : villes similaires → un marqueur. */
|
|
34
|
-
export declare function clusterKeyForCity(city: string): string;
|