Enhance family tree statistics with biography data and fix Methuselah

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 <noreply@anthropic.com>
This commit is contained in:
2025-11-29 14:18:20 -05:00
parent decc341452
commit f7013de63a
3 changed files with 45 additions and 16 deletions
+33
View File
@@ -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
},
+8 -16
View File
@@ -181,10 +181,6 @@ section:nth-of-type(3) {
<td>Most Children:</td>
<td id="stat-most-children"></td>
</tr>
<tr>
<td>Average Lifespan:</td>
<td id="stat-average-lifespan"></td>
</tr>
</tbody>
</table>
</section>
@@ -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 =
`<a href="/family-tree/person/${personId}">${personName}</a> (${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 =
`<a href="/family-tree/person/${personId}">${personName}</a> (${data.most_children.value} children)`;
}
// Show stats, hide loading
+4
View File
@@ -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