Lessons from a Python CSV Seed Import Disaster

Day 96 of 100 Days Coding Challenge: Python

Today was less about writing shiny new features and more about wrestling with files until the app behaved like it did back on Day 19. After nearly four hours of tinkering yesterday, I had to step away—full-time jobs and real life have a way of enforcing time limits. But I was annoyed at myself for one thing: forgetting to commit to GitHub. Again.

This project has grown into a filing labyrinth, the kind where one wrong turn in data/ leaves you staring at errors that make no sense. The safest way to preserve it is to both recreate the file structure and push to GitHub. Of course, I did neither at first. Nineteen days of work, sometimes 4–5 hours at a stretch, left hanging by a thread. Thankfully, I managed to recover everything, lock it with a password, and—finally—commit it to GitHub. Lesson learned.

Today’s Motivation / Challenge

Why does this matter? Because all the code in the world won’t help if your files don’t point to the right place. Data seeds are the backbone of this project: civilizations, events, and sources all live in CSVs. If your app can’t find them, it’s like trying to read a book without the pages.

Purpose of the Code (Object)

The code reconnects the app to its seed files—civilizations.csv, events.csv, and sources.csv. These files feed the database with initial data, ensuring the app has something to display. Without them, the app just spins its wheels and looks confused. By linking everything properly, the app can reload cleanly whenever you restart it.

AI Prompt

We will work on the Import.
Creating the link to the correct data seeds:

  • data/seeds/civilizations.csv
  • data/seeds/events.csv
  • data/seeds/sources.csv

Functions & Features

  • Import civilizations, events, and sources from CSV seed files.
  • Ensure the app database initializes with correct data.
  • Reconnect app logic to seed files if paths are broken.

Requirements / Setup

You’ll need:

  • Python 3.11

Installs:

pip install sqlmodel

Minimal Code Sample

import pandas as pd

civs = pd.read_csv(“data/seeds/civilizations.csv”)

events = pd.read_csv(“data/seeds/events.csv”)

sources = pd.read_csv(“data/seeds/sources.csv”)

Reads the seed CSVs into dataframes for use in the app.

The Civilization Timeline Builder

Notes / Lessons Learned

When you hit a problem importing data, the best thing you can do is stay calm and check filenames line by line. My biggest mistake came from an earlier definition error—I had renamed a file, recreated seeds, and left a trail of confusion. AI didn’t make it worse, but long chats with shifting memory meant old names and new names occasionally got tangled. Programming is like that: fragile, literal, unforgiving.

At one point, I even had the wrong database URL: sqlite:///./data/app.db instead of the correct sqlite:///./data/civ.db. One tiny typo, and the whole app refused to recognize my seeds. Reloading Streamlit without the right database path? A guaranteed disaster.

In the end, a fresh brain helped more than any clever trick. I reconnected the seed files, corrected the database path, and the app sprang back to life. Programming, I’ve realized, is 50% code, 50% precision—and 100% patience.

Optional Ideas for Expansion

  • Add validation scripts to confirm all seed files exist before the app runs.
  • Build a “seed reloader” button in the UI for quick fixes.
  • Let users upload their own seed CSVs to extend the dataset.

I Learned How Important it is to Back Up Your files

Day 95 of 100 Days Coding Challenge: Python

Today was supposed to be the grand finale. The plan: add a simple authorization system, set up daily backups, and polish everything with a shiny test suite. Instead, one tiny testing error snowballed into a catastrophic app crash. And the punchline? I hadn’t pushed to GitHub in four days. Yes, four.

So there I was, watching my carefully crafted app crumble, muttering at myself for breaking the golden rule: always back up. The irony stung—after weeks of building a timeline of civilizations that fell for ignoring simple rules, I managed to repeat history myself. Turns out, hubris gets you whether you’re Rome or just a tired coder.

Today’s Motivation / Challenge

Why does this matter? Because by the end of any project, you want to know your work isn’t just duct-taped together. Governance, performance, backups—these are the boring but essential pieces that keep your app from toppling like a badly built tower. Think of it like brushing your teeth: not exciting, but skip it, and you’ll regret it.

Purpose of the Code (Object)

The code here ties up loose ends: adding simple password-based access, creating a daily backup script, optimizing database performance with indexes, and ensuring tests cover at least 80% of the data and API. The goal is to make sure the app runs fast, stays secure, and—most importantly—doesn’t collapse when someone sneezes.

AI Prompt

Please do the following:
Governance & perf

  • Simple auth (env-guarded)
  • Daily backup script
  • DB indexes
  • Test suite

Accept: tests ≥80% coverage on data + API; timeline loads <250ms on sample set.

Functions & Features

  • Add environment-based simple authentication.
  • Run a daily script to back up the database.
  • Add indexes for faster queries.
  • Build a test suite with at least 80% coverage.

Requirements / Setup

You’ll need:

  • Python 3.11

Installs:

pip install pytest

Minimal Code Sample

# Simple env-based auth

import os

PASSWORD = os.getenv(“APP_PASSWORD”)

def check_auth(pw):

    return pw == PASSWORD

A tiny helper to check passwords against environment variables.

The Civilization Timeline Builder

Notes / Lessons Learned

Creating authorization wasn’t the problem—it was forgetting that I’d hardcoded a password in an .env file ages ago and then promptly lost it. Nothing like digging through old notes trying to remember whether you typed password123 or password_123.

Then came the imports. Every time I messed with data/seeds, my app broke in spectacular fashion. I’ve realized most of my headaches came from database imports, not from the app itself. Lesson? When your project leans heavily on a database, really know how you’re connecting to it. Otherwise, you’ll spend more time fixing imports than writing actual features.

In the end, I didn’t get the clean finale I wanted, but I walked away with the most important reminder of all: always, always back up. Civilization may fall without warning, but your repo doesn’t have to.

Optional Ideas for Expansion

  • Add role-based permissions (e.g., read-only vs. admin).
  • Automate cloud backups instead of local scripts.
  • Track performance metrics over time to catch slowdowns early.

Packing History in a Zip File

Day 94 of 100 Days Coding Challenge: Python

Today I leveled up my app by teaching it how to bundle everything into one neat little care package: CSV, PNG, quizzes, and all—zipped up and ready to go. Think of it as a take-home kit for history nerds. Instead of juggling screenshots and exports, now you can grab one file, open it later, and relive the glory of your filters and quizzes.

What excites me most is not just the feature itself but the possibilities it unlocks. I suddenly realized this kind of import/export bundle could be handy for any project where you want to ship outputs to others—or just yourself. If I had to re-create the logic from scratch, I’d probably be lost in the weeds. But now? I know the function exists, I know how to call it, and I have the code. That’s enough to carry the trick into future projects. And yes, I’m ridiculously happy about that.

Today’s Motivation / Challenge

Why does this matter? Because nobody wants to lose work. Whether it’s a report, a chart, or a quiz, being able to package and save everything guarantees you can come back to it later. Plus, if you’re sharing your app with others, a zipped curriculum pack makes you look like you thought ahead. It’s like sending someone leftovers in a perfectly labeled container instead of handing them a messy plate.

Purpose of the Code (Object)

The code bundles your selected civilizations, filters, quizzes, and images into a single zip file. Inside, you’ll find JSON, CSV, PNGs, and even a README so you know what’s what. It’s designed to be portable, so you can save a snapshot of your exploration, share it with a friend, or use it as a reference for another project.

AI Prompt

Add the following function:
Curriculum packs

  • Bundle lens + selected civs + quiz + images into a zip.
  • ✅ Accept: zip contains JSON, CSV, PNGs, README.

Functions & Features

  • Collect current filters, data, and quiz results.
  • Export charts and timelines as PNGs.
  • Save everything into a single zip archive.
  • Include a README file for clarity.

Requirements / Setup

You’ll need:

  • Python 3.11

Installs:

pip install plotly kaleido streamlit

Minimal Code Sample

import zipfile, io

with zipfile.ZipFile(“curriculum_pack.zip”, “w”) as zf:

    zf.writestr(“filters.json”, json.dumps(filters))

    zf.writestr(“data.csv”, df.to_csv(index=False))

    zf.writestr(“README.txt”, “Curriculum pack export”)

Writes JSON, CSV, and a README into a zip file in memory or on disk.

The Civilization Timeline Builder

Notes / Lessons Learned

Sometimes it’s the little discoveries that bring the most joy. I used to write imports like:

import io

import csv

But today I learned you can just write:

import io, csv

It’s small, but it makes me smile—one of those tiny conveniences you only stumble upon while coding.

The helper function itself starts by converting Plotly figures into PNG bytes (thanks to Kaleido, which I’d already added earlier). Then, with the io library, I created in-memory streams for text and files. io.StringIO in particular is handy: it treats strings like files, making them easy to read and write. I’d used it before for downloads, but seeing it in action again for zipping files reminded me just how versatile it is.

The result: a neat little archive of history in my downloads folder. Simple, but powerful.

Optional Ideas for Expansion

  • Add an “unzip and load” feature so packs can be re-imported into the app.
  • Include a summary.txt file with auto-generated narrative recaps.
  • Allow multiple packs to be batched into one archive for classroom use.

Adding Quiz Mode to Your Streamlit History App

Day 93 of 100 Days Coding Challenge: Python

Ninety-three days into this Python marathon, it suddenly hit me: I’ve only got a week left. The thought made me a little sad—like reaching the last few pages of a great book and realizing the ending is near. But rather than mope, I decided to channel that energy into something fun.

Today’s feature: quizzes. That’s right, the Civilization Timeline Builder is now handing out pop quizzes like a history teacher who drank too much coffee. The quiz pulls from the same filters (year range, regions, tags) you’ve already selected and generates random questions. “Which came first?” “Match the civ to the years?” It’s like turning my carefully curated data into a trivia night—minus the nachos, unfortunately. When you finish, the program even returns your score. History with instant feedback—cool, right?

Today’s Motivation / Challenge

Why does this matter? Because learning sticks better when you test yourself. You can scroll through timelines all day, but nothing sharpens your memory like a question asking, “Did the Gupta Empire adopt iron later than Rome?” Quizzes make history interactive, playful, and just a little competitive—especially if you’re the type who hates losing, even to yourself.

Purpose of the Code (Object)

The code auto-generates quiz questions based on your current filters. It randomizes both the content and the order, then provides a scoring system when you submit. There’s even a “show answers” toggle, so you can learn from your mistakes without a stern lecture.

AI Prompt

Please add the following function:
Quiz mode

  • Auto-generate 10 questions (“Which came first?”, “Match civ→years”).
  • Accept: scoring & “show answers” toggle.

Functions & Features

  • Generate random quiz questions from filtered civilizations and events.
  • Support multiple-choice or “which came first” style questions.
  • Return a score when the quiz is submitted.
  • Provide reset and show-answer options for replay and review.

Requirements / Setup

You’ll need:

  • Python 3.11

Installs:

pip install streamlit

Minimal Code Sample

import random

questions = random.sample(filtered_events, 10)

for q in questions:

    st.write(f”Which came first: {q[0].name} or {q[1].name}?”)

Picks 10 random event pairs and asks the user to guess which happened earlier.

The Civilization Timeline Builder

Notes / Lessons Learned

As long as I followed the instructions, it was surprisingly straightforward. Since it shares the same sidebar filters, I had to insert the quiz logic right after the filter selections.

To keep things flexible, I used st.number_input to let users decide how many questions they want. With the random library already installed, the program pulls events at random and builds the quiz. I even added two buttons: one to submit answers and another to reset the quiz entirely, giving users the choice to try again.

What struck me is how reusable this structure is. Today it’s history trivia, but tomorrow it could just as easily be a vocabulary quiz, a math drill, or even a fun little self-test on Python syntax. Who knew that adding a quiz mode could make history—and coding—feel a lot like game night?

Optional Ideas for Expansion

  • Add a timer for extra pressure (and bragging rights).
  • Track high scores across sessions.

Let users choose the quiz type: matching, multiple-choice, or true/false.

Exporting Timelines as CSV and PNG in Streamlit

Day 92 of 100 Days Coding Challenge: Python

Today I taught my app how to pack its bags and leave. In other words, I added import and export features. Sure, I could already copy-paste data or grab a screenshot with Greenshot, but sometimes you want your history neat and official—like a CSV for number crunching, a JSON for data nerds, or even a PNG snapshot to show off.

As an accountant, I’m a little obsessed with documentation. If it isn’t written down (preferably in triplicate), it might as well not exist. So this feature speaks to my soul: a one-click way to preserve the work I’ve done without juggling external tools. The first step? Adding Plotly’s Kaleido, which lets charts gracefully turn into PNGs instead of stubbornly staring back at you.

Today’s Motivation / Challenge

Why does this matter? Because sometimes you want to share your findings with others—or just keep a record for yourself—without re-running all the filters. Import/export turns the app into a portable historian. Think of it like saving leftovers: you can pack up today’s insights, reheat them later, and maybe even share with a friend.

Purpose of the Code (Object)

The code adds the ability to import data into the app and export filtered timelines as CSV, JSON, or PNG. That means you can save what you’re looking at, reload it later, or use it in another tool. It makes the app more than just an interactive toy—it becomes a proper record-keeper.

AI Prompt

I would like to add the following function:
Day 17 — Import/Export

  • CSV/JSON importers
  • Export filtered timeline as CSV + PNG snapshot
  • Accept: round-trip a tiny dataset; PNG file downloads

Functions & Features

  • Import datasets in CSV or JSON format.
  • Export filtered results as CSV for spreadsheets.
  • Save a PNG snapshot of the current timeline view.
  • Round-trip: test importing and exporting to confirm nothing gets lost.

Requirements / Setup

You’ll need:

  • Python 3.11

Installs:

pip install plotly kaleido streamlit

Minimal Code Sample

# Export timeline to PNG

fig.write_image(“timeline.png”)

# Export filtered data to CSV

df.to_csv(“filtered_timeline.csv”, index=False)

One saves your visualization as an image, the other exports your filtered data as a spreadsheet.

The Civilization Timeline Builder

Notes / Lessons Learned

Adding the helper functions was straightforward, but the placement of the sidebar UI block was trickier than expected. I initially dropped it in the wrong spot in streamlit_app.py, which triggered a spectacular cascade of errors. Once I moved it to sit neatly after the existing filters (year_range, selected_regions, selected_tags), everything clicked into place.

The PNG export was almost an afterthought—I nearly skipped it, exhausted after five hours of coding. But I pushed through, added it, and suddenly there it was: a crisp timeline image sitting on my desktop. Oddly enough, that little success gave me more motivation than any lecture on perseverance. Sometimes the best reward is seeing history look good in a file you can actually keep.

Optional Ideas for Expansion

  • Add Excel export for people who live in spreadsheets.
  • Bundle multiple outputs (CSV + PNG) into a single downloadable zip file.
  • Create an auto-backup option so every filter session saves itself without asking.

Building a Sources & Citations System in SQLModel

Day 91 of 100 Days Coding Challenge: Python

Today was a reminder that computers are ruthless sticklers for details. I decided to add sources linked to the detailed card—because just like in academic papers, you can’t just “borrow” information floating around the internet without tipping your hat to whoever wrote it first. Seems fair, right?

But of course, the minute I touched the database, I triggered the classic relationship drama. My code started yelling at me: “source not defined.” Over and over. It was like being haunted by an ex who just wouldn’t leave the room. I spent five solid hours untangling imports, relationships, and annotations, trying everything from brute force to delicate tweaks. Eventually, the stars aligned, and the errors gave up. Victory never felt so earned.

Today’s Motivation / Challenge

Why spend a day on sources? Because information without sources is gossip, and gossip doesn’t belong in a timeline app. Adding citations isn’t just academic—it’s about trust. If you’re building a history tool, you want to know where the facts came from. Think of it like putting a bibliography on your app so it won’t get expelled for plagiarism.

Purpose of the Code (Object)


This code connects events and civilizations in the database to their sources, so each detail card can show where the information came from. Instead of a bare timeline with floating facts, now you get credibility built in. It’s like footnotes, but without the foot pain.

AI Prompt (for reference)

Day 16 — Sources & citations

  • sources table; link events/civs to sources; show bibliography per page.

Accept: civ detail lists sources with links.

Functions & Features

  • Add and store sources in a dedicated table.
  • Link sources to both events and civilizations.
  • Display a neat bibliography with clickable references on detail pages.

Requirements / Setup

  • Python 3.11+
  • Install SQLModel and SQLAlchemy 2.0+

pip install sqlmodel sqlalchemy

Minimal Code Sample

from typing import TYPE_CHECKING

from sqlmodel import SQLModel, Field, Relationship

class Source(SQLModel, table=True):

    id: int = Field(primary_key=True)

    title: str

    # Source linked to events

    events: list[“Event”] = Relationship(back_populates=”sources”)

# Ensures circular imports don’t trip us up

if TYPE_CHECKING:

    from .models import Event

This shows how a Source model can safely connect to events without circular import chaos.

The Civilization Timeline Builder

Notes / Lessons Learned

Those “source not defined” errors? Almost always circular-import headaches. The fix: postpone annotation evaluation, only import for type checking, and pass actual class objects instead of string guesses. SQLAlchemy 2.0 makes you play by the rules, and if you don’t, it punishes you with cryptic error messages.

After five hours of trial and error, I realized programming is like fixing plumbing: leaks pop up where you least expect them, and you just have to stay calm, systematic, and maybe a little stubborn. My biggest takeaway? Debugging isn’t a sprint—it’s therapy with a keyboard.

Optional Ideas for Expansion

  • Add clickable links so users can jump straight to the source online.
  • Allow filtering events by source—for example, “show me all events documented by Herodotus.”
  • Add a “source credibility” rating, so you can separate legends from solid history.

Historical Narrative Generator with One Click

Day 90 of 100 Days Coding Challenge: Python

Today I set out to give my app a voice of its own. The mission: generate a short 200–300-word historical narrative generator for whatever filters I’ve chosen. Basically, I wanted my app to play historian, skimming the top events and spitting out a digestible report. To do this, I turned to the trusty textwrap library.

Now, textwrap doesn’t sound glamorous—it’s no flashing map or animated arrow—but it does something crucial: it formats and wraps text so it looks neat and tidy. Think of it as the digital equivalent of that English teacher who insisted your essays had to fit on one page, double-spaced, 12-point font. Without it, you end up with messy, runaway text blocks. With it, you get something polished enough to pass off as a summary, a report, or maybe even a history newsletter.

It felt oddly satisfying—like giving the app its own narrator.

Today’s Motivation / Challenge

Why does this matter? Because clicking through endless events is like reading footnotes without the main text. A historical narrative generator summarizes a historical event and gives you the big picture: what happened, where it mattered, and why you should care. It’s like the “Previously on…” segment at the start of a TV show—except the show is about Mesopotamia instead of crime dramas.

Purpose of the Code (Object)

The code pulls the most important events from the current filter and stitches them into a compact narrative. It uses textwrap to make the summary readable and presentable. The end result is a neat little block of text you can generate with a button click, then edit as you see fit.

AI Prompt

We would like to add:
Narrative summary (extractive)

  • Generate 200–300-word summary for current filter (extract top events).
  • Accept: button produces summary; editable text area.

Functions & Features

  • Extract top events from the current filter.
  • Auto-generate a narrative summary (200–300 words).
  • Display the summary in an editable text box for user tweaks.
  • Format the text neatly with textwrap.

Requirements / Setup

You’ll need:

  • Python 3.11

Installs:

pip install textwrap3 streamlit

Minimal Code Sample

import textwrap

summary = ” “.join([e.description for e in top_events])

wrapped = textwrap.fill(summary, width=80)

st.text_area(“Generated Summary”, value=wrapped, height=200)

Collects event descriptions, wraps the text neatly, and displays it for editing.

The Civilization Timeline Builder

Notes / Lessons Learned

After wiring up the function, I thought everything was ready—until I realized my civilization details had disappeared. Worse, the shiny new “Generate Summary” button was missing too. The culprit? Code order. My narrative block had slipped into the wrong conditional, pairing itself with the diffusion toggle instead of the list/detail switch.

So when diffusion was off, the else: block tried to show a nonexistent detail view, and the summary button went into hiding. Once I moved everything back under the right branch, the details returned, and my summary button reappeared like a magician pulling a rabbit from a hat.

Lesson learned: order matters. Functions can’t just float wherever they want—think of them like stubborn cats. If you put them in the wrong room, they’ll refuse to come out until you move them properly.

Optional Ideas for Expansion

  • Add a “copy to clipboard” button for easy sharing.
  • Export summaries as a text or PDF file.
  • Let users choose between a short, medium, or long summary length.

Tech Diffusion — Tracing How Innovations Spread Across Civilizations

Day 89 of 100 Days Coding Challenge: Python

Today I gave my app a new toy: Tech Diffusion (beta). This feature traces how a chosen technology or theme spread from one civilization to another. It builds a mini-timeline of “first appearances” and plots arrows on a map showing the likely diffusion paths. It’s basically gossip for inventions—who did it first, and who copied whom.

I tested it with ironworking, fully expecting to see a neat west-to-east progression from the Middle East to Asia. Instead, I was surprised. Rome and Han China both show ironworking popping up around the same time, while the Gupta Empire only catches on centuries later. The Silk Road could explain part of this, but it still made me pause. Either the tech sprinted faster than my app shows—or my data has some catching up to do. Either way, it’s a reminder: history doesn’t always follow the tidy arrows we imagine.

Today’s Motivation / Challenge

Why does this matter? Because inventions don’t just happen—they move. From ironworking to paper to gunpowder, tracking how ideas spread reveals the hidden highways of human history. It’s the connective tissue between civilizations, proof that even ancient cultures were networked (without Wi-Fi, no less).

Purpose of the Code (Object)

The code identifies the earliest events tagged with a given theme, orders them on a timeline, and draws map arrows from early adopters to later ones. With it, you can watch technology hopscotch across regions, showing not only who led the way but also who eventually caught on. It’s a way to visualize cultural diffusion, not just in theory but in clickable, colorful practice.

AI Prompt

Please add the following function:
Tech diffusion

  • For tag “ironworking” (example), show first appearances → later adoptions timeline & map arrows.

Functions & Features

  • Search events by theme (e.g., “ironworking,” “Christianity”).
  • Identify earliest appearances for each civilization.
  • Plot a timeline ordered by first adopters.
  • Draw arrows on a map to suggest diffusion paths.

Requirements / Setup

You’ll need:

  • Python 3.11

Installs:

pip install streamlit plotly

Minimal Code Sample

events = [e for e in all_events if “ironworking” in e.tags]

events.sort(key=lambda e: e.year)

for i in range(1, len(events)):

    fig.add_annotation(

        x=events[i].lon, y=events[i].lat,

        ax=events[i-1].lon, ay=events[i-1].lat,

        arrowhead=2, showarrow=True

    )

Orders events by year, then draws arrows between earlier and later adopters.

The Civilization Timeline Builder

Notes / Lessons Learned

The build went smoothly—until testing. When I searched for Christianity, everything worked: Rome’s Edict of Milan showed up, and Aksum’s adoption of Christianity appeared right where it belonged. But when I tested ironworking, nothing showed. After a few puzzled minutes, I discovered the culprit: my seed data. Rome and Han had tags with “ironworking,” but Gupta’s event was missing the tag altogether. No data, no arrows.

It reminded me of earlier struggles mapping civilizations—if the data isn’t there, no amount of clever code will conjure it. After correcting my CSV and reseeding, the function worked perfectly. Lesson learned: the best code still depends on the quality of the story you feed it.

Optional Ideas for Expansion

  • Add multiple themes at once (e.g., track ironworking and horse domestication).
  • Let users toggle arrow thickness by time delay (longer gaps = thinner arrows).
  • Animate the diffusion so technologies appear and spread in real time.

Sequence Hints in Streamlit: Find Neighbor Events Within ±50 Years

Day 88 of 100 Days Coding Challenge: Python

Today, I rolled out an experimental function for my civilization app—something I’ve never attempted before. The idea? Track what’s happening in neighboring civilizations within ±50 years of a major event. The goal isn’t to claim causation, but to look for ripples because history loves ripples.

Take Rome, for example. If you see a burst of military aggression from nearby empires around the same time Rome is wobbling, you can start to piece together the larger story. And it doesn’t stop at politics or warfare. Cultural and technological shifts—like paper-making or gunpowder—also spread from neighbor to neighbor, changing the trajectory of civilizations in ways subtle and spectacular.

Honestly, I was pretty excited to build this. It feels like uncovering the “behind-the-scenes” footage of history, where you see not just what happened but what was happening in the background.

Today’s Motivation / Challenge

Why does this matter? Because no civilization exists in isolation. Every rise or fall has echoes in neighboring regions. Whether it’s Rome watching the Huns thunder in, or India passing along the concept of zero, the world is full of crossovers. By tracking those overlaps, we get a richer picture of history’s interconnected web—basically, the original shared universe.

Purpose of the Code (Object)

The code adds a “sequence hints” feature that scans for major neighbor changes within ±50 years of a civilization’s events. The results are shown as a neat badge with tooltips, so you can quickly see counts and peek at details. It’s not claiming “X caused Y,” but it highlights when civilizations were moving in sync—or clashing in spectacular ways.

AI Prompt

Please add this function:
Day 13 — Sequence hints

  • For each civ, list major neighbor changes within ±50 years (label “sequence, not causation”).
  • Accept: UI badge shows counts; tooltip text is clear.

Functions & Features

  • Scan for significant neighbor events within ±50 years.
  • Display overlaps as badges with event counts.
  • Provide tooltips with details like event type, year, and time delta.
  • Sync with the same filters (region, tags, years).

Requirements / Setup

You’ll need:

  • Python 3.11

Installs:

pip install streamlit plotly

Minimal Code Sample

def sequence_hints(civ, all_civs):

    hints = []

    for neighbor in all_civs:

        if neighbor.id == civ.id: 

            continue

        for event in neighbor.events:

            if abs(event.year – civ.year) <= 50:

                hints.append((neighbor.name, event))

    return hints

Checks if a neighbor’s event happened within ±50 years and records it.

The Civilization Timeline Builder

Notes / Lessons Learned

The feature turned out to be fascinating in practice. I tested it with the Roman Republic, focusing on 0–400 CE, and the results popped:

  • Gupta Empire
    400: Decimal & Zero · tech · Δ5 yrs
    415: Iron Pillar of Delhi · tech · Δ20 yrs
    500: Decline Under Huns · war · Δ24 yrs
  • Kingdom of Aksum
    330: Adopts Christianity · religion · Δ17 yrs

Not exactly the sweeping revelations I imagined, but enough to prove the concept. The catch? I only uploaded 12 civilizations so far. To make this function shine, I’ll need a much larger dataset.

Lesson of the day: even pilot versions can teach you a lot, but history—like data science—works best with more data points.

Optional Ideas for Expansion

  • Add filters to focus on specific event types (e.g., only tech overlaps).
  • Visualize the overlaps on the map with arrows or connectors.
  • Show a timeline side-by-side view to compare ripple effects across civilizations.

 Save Filter Presets — Add Theme Lenses to Your History App

Day 87 of 100 Days Coding Challenge: Python

Because I have another commitment tomorrow, I decided to sneak today’s function into the schedule a little early. The feature of the day? Saving filters. Think of it as adding a “save game” button to history. If you’ve ever played a Nintendo game that didn’t let you save, you know the pain—hours of progress gone because your little brother unplugged the console. I didn’t want my carefully chosen filters to vanish the same way.

Of course, no feature comes without a little drama. I hit two big issues. Back when I started coding, any red error message would send me into full panic mode. Now, I’ve learned that most problems are just puzzles with bad timing. Today reminded me of that: slow down, breathe, and fix one thing at a time.

Today’s Motivation / Challenge

Why does this matter? Because sometimes you don’t want to rebuild the same filter every time you open the app. Imagine crafting the perfect view of “iron adoption in Eurasia, 800 BCE–200 CE,” then losing it forever because you clicked away. Saving presets means you can revisit your favorite “lenses” with one click, whether it’s maritime trade routes or technological revolutions.

Purpose of the Code (Object)

The code lets you save your current filter selections as JSON presets—called “theme lenses.” You can load, switch, and reuse them later. This way, your app remembers what you care about, and you can jump straight into exploring without re-selecting everything.

AI Prompt

Please add the following functions:
Theme lenses

  • Save filter presets (JSON): “maritime trade”, “iron adoption”, etc.

Accept: load/switch lenses in 1 click

Functions & Features

  • Save current filter selections as named JSON files.
  • Load a saved filter (“lens”) instantly with one click.
  • Keep taxonomy tags (like “tech” or “war”) consistent, even if not in the current tag list.

Requirements / Setup

You’ll need:

  • Python 3.11

Installs:

pip install streamlit

Minimal Code Sample

def save_lens(name, filters):

    with open(f”lenses/{name}.json”, “w”) as f:

        json.dump(filters, f)

def load_lens(name):

    with open(f”lenses/{name}.json”) as f:

        return json.load(f)

One function saves filters as JSON; the other loads them back into the app.

The Civilization Timeline Builder

Notes / Lessons Learned

The first bug was almost comical: I forgot to delete the existing sidebar widget, so suddenly I had two period selectors glaring at me. That one was an easy fix. The trickier problem was when lenses like “maritime trade” or “iron adoption” triggered Streamlit errors. The issue? I had assigned values that didn’t exist in the current all_tags list. Streamlit doesn’t like when you ask for something it doesn’t offer.

The solution was to create a script that merges the database-derived options with whatever free-form values the lens needed. That way, even if the lens specifies tags like “sea” or “tech,” it still works, whether or not those appear in all_tags. After some careful debugging, everything finally clicked into place.

The real lesson: don’t try to fix every error at once. Solve one, breathe, then tackle the next. The process is a lot less stressful that way.

Optional Ideas for Expansion

  • Add a dropdown of saved lenses in the sidebar for quick access.
  • Allow exporting and sharing lenses with friends (history study group, anyone?).
  • Include a “random lens” button for surprise explorations.