mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 23:00:16 +00:00
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:
@@ -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
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user