aa62ca490e
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>
92 lines
3.2 KiB
Python
92 lines
3.2 KiB
Python
import uuid
|
|
|
|
from fastapi import APIRouter, File, UploadFile
|
|
|
|
from app.api.deps import CurrentUser, SessionDep
|
|
from app.schemas.cleanup import (
|
|
CleanupResult,
|
|
DeceasedApply,
|
|
DeceasedCandidate,
|
|
GenderApply,
|
|
GenderProposal,
|
|
NameApply,
|
|
NameIssue,
|
|
)
|
|
from app.services import cleanup_service, tree_service
|
|
|
|
router = APIRouter(prefix="/trees", tags=["cleanup"])
|
|
|
|
|
|
@router.get("/{tree_id}/cleanup/deceased", response_model=list[DeceasedCandidate])
|
|
async def preview_deceased(
|
|
tree_id: uuid.UUID,
|
|
session: SessionDep,
|
|
current: CurrentUser,
|
|
born_on_or_before: int = 1930,
|
|
) -> list[DeceasedCandidate]:
|
|
tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id)
|
|
rows = await cleanup_service.preview_deceased(
|
|
session, actor=current, tree=tree, year=born_on_or_before
|
|
)
|
|
return [DeceasedCandidate(**r) for r in rows]
|
|
|
|
|
|
@router.post("/{tree_id}/cleanup/deceased", response_model=CleanupResult)
|
|
async def apply_deceased(
|
|
tree_id: uuid.UUID, data: DeceasedApply, session: SessionDep, current: CurrentUser
|
|
) -> CleanupResult:
|
|
tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id)
|
|
n = await cleanup_service.apply_deceased(
|
|
session, actor=current, tree=tree, person_ids=data.person_ids
|
|
)
|
|
return CleanupResult(updated=n)
|
|
|
|
|
|
@router.post("/{tree_id}/cleanup/gender/preview", response_model=list[GenderProposal])
|
|
async def preview_gender(
|
|
tree_id: uuid.UUID,
|
|
session: SessionDep,
|
|
current: CurrentUser,
|
|
file: UploadFile = File(...),
|
|
) -> list[GenderProposal]:
|
|
tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id)
|
|
text = (await file.read()).decode("utf-8", errors="replace")
|
|
rows = await cleanup_service.preview_gender(
|
|
session, actor=current, tree=tree, gedcom_text=text
|
|
)
|
|
return [GenderProposal(**r) for r in rows]
|
|
|
|
|
|
@router.post("/{tree_id}/cleanup/gender", response_model=CleanupResult)
|
|
async def apply_gender(
|
|
tree_id: uuid.UUID, data: GenderApply, session: SessionDep, current: CurrentUser
|
|
) -> CleanupResult:
|
|
tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id)
|
|
n = await cleanup_service.apply_gender(
|
|
session,
|
|
actor=current,
|
|
tree=tree,
|
|
updates=[u.model_dump() for u in data.updates],
|
|
)
|
|
return CleanupResult(updated=n)
|
|
|
|
|
|
@router.get("/{tree_id}/cleanup/names", response_model=list[NameIssue])
|
|
async def preview_names(
|
|
tree_id: uuid.UUID, session: SessionDep, current: CurrentUser
|
|
) -> list[NameIssue]:
|
|
tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id)
|
|
rows = await cleanup_service.preview_names(session, actor=current, tree=tree)
|
|
return [NameIssue(**r) for r in rows]
|
|
|
|
|
|
@router.post("/{tree_id}/cleanup/names", response_model=CleanupResult)
|
|
async def apply_names(
|
|
tree_id: uuid.UUID, data: NameApply, session: SessionDep, current: CurrentUser
|
|
) -> CleanupResult:
|
|
tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id)
|
|
n = await cleanup_service.apply_names(
|
|
session, actor=current, tree=tree, edits=[e.model_dump() for e in data.edits]
|
|
)
|
|
return CleanupResult(updated=n)
|