Fix demo family generation: add diverse events and fix missing event references

- Added 12 different event types (Baptism, Education, Occupation, etc.)
- Fixed missing event references by storing and reusing original events
- Made event generation deterministic with random seed
- Updated gen_person to return both XML and tuple format for event reuse
- All event references now properly defined and validated
- Demo family now includes 240+ additional events for comprehensive testing
This commit is contained in:
Daniel Viegas 2025-11-28 23:19:09 +01:00
parent 8be124f5e0
commit 0abe20849c
3 changed files with 2183 additions and 589 deletions

View File

@ -608,7 +608,7 @@ class MyTimelineView(NavigationView):
clicked_index = self.find_event_at_position(event.x, event.y) clicked_index = self.find_event_at_position(event.x, event.y)
if clicked_index is not None: if clicked_index is not None:
# Check if clicking on marker (for person selection) or label (for expansion) # Check if clicking on marker (for person selection) or label (for expansion)
date_sort, date_obj, clicked_event, clicked_person, event_type, expanded, _ = self.events[clicked_index] date_sort, date_obj, clicked_event, clicked_person, event_type, expanded, _y_pos = self.events[clicked_index]
# Check if click is on marker area (left side) or label area (right side) # Check if click is on marker area (left side) or label area (right side)
timeline_x = TIMELINE_MARGIN_LEFT timeline_x = TIMELINE_MARGIN_LEFT
@ -877,7 +877,7 @@ class MyTimelineView(NavigationView):
# Calculate initial Y positions based on dates # Calculate initial Y positions based on dates
events_with_y_pos = [] events_with_y_pos = []
for i, event_data in enumerate(self.events): for i, event_data in enumerate(self.events):
date_sort, date_obj, event, person, event_type, expanded, _ = event_data date_sort, date_obj, event, person, event_type, expanded, _y_pos = event_data
# Calculate Y position based on date # Calculate Y position based on date
y_pos = TIMELINE_MARGIN_TOP + ( y_pos = TIMELINE_MARGIN_TOP + (
@ -1226,7 +1226,7 @@ class MyTimelineView(NavigationView):
context.set_line_width(1.5) context.set_line_width(1.5)
# Create a smooth curve through all points # Create a smooth curve through all points
y_positions = [y for y, _ in person_events] y_positions = [y for y, _event_data in person_events]
min_y = min(y_positions) min_y = min(y_positions)
max_y = max(y_positions) max_y = max(y_positions)

File diff suppressed because it is too large Load Diff

View File

@ -6,13 +6,96 @@ Generate a huge demo family for Gramps testing
import random import random
from datetime import datetime, timedelta from datetime import datetime, timedelta
# Set seed for deterministic event generation
random.seed(42)
# Generate unique handles # Generate unique handles
def gen_handle(prefix, num): def gen_handle(prefix, num):
return f"_{prefix}{num:08d}" return f"_{prefix}{num:08d}"
# Event types to add
EVENT_TYPES = [
("Baptism", 0.7, 0, 2), # 70% chance, 0-2 years after birth
("Christening", 0.5, 0, 1), # 50% chance, 0-1 years after birth
("Education", 0.8, 5, 18), # 80% chance, 5-18 years after birth
("Graduation", 0.6, 18, 25), # 60% chance, 18-25 years after birth
("Occupation", 0.9, 18, 65), # 90% chance, 18-65 years after birth
("Military Service", 0.3, 18, 30), # 30% chance, 18-30 years after birth
("Residence", 0.7, 0, 80), # 70% chance, any time
("Emigration", 0.2, 20, 50), # 20% chance, 20-50 years after birth
("Immigration", 0.15, 20, 50), # 15% chance, 20-50 years after birth
("Retirement", 0.4, 60, 75), # 40% chance, 60-75 years after birth
("Burial", 0.6, None, None), # 60% chance if death exists, at death time
("Cremation", 0.2, None, None), # 20% chance if death exists, at death time
]
# Generate additional events for a person
def gen_additional_events(pid, first_name, surname, birth_year, death_year=None):
events = []
event_id_offset = pid * 10 + 2 # Start after birth and death events
for event_type, probability, min_years, max_years in EVENT_TYPES:
if random.random() > probability:
continue
# Special handling for death-related events
if event_type in ("Burial", "Cremation"):
if not death_year:
continue
event_year = death_year
event_month = random.randint(1, 12)
event_day = random.randint(1, 28)
else:
if max_years is None:
continue
event_year = birth_year + random.randint(min_years, max_years)
if death_year and event_year > death_year:
continue
event_month = random.randint(1, 12)
event_day = random.randint(1, 28)
event_handle = gen_handle("EVENT", event_id_offset)
event_id_offset += 1
# Generate description based on event type
if event_type == "Education":
description = f"Education - {first_name} {surname}"
elif event_type == "Graduation":
description = f"Graduation - {first_name} {surname}"
elif event_type == "Occupation":
occupations = ["Farmer", "Teacher", "Engineer", "Doctor", "Lawyer", "Merchant",
"Carpenter", "Blacksmith", "Sailor", "Soldier", "Clerk", "Nurse"]
occupation = random.choice(occupations)
description = f"{occupation} - {first_name} {surname}"
elif event_type == "Military Service":
description = f"Military Service - {first_name} {surname}"
elif event_type == "Residence":
places = ["New York", "London", "Paris", "Berlin", "Rome", "Madrid", "Amsterdam",
"Vienna", "Prague", "Warsaw", "Stockholm", "Copenhagen"]
place = random.choice(places)
description = f"Residence in {place} - {first_name} {surname}"
elif event_type == "Emigration":
description = f"Emigration - {first_name} {surname}"
elif event_type == "Immigration":
description = f"Immigration - {first_name} {surname}"
elif event_type == "Retirement":
description = f"Retirement - {first_name} {surname}"
else:
description = f"{event_type} of {surname}, {first_name}"
event_xml = f""" <event handle="{event_handle}" change="{int(datetime.now().timestamp())}" id="E{event_id_offset-1:04d}">
<type>{event_type}</type>
<dateval val="{event_year}-{event_month:02d}-{event_day:02d}"/>
<description>{description}</description>
</event>
"""
events.append((event_handle, event_xml))
return events
# Generate a person # Generate a person
def gen_person(pid, first_name, surname, birth_year, death_year=None, gender="M", def gen_person(pid, first_name, surname, birth_year, death_year=None, gender="M",
parentin_families=None, childof_families=None): parentin_families=None, childof_families=None, reuse_additional_events=None):
handle = gen_handle("PERSON", pid) handle = gen_handle("PERSON", pid)
birth_handle = gen_handle("EVENT", pid * 10) birth_handle = gen_handle("EVENT", pid * 10)
death_handle = gen_handle("EVENT", pid * 10 + 1) if death_year else None death_handle = gen_handle("EVENT", pid * 10 + 1) if death_year else None
@ -28,6 +111,18 @@ def gen_person(pid, first_name, surname, birth_year, death_year=None, gender="M"
if death_handle: if death_handle:
person_xml += f""" <eventref hlink="{death_handle}" role="Primary"/> person_xml += f""" <eventref hlink="{death_handle}" role="Primary"/>
""" """
# Add additional events - reuse if provided, otherwise generate new
if reuse_additional_events is not None:
# reuse_additional_events is a list of (handle, xml) tuples
additional_events = reuse_additional_events
else:
additional_events = gen_additional_events(pid, first_name, surname, birth_year, death_year)
for event_handle, _ in additional_events:
person_xml += f""" <eventref hlink="{event_handle}" role="Primary"/>
"""
# Add parentin references (for fathers and mothers) # Add parentin references (for fathers and mothers)
if parentin_families: if parentin_families:
for family_handle in parentin_families: for family_handle in parentin_families:
@ -63,7 +158,10 @@ def gen_person(pid, first_name, surname, birth_year, death_year=None, gender="M"
</event> </event>
""" """
return person_xml, birth_event, death_event # Collect all additional events (return tuples for reuse, XML strings for output)
all_additional_events_xml = [event_xml for _, event_xml in additional_events]
return person_xml, birth_event, death_event, all_additional_events_xml, additional_events
# Generate a family # Generate a family
def gen_family(fid, father_handle, mother_handle, marriage_year, children_handles): def gen_family(fid, father_handle, mother_handle, marriage_year, children_handles):
@ -122,7 +220,7 @@ def main():
father_id = 1 father_id = 1
father_handle = gen_handle("PERSON", father_id) father_handle = gen_handle("PERSON", father_id)
main_family_handle = gen_handle("FAMILY", 1) main_family_handle = gen_handle("FAMILY", 1)
father_person, father_birth, father_death = gen_person( father_person, father_birth, father_death, father_additional_xml, _ = gen_person(
father_id, "John", "Smith", 1950, 2010, "M", father_id, "John", "Smith", 1950, 2010, "M",
parentin_families=[main_family_handle] parentin_families=[main_family_handle]
) )
@ -130,15 +228,18 @@ def main():
# Mother: Mary Smith, born 1952, died 2015 # Mother: Mary Smith, born 1952, died 2015
mother_id = 2 mother_id = 2
mother_handle = gen_handle("PERSON", mother_id) mother_handle = gen_handle("PERSON", mother_id)
mother_person, mother_birth, mother_death = gen_person( mother_person, mother_birth, mother_death, mother_additional_xml, _ = gen_person(
mother_id, "Mary", "Smith", 1952, 2015, "F", mother_id, "Mary", "Smith", 1952, 2015, "F",
parentin_families=[main_family_handle] parentin_families=[main_family_handle]
) )
all_additional_events = father_additional_xml + mother_additional_xml
# Generate 15 children # Generate 15 children
children = [] children = []
child_handles = [] child_handles = []
child_events = [] child_events = []
child_additional_events_map = {} # Store additional events by child_id
child_id = 3 child_id = 3
for i in range(15): for i in range(15):
@ -148,7 +249,7 @@ def main():
death_year = birth_year + random.randint(60, 90) if random.random() < 0.3 else None # 30% chance of death death_year = birth_year + random.randint(60, 90) if random.random() < 0.3 else None # 30% chance of death
child_handle = gen_handle("PERSON", child_id) child_handle = gen_handle("PERSON", child_id)
child_person, child_birth, child_death = gen_person( child_person, child_birth, child_death, child_additional_xml, child_additional_tuples = gen_person(
child_id, first_name, "Smith", birth_year, death_year, gender, child_id, first_name, "Smith", birth_year, death_year, gender,
childof_families=[main_family_handle] childof_families=[main_family_handle]
) )
@ -158,6 +259,9 @@ def main():
child_events.append(child_birth) child_events.append(child_birth)
if child_death: if child_death:
child_events.append(child_death) child_events.append(child_death)
# Store tuples for reuse when regenerating
child_additional_events_map[child_id] = child_additional_tuples
all_additional_events.extend(child_additional_xml)
child_id += 1 child_id += 1
# Generate family # Generate family
@ -214,7 +318,7 @@ def main():
"birth": spouse_birth, "death": None, "gender": spouse_gender, "birth": spouse_birth, "death": None, "gender": spouse_gender,
"parentin": [child_family_handle], "childof": []} "parentin": [child_family_handle], "childof": []}
spouse_person, spouse_birth_event, spouse_death_event = gen_person( spouse_person, spouse_birth_event, spouse_death_event, spouse_additional_xml, _ = gen_person(
grandchild_id, spouse_name, "Smith", spouse_birth, None, spouse_gender, grandchild_id, spouse_name, "Smith", spouse_birth, None, spouse_gender,
parentin_families=[child_family_handle] parentin_families=[child_family_handle]
) )
@ -222,6 +326,7 @@ def main():
grandchild_events.append(spouse_birth_event) grandchild_events.append(spouse_birth_event)
if spouse_death_event: if spouse_death_event:
grandchild_events.append(spouse_death_event) grandchild_events.append(spouse_death_event)
all_additional_events.extend(spouse_additional_xml)
grandchild_id += 1 grandchild_id += 1
# Update parent to include parentin reference # Update parent to include parentin reference
@ -240,7 +345,7 @@ def main():
"birth": gchild_birth, "death": None, "gender": gchild_gender, "birth": gchild_birth, "death": None, "gender": gchild_gender,
"parentin": [], "childof": [child_family_handle]} "parentin": [], "childof": [child_family_handle]}
gchild_person, gchild_birth_event, gchild_death_event = gen_person( gchild_person, gchild_birth_event, gchild_death_event, gchild_additional_xml, _ = gen_person(
grandchild_id, gchild_name, "Smith", gchild_birth, None, gchild_gender, grandchild_id, gchild_name, "Smith", gchild_birth, None, gchild_gender,
childof_families=[child_family_handle] childof_families=[child_family_handle]
) )
@ -249,6 +354,7 @@ def main():
grandchild_events.append(gchild_birth_event) grandchild_events.append(gchild_birth_event)
if gchild_death_event: if gchild_death_event:
grandchild_events.append(gchild_death_event) grandchild_events.append(gchild_death_event)
all_additional_events.extend(gchild_additional_xml)
grandchild_id += 1 grandchild_id += 1
# Create family for this couple # Create family for this couple
@ -258,13 +364,17 @@ def main():
child_events.append(fam_marriage) child_events.append(fam_marriage)
# Regenerate children XMLs with updated family references # Regenerate children XMLs with updated family references
# We need to regenerate to update family references, but reuse the same events
children = [] children = []
for i, child_handle in enumerate(child_handles): for i, child_handle in enumerate(child_handles):
child_pid = 3 + i child_pid = 3 + i
data = person_data[child_pid] data = person_data[child_pid]
child_person, _, _ = gen_person( # Reuse the original additional events to ensure consistency
original_additional_events = child_additional_events_map.get(child_pid, [])
child_person, _, _, _, _ = gen_person(
child_pid, data["name"], data["surname"], data["birth"], data["death"], data["gender"], child_pid, data["name"], data["surname"], data["birth"], data["death"], data["gender"],
parentin_families=data["parentin"], childof_families=data["childof"] parentin_families=data["parentin"], childof_families=data["childof"],
reuse_additional_events=original_additional_events
) )
children.append(child_person) children.append(child_person)
@ -293,6 +403,8 @@ def main():
xml_content += event xml_content += event
for event in grandchild_events: for event in grandchild_events:
xml_content += event xml_content += event
for event in all_additional_events:
xml_content += event
xml_content += """ </events> xml_content += """ </events>
<people> <people>
@ -316,6 +428,7 @@ def main():
with open("demo_family.gramps", "w", encoding="utf-8") as f: with open("demo_family.gramps", "w", encoding="utf-8") as f:
f.write(xml_content) f.write(xml_content)
total_events = len(child_events) + len(grandchild_events) + len(all_additional_events)
print(f"Generated demo_family.gramps with:") print(f"Generated demo_family.gramps with:")
print(f" - 2 parents (John and Mary Smith)") print(f" - 2 parents (John and Mary Smith)")
print(f" - 15 children") print(f" - 15 children")
@ -323,6 +436,8 @@ def main():
print(f" - ~20 grandchildren") print(f" - ~20 grandchildren")
print(f" - Multiple families with marriage events") print(f" - Multiple families with marriage events")
print(f" - Birth and death events for all") print(f" - Birth and death events for all")
print(f" - {len(all_additional_events)} additional events (Baptism, Education, Occupation, etc.)")
print(f" - Total events: {total_events}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()