Tree Cleanup tool: bulk fixes with preview → approve
A new per-tree Cleanup page (and cleanup_service + endpoints), each fix preview-first per the propose-then-approve rule: - Mark deceased by birth year: lists people born ≤ a cutoff (default 1930) not already deceased; apply sets is_living=false for the ones you keep checked. - Set sex from a source GEDCOM: upload the source .ged (it carries SEX); matches by name and proposes sex only where it's missing — far more accurate than guessing from first names. Review, then apply. - Names that look broken: flags date-in-surname / date-in-given / no-surname / packed given names, with inline editable given+surname; fix the checked ones. No migration (uses existing columns). 55 backend tests pass (preview+apply for all three); frontend builds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vendored
+364
@@ -679,6 +679,76 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/trees/{tree_id}/cleanup/deceased": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Preview Deceased */
|
||||
get: operations["preview_deceased_api_v1_trees__tree_id__cleanup_deceased_get"];
|
||||
put?: never;
|
||||
/** Apply Deceased */
|
||||
post: operations["apply_deceased_api_v1_trees__tree_id__cleanup_deceased_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/trees/{tree_id}/cleanup/gender/preview": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Preview Gender */
|
||||
post: operations["preview_gender_api_v1_trees__tree_id__cleanup_gender_preview_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/trees/{tree_id}/cleanup/gender": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Apply Gender */
|
||||
post: operations["apply_gender_api_v1_trees__tree_id__cleanup_gender_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/trees/{tree_id}/cleanup/names": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Preview Names */
|
||||
get: operations["preview_names_api_v1_trees__tree_id__cleanup_names_get"];
|
||||
put?: never;
|
||||
/** Apply Names */
|
||||
post: operations["apply_names_api_v1_trees__tree_id__cleanup_names_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
}
|
||||
export type webhooks = Record<string, never>;
|
||||
export interface components {
|
||||
@@ -713,6 +783,11 @@ export interface components {
|
||||
/** File */
|
||||
file: string;
|
||||
};
|
||||
/** Body_preview_gender_api_v1_trees__tree_id__cleanup_gender_preview_post */
|
||||
Body_preview_gender_api_v1_trees__tree_id__cleanup_gender_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: {
|
||||
/** File */
|
||||
@@ -796,6 +871,28 @@ export interface components {
|
||||
detail?: string | null;
|
||||
confidence?: components["schemas"]["CitationConfidence"] | null;
|
||||
};
|
||||
/** CleanupResult */
|
||||
CleanupResult: {
|
||||
/** Updated */
|
||||
updated: number;
|
||||
};
|
||||
/** DeceasedApply */
|
||||
DeceasedApply: {
|
||||
/** Person Ids */
|
||||
person_ids: string[];
|
||||
};
|
||||
/** DeceasedCandidate */
|
||||
DeceasedCandidate: {
|
||||
/**
|
||||
* Person Id
|
||||
* Format: uuid
|
||||
*/
|
||||
person_id: string;
|
||||
/** Name */
|
||||
name: string;
|
||||
/** Birth Year */
|
||||
birth_year: number;
|
||||
};
|
||||
/** DuplicateMatch */
|
||||
DuplicateMatch: {
|
||||
/** Xref */
|
||||
@@ -905,6 +1002,33 @@ export interface components {
|
||||
/** Notes */
|
||||
notes?: string | null;
|
||||
};
|
||||
/** GenderApply */
|
||||
GenderApply: {
|
||||
/** Updates */
|
||||
updates: components["schemas"]["GenderUpdate"][];
|
||||
};
|
||||
/** GenderProposal */
|
||||
GenderProposal: {
|
||||
/**
|
||||
* Person Id
|
||||
* Format: uuid
|
||||
*/
|
||||
person_id: string;
|
||||
/** Name */
|
||||
name: string;
|
||||
/** Proposed Gender */
|
||||
proposed_gender: string;
|
||||
};
|
||||
/** GenderUpdate */
|
||||
GenderUpdate: {
|
||||
/**
|
||||
* Person Id
|
||||
* Format: uuid
|
||||
*/
|
||||
person_id: string;
|
||||
/** Gender */
|
||||
gender: string;
|
||||
};
|
||||
/** HTTPValidationError */
|
||||
HTTPValidationError: {
|
||||
/** Detail */
|
||||
@@ -984,6 +1108,11 @@ export interface components {
|
||||
/** Source Id */
|
||||
source_id?: string | null;
|
||||
};
|
||||
/** NameApply */
|
||||
NameApply: {
|
||||
/** Edits */
|
||||
edits: components["schemas"]["NameEdit"][];
|
||||
};
|
||||
/** NameCreate */
|
||||
NameCreate: {
|
||||
/**
|
||||
@@ -1007,6 +1136,37 @@ export interface components {
|
||||
*/
|
||||
is_primary?: boolean;
|
||||
};
|
||||
/** NameEdit */
|
||||
NameEdit: {
|
||||
/**
|
||||
* Name Id
|
||||
* Format: uuid
|
||||
*/
|
||||
name_id: string;
|
||||
/** Given */
|
||||
given?: string | null;
|
||||
/** Surname */
|
||||
surname?: string | null;
|
||||
};
|
||||
/** NameIssue */
|
||||
NameIssue: {
|
||||
/**
|
||||
* Name Id
|
||||
* Format: uuid
|
||||
*/
|
||||
name_id: string;
|
||||
/**
|
||||
* Person Id
|
||||
* Format: uuid
|
||||
*/
|
||||
person_id: string;
|
||||
/** Given */
|
||||
given?: string | null;
|
||||
/** Surname */
|
||||
surname?: string | null;
|
||||
/** Issue */
|
||||
issue: string;
|
||||
};
|
||||
/** NameRead */
|
||||
NameRead: {
|
||||
/**
|
||||
@@ -3218,4 +3378,208 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
preview_deceased_api_v1_trees__tree_id__cleanup_deceased_get: {
|
||||
parameters: {
|
||||
query?: {
|
||||
born_on_or_before?: number;
|
||||
};
|
||||
header?: never;
|
||||
path: {
|
||||
tree_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["DeceasedCandidate"][];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
apply_deceased_api_v1_trees__tree_id__cleanup_deceased_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
tree_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["DeceasedApply"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["CleanupResult"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
preview_gender_api_v1_trees__tree_id__cleanup_gender_preview_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
tree_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"multipart/form-data": components["schemas"]["Body_preview_gender_api_v1_trees__tree_id__cleanup_gender_preview_post"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["GenderProposal"][];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
apply_gender_api_v1_trees__tree_id__cleanup_gender_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
tree_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["GenderApply"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["CleanupResult"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
preview_names_api_v1_trees__tree_id__cleanup_names_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
tree_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["NameIssue"][];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
apply_names_api_v1_trees__tree_id__cleanup_names_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
tree_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["NameApply"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["CleanupResult"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user