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:
@@ -133,6 +133,51 @@ async def apply_deceased(
|
||||
return len(persons)
|
||||
|
||||
|
||||
# ---- 1b. Mark deceased by a CHILD's birth year -------------------------------------
|
||||
# For parents whose own birth date is missing (so the birth-year rule can't reach
|
||||
# them) but who have a child born long ago — they're necessarily deceased. Applies
|
||||
# through the same apply_deceased() path.
|
||||
|
||||
async def preview_deceased_by_child(
|
||||
session: AsyncSession, *, actor: User, tree: Tree, year: int
|
||||
) -> list[dict]:
|
||||
await _require_editor(session, actor=actor, tree=tree)
|
||||
names = await _primary_name_by_person(session, tree.id)
|
||||
years = await _birth_year_by_person(session, tree.id)
|
||||
rels = (
|
||||
await session.execute(
|
||||
select(Relationship).where(
|
||||
Relationship.tree_id == tree.id,
|
||||
Relationship.deleted_at.is_(None),
|
||||
Relationship.type == RelationshipType.parent_child,
|
||||
)
|
||||
)
|
||||
).scalars().all()
|
||||
# parent id -> earliest child birth year, among children born on/before `year`.
|
||||
earliest_child: dict[uuid.UUID, int] = {}
|
||||
for r in rels:
|
||||
cy = years.get(r.person_to_id) # the child's birth year
|
||||
if cy is None or cy > year:
|
||||
continue
|
||||
if r.person_from_id not in earliest_child or cy < earliest_child[r.person_from_id]:
|
||||
earliest_child[r.person_from_id] = cy
|
||||
persons = {p.id: p for p in await _persons(session, tree.id)}
|
||||
out: list[dict] = []
|
||||
for parent_id, cy in earliest_child.items():
|
||||
p = persons.get(parent_id)
|
||||
if p is None or p.is_living is False: # gone or already deceased
|
||||
continue
|
||||
out.append(
|
||||
{
|
||||
"person_id": str(parent_id),
|
||||
"name": _display(names.get(parent_id)),
|
||||
"child_birth_year": cy,
|
||||
}
|
||||
)
|
||||
out.sort(key=lambda r: r["child_birth_year"])
|
||||
return out
|
||||
|
||||
|
||||
# ---- 2. Re-derive gender from a source GEDCOM (matches by name) ----------------------
|
||||
|
||||
async def preview_gender(
|
||||
|
||||
Reference in New Issue
Block a user