GEDCOM: duplicate-aware import + typed name/attribute mapping
Duplicate detection (the "merge / skip / overwrite" the user asked for):
- New POST /gedcom/preview dry-runs the file and flags incoming people that
resemble existing ones (name similarity via difflib + birth-year guard;
high/medium score). No writes.
- /gedcom/import takes default_action (new|skip|merge|overwrite) + per-xref
resolutions {xref: {action, target_id}}:
new create as a new person (current behavior)
skip link families to the existing person, copy nothing
merge attach the incoming names (as alternates), events, citations,
and notes onto the existing person
overwrite soft-delete the existing person, import the incoming one fresh
Relationship creation is deduped so a merge can't double an edge.
Richer record mapping (covers the user's repo's GEDCOM):
- Multiple NAME records honor their TYPE; _MARNM (and NICK) import as typed
alternate names — maiden stays primary, married becomes a "married" Name.
- RELI -> a "religion" event with the value in detail; OCCU/EDUC values too.
- NOTE -> person notes (and event notes); NOTE/RELI are no longer "unmapped".
- Export round-trips name TYPE.
Verified against the user's 2185-person export: 0 unmapped tags. 48 tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vendored
+108
-1
@@ -557,6 +557,27 @@ export interface paths {
|
||||
patch: operations["update_media_api_v1_trees__tree_id__media__media_id__patch"];
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/trees/{tree_id}/gedcom/preview": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Preview Gedcom
|
||||
* @description Dry run: report counts and incoming people that look like duplicates of
|
||||
* existing ones, so the user can choose how to resolve each before importing.
|
||||
*/
|
||||
post: operations["preview_gedcom_api_v1_trees__tree_id__gedcom_preview_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/trees/{tree_id}/gedcom/import": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -566,7 +587,12 @@ export interface paths {
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Import Gedcom */
|
||||
/**
|
||||
* Import Gedcom
|
||||
* @description Import a GEDCOM. ``default_action`` (new|skip|merge|overwrite) applies to
|
||||
* incoming people that match an existing one; ``resolutions`` is a JSON object
|
||||
* {xref: {action, target_id}} overriding it per record.
|
||||
*/
|
||||
post: operations["import_gedcom_api_v1_trees__tree_id__gedcom_import_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
@@ -599,6 +625,21 @@ export interface components {
|
||||
Body_import_gedcom_api_v1_trees__tree_id__gedcom_import_post: {
|
||||
/** File */
|
||||
file: string;
|
||||
/**
|
||||
* Default Action
|
||||
* @default new
|
||||
*/
|
||||
default_action?: string;
|
||||
/**
|
||||
* Resolutions
|
||||
* @default {}
|
||||
*/
|
||||
resolutions?: string;
|
||||
};
|
||||
/** Body_preview_gedcom_api_v1_trees__tree_id__gedcom_preview_post */
|
||||
Body_preview_gedcom_api_v1_trees__tree_id__gedcom_preview_post: {
|
||||
/** File */
|
||||
file: string;
|
||||
};
|
||||
/** Body_upload_media_api_v1_trees__tree_id__media_post */
|
||||
Body_upload_media_api_v1_trees__tree_id__media_post: {
|
||||
@@ -683,6 +724,26 @@ export interface components {
|
||||
detail?: string | null;
|
||||
confidence?: components["schemas"]["CitationConfidence"] | null;
|
||||
};
|
||||
/** DuplicateMatch */
|
||||
DuplicateMatch: {
|
||||
/** Xref */
|
||||
xref: string;
|
||||
/** Incoming Name */
|
||||
incoming_name: string;
|
||||
/** Incoming Birth Year */
|
||||
incoming_birth_year?: string | null;
|
||||
/**
|
||||
* Existing Person Id
|
||||
* Format: uuid
|
||||
*/
|
||||
existing_person_id: string;
|
||||
/** Existing Name */
|
||||
existing_name: string;
|
||||
/** Existing Birth Year */
|
||||
existing_birth_year?: string | null;
|
||||
/** Score */
|
||||
score: string;
|
||||
};
|
||||
/** EventCreate */
|
||||
EventCreate: {
|
||||
/** Event Type */
|
||||
@@ -777,6 +838,17 @@ export interface components {
|
||||
/** Detail */
|
||||
detail?: components["schemas"]["ValidationError"][];
|
||||
};
|
||||
/** ImportPreview */
|
||||
ImportPreview: {
|
||||
/** Counts */
|
||||
counts: {
|
||||
[key: string]: number;
|
||||
};
|
||||
/** Potential Duplicates */
|
||||
potential_duplicates: components["schemas"]["DuplicateMatch"][];
|
||||
/** Unmapped Tags */
|
||||
unmapped_tags: string[];
|
||||
};
|
||||
/** ImportReport */
|
||||
ImportReport: {
|
||||
/** Counts */
|
||||
@@ -2845,6 +2917,41 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
preview_gedcom_api_v1_trees__tree_id__gedcom_preview_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
tree_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"multipart/form-data": components["schemas"]["Body_preview_gedcom_api_v1_trees__tree_id__gedcom_preview_post"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ImportPreview"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
import_gedcom_api_v1_trees__tree_id__gedcom_import_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
Reference in New Issue
Block a user