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:
@@ -2701,6 +2701,322 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/trees/{tree_id}/cleanup/deceased": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"cleanup"
|
||||
],
|
||||
"summary": "Preview Deceased",
|
||||
"operationId": "preview_deceased_api_v1_trees__tree_id__cleanup_deceased_get",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tree_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Tree Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "born_on_or_before",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 1930,
|
||||
"title": "Born On Or Before"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DeceasedCandidate"
|
||||
},
|
||||
"title": "Response Preview Deceased Api V1 Trees Tree Id Cleanup Deceased Get"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"cleanup"
|
||||
],
|
||||
"summary": "Apply Deceased",
|
||||
"operationId": "apply_deceased_api_v1_trees__tree_id__cleanup_deceased_post",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tree_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Tree Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/DeceasedApply"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CleanupResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/trees/{tree_id}/cleanup/gender/preview": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"cleanup"
|
||||
],
|
||||
"summary": "Preview Gender",
|
||||
"operationId": "preview_gender_api_v1_trees__tree_id__cleanup_gender_preview_post",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tree_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Tree Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Body_preview_gender_api_v1_trees__tree_id__cleanup_gender_preview_post"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/GenderProposal"
|
||||
},
|
||||
"title": "Response Preview Gender Api V1 Trees Tree Id Cleanup Gender Preview Post"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/trees/{tree_id}/cleanup/gender": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"cleanup"
|
||||
],
|
||||
"summary": "Apply Gender",
|
||||
"operationId": "apply_gender_api_v1_trees__tree_id__cleanup_gender_post",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tree_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Tree Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GenderApply"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CleanupResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/trees/{tree_id}/cleanup/names": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"cleanup"
|
||||
],
|
||||
"summary": "Preview Names",
|
||||
"operationId": "preview_names_api_v1_trees__tree_id__cleanup_names_get",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tree_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Tree Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/NameIssue"
|
||||
},
|
||||
"title": "Response Preview Names Api V1 Trees Tree Id Cleanup Names Get"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"cleanup"
|
||||
],
|
||||
"summary": "Apply Names",
|
||||
"operationId": "apply_names_api_v1_trees__tree_id__cleanup_names_post",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tree_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Tree Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NameApply"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CleanupResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
@@ -2770,6 +3086,20 @@
|
||||
],
|
||||
"title": "Body_preview_gedcom_api_v1_trees__tree_id__gedcom_preview_post"
|
||||
},
|
||||
"Body_preview_gender_api_v1_trees__tree_id__cleanup_gender_preview_post": {
|
||||
"properties": {
|
||||
"file": {
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
"title": "File"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"file"
|
||||
],
|
||||
"title": "Body_preview_gender_api_v1_trees__tree_id__cleanup_gender_preview_post"
|
||||
},
|
||||
"Body_upload_media_api_v1_trees__tree_id__media_post": {
|
||||
"properties": {
|
||||
"file": {
|
||||
@@ -3091,6 +3421,60 @@
|
||||
"type": "object",
|
||||
"title": "CitationUpdate"
|
||||
},
|
||||
"CleanupResult": {
|
||||
"properties": {
|
||||
"updated": {
|
||||
"type": "integer",
|
||||
"title": "Updated"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"updated"
|
||||
],
|
||||
"title": "CleanupResult"
|
||||
},
|
||||
"DeceasedApply": {
|
||||
"properties": {
|
||||
"person_ids": {
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Person Ids"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"person_ids"
|
||||
],
|
||||
"title": "DeceasedApply"
|
||||
},
|
||||
"DeceasedCandidate": {
|
||||
"properties": {
|
||||
"person_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Person Id"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Name"
|
||||
},
|
||||
"birth_year": {
|
||||
"type": "integer",
|
||||
"title": "Birth Year"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"person_id",
|
||||
"name",
|
||||
"birth_year"
|
||||
],
|
||||
"title": "DeceasedCandidate"
|
||||
},
|
||||
"DuplicateMatch": {
|
||||
"properties": {
|
||||
"xref": {
|
||||
@@ -3526,6 +3910,65 @@
|
||||
"type": "object",
|
||||
"title": "EventUpdate"
|
||||
},
|
||||
"GenderApply": {
|
||||
"properties": {
|
||||
"updates": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/GenderUpdate"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Updates"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"updates"
|
||||
],
|
||||
"title": "GenderApply"
|
||||
},
|
||||
"GenderProposal": {
|
||||
"properties": {
|
||||
"person_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Person Id"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Name"
|
||||
},
|
||||
"proposed_gender": {
|
||||
"type": "string",
|
||||
"title": "Proposed Gender"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"person_id",
|
||||
"name",
|
||||
"proposed_gender"
|
||||
],
|
||||
"title": "GenderProposal"
|
||||
},
|
||||
"GenderUpdate": {
|
||||
"properties": {
|
||||
"person_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Person Id"
|
||||
},
|
||||
"gender": {
|
||||
"type": "string",
|
||||
"title": "Gender"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"person_id",
|
||||
"gender"
|
||||
],
|
||||
"title": "GenderUpdate"
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"properties": {
|
||||
"detail": {
|
||||
@@ -3774,6 +4217,22 @@
|
||||
"type": "object",
|
||||
"title": "MediaUpdate"
|
||||
},
|
||||
"NameApply": {
|
||||
"properties": {
|
||||
"edits": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/NameEdit"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Edits"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"edits"
|
||||
],
|
||||
"title": "NameApply"
|
||||
},
|
||||
"NameCreate": {
|
||||
"properties": {
|
||||
"name_type": {
|
||||
@@ -3845,6 +4304,89 @@
|
||||
"type": "object",
|
||||
"title": "NameCreate"
|
||||
},
|
||||
"NameEdit": {
|
||||
"properties": {
|
||||
"name_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Name Id"
|
||||
},
|
||||
"given": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Given"
|
||||
},
|
||||
"surname": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Surname"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name_id"
|
||||
],
|
||||
"title": "NameEdit"
|
||||
},
|
||||
"NameIssue": {
|
||||
"properties": {
|
||||
"name_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Name Id"
|
||||
},
|
||||
"person_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Person Id"
|
||||
},
|
||||
"given": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Given"
|
||||
},
|
||||
"surname": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Surname"
|
||||
},
|
||||
"issue": {
|
||||
"type": "string",
|
||||
"title": "Issue"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name_id",
|
||||
"person_id",
|
||||
"issue"
|
||||
],
|
||||
"title": "NameIssue"
|
||||
},
|
||||
"NameRead": {
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
Reference in New Issue
Block a user