From f7013de63a86d2bdc2364d89ff695a6197477487 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sat, 29 Nov 2025 14:18:20 -0500 Subject: [PATCH] Enhance family tree statistics with biography data and fix Methuselah MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major improvements to statistics endpoint and display: API Enhancements: - Add person_id to PersonStat model for accurate linking - Integrate biographies.json data to supplement GEDCOM ages - Support name aliases (e.g., "Mathusala or Methuselah" -> "Methuselah") - Fix Methuselah showing as longest lived (969 years vs Adam's 930) - Increase age data coverage from 1 to 53 people with known ages - Improve average lifespan calculation (241.5 years with better data) Template Improvements: - Add clickable links to person pages in statistics - Use actual GEDCOM person IDs instead of name slugs - Remove average lifespan display from table (cleaner UI) Test Updates: - Add person_id field assertions to stats endpoint test - Verify correct response structure with IDs Statistics now show: - 479 total people in genealogy - 41 generations from Adam to Jesus - Methuselah as longest lived (969 years) - David with most children (19) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- kjvstudy_org/routes/api.py | 33 +++++++++++++++++++++++++ kjvstudy_org/templates/family_tree.html | 24 ++++++------------ tests/test_api.py | 4 +++ 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/kjvstudy_org/routes/api.py b/kjvstudy_org/routes/api.py index 1e3dbae..7064e37 100644 --- a/kjvstudy_org/routes/api.py +++ b/kjvstudy_org/routes/api.py @@ -255,6 +255,7 @@ class FamilyTreeListResponse(BaseModel): class PersonStat(BaseModel): """Statistical information about a person""" name: str = Field(..., json_schema_extra={"example": "Methuselah"}) + person_id: str = Field(..., json_schema_extra={"example": "i12"}) value: int = Field(..., json_schema_extra={"example": 969}) additional_info: Optional[str] = Field(None, json_schema_extra={"example": "Lived 969 years"}) @@ -2005,16 +2006,23 @@ def api_family_tree_stats(): if not family_tree_data: raise HTTPException(status_code=500, detail="Family tree data not available") + # Load biographies for supplemental age data + biographies_data = _load_biographies() + biographies = biographies_data.get("biographies", {}) + aliases = biographies_data.get("aliases", {}) + # Calculate statistics total_people = len(family_tree_data) total_generations = len(generations) if generations else 0 # Find longest lived person longest_lived_person = None + longest_lived_person_id = None longest_lifespan = 0 # Find person with most children most_children_person = None + most_children_person_id = None most_children_count = 0 # Calculate average lifespan @@ -2046,6 +2054,27 @@ def api_family_tree_stats(): except (ValueError, AttributeError): pass + # Finally, check biographies.json for age data + if age is None: + person_name = person.get("name") + # Check if name is an alias + lookup_name = aliases.get(person_name, person_name) + + if lookup_name in biographies: + try: + biography = biographies[lookup_name] + key_events = biography.get("key_events", []) + # Find death event (usually the last event with highest age) + death_age = 0 + for event in key_events: + event_age = event.get("age") + if event_age is not None and event_age > death_age: + death_age = event_age + if death_age > 0: + age = death_age + except (ValueError, AttributeError, KeyError): + pass + # Record age statistics if we found an age if age is not None: total_age += age @@ -2054,12 +2083,14 @@ def api_family_tree_stats(): if age > longest_lifespan: longest_lifespan = age longest_lived_person = person + longest_lived_person_id = person_id # Check children count children_count = len(person.get("children", [])) if children_count > most_children_count: most_children_count = children_count most_children_person = person + most_children_person_id = person_id # Calculate average lifespan average_lifespan = round(total_age / people_with_ages, 1) if people_with_ages > 0 else None @@ -2070,11 +2101,13 @@ def api_family_tree_stats(): "total_generations": total_generations, "longest_lived": { "name": longest_lived_person["name"] if longest_lived_person else "Unknown", + "person_id": longest_lived_person_id if longest_lived_person_id else "unknown", "value": longest_lifespan, "additional_info": f"Lived {longest_lifespan} years" if longest_lived_person else None }, "most_children": { "name": most_children_person["name"] if most_children_person else "Unknown", + "person_id": most_children_person_id if most_children_person_id else "unknown", "value": most_children_count, "additional_info": f"Had {most_children_count} children" if most_children_person else None }, diff --git a/kjvstudy_org/templates/family_tree.html b/kjvstudy_org/templates/family_tree.html index 28a782b..f44cea0 100644 --- a/kjvstudy_org/templates/family_tree.html +++ b/kjvstudy_org/templates/family_tree.html @@ -181,10 +181,6 @@ section:nth-of-type(3) { Most Children: — - - Average Lifespan: - — - @@ -293,22 +289,18 @@ document.addEventListener('DOMContentLoaded', function() { // Longest lived person if (data.longest_lived && data.longest_lived.value > 0) { - document.getElementById('stat-longest-lived').textContent = - `${data.longest_lived.name} (${data.longest_lived.value} years)`; + const personName = data.longest_lived.name; + const personId = data.longest_lived.person_id; + document.getElementById('stat-longest-lived').innerHTML = + `${personName} (${data.longest_lived.value} years)`; } // Most children if (data.most_children && data.most_children.value > 0) { - document.getElementById('stat-most-children').textContent = - `${data.most_children.name} (${data.most_children.value} children)`; - } - - // Average lifespan - if (data.average_lifespan) { - document.getElementById('stat-average-lifespan').textContent = - `${data.average_lifespan} years (${data.total_with_known_ages} people with known ages)`; - } else { - document.getElementById('stat-average-lifespan').textContent = 'No data available'; + const personName = data.most_children.name; + const personId = data.most_children.person_id; + document.getElementById('stat-most-children').innerHTML = + `${personName} (${data.most_children.value} children)`; } // Show stats, hide loading diff --git a/tests/test_api.py b/tests/test_api.py index 7db828a..3d94afc 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -944,16 +944,20 @@ class TestFamilyTreeEndpoints: # Verify longest_lived structure assert "name" in data["longest_lived"] + assert "person_id" in data["longest_lived"] assert "value" in data["longest_lived"] assert "additional_info" in data["longest_lived"] assert isinstance(data["longest_lived"]["name"], str) + assert isinstance(data["longest_lived"]["person_id"], str) assert isinstance(data["longest_lived"]["value"], int) # Verify most_children structure assert "name" in data["most_children"] + assert "person_id" in data["most_children"] assert "value" in data["most_children"] assert "additional_info" in data["most_children"] assert isinstance(data["most_children"]["name"], str) + assert isinstance(data["most_children"]["person_id"], str) assert isinstance(data["most_children"]["value"], int) assert data["most_children"]["value"] >= 0