Cleanup tool: "mark deceased by a child's birth year" rule

Adds a preview/apply rule to the Cleanup tool for parents who have NO birth date
of their own (so the existing born-on-or-before rule can't reach them) but who
have a child born long ago — they're necessarily deceased. This is the gap that
left ~56 parents in the Paul tree as "unknown".

- cleanup_service.preview_deceased_by_child(year): parents of any child born
  on/before the cutoff, excluding already-deceased; returns child_birth_year.
- GET /trees/{id}/cleanup/deceased-by-child?born_on_or_before=1900. Apply reuses
  the existing POST .../cleanup/deceased (same audited mark-deceased path).
- Frontend: a new card in the Cleanup tool (year input → preview → select →
  apply), preview-first like the rest of the tool.

Test covers preview (finds the no-birthdate parent of a pre-cutoff child,
excludes modern-child parents), child_birth_year, apply, and re-preview drop.
Suite 106 passing.

Signed-off-by: Justin Paul <justin@jpaul.me>
This commit is contained in:
2026-06-11 11:08:50 -04:00
parent e24a7cfcc9
commit 1340d1957f
7 changed files with 342 additions and 0 deletions
+66
View File
@@ -718,6 +718,27 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/v1/trees/{tree_id}/cleanup/deceased-by-child": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Preview Deceased By Child
* @description People with a child born on/before the cutoff — necessarily deceased even
* when their own birth date is missing. Apply via POST .../cleanup/deceased.
*/
get: operations["preview_deceased_by_child_api_v1_trees__tree_id__cleanup_deceased_by_child_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/trees/{tree_id}/cleanup/gender/preview": {
parameters: {
query?: never;
@@ -1286,6 +1307,18 @@ export interface components {
/** Person Ids */
person_ids: string[];
};
/** DeceasedByChildCandidate */
DeceasedByChildCandidate: {
/**
* Person Id
* Format: uuid
*/
person_id: string;
/** Name */
name: string;
/** Child Birth Year */
child_birth_year: number;
};
/** DeceasedCandidate */
DeceasedCandidate: {
/**
@@ -4013,6 +4046,39 @@ export interface operations {
};
};
};
preview_deceased_by_child_api_v1_trees__tree_id__cleanup_deceased_by_child_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"]["DeceasedByChildCandidate"][];
};
};
/** @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;