BorisovAI
All posts
Code Changetrend-analisisClaude Code

Building Trends: From Mockups to Data-Driven Analysis Engine

Building Trends: From Mockups to Data-Driven Analysis Engine

Building Trend Analysis: From UI Mockup to Data Layer

The trend-analysis project needed serious architectural work. The HTML prototype was done—nice buttons, forms, the whole visual dance—but now came the real challenge: connecting it all to a backend that could actually think.

The task was ambitious but clear: implement the complete backend data layer, versioning system, and API endpoints that would let analysts track how trends evolve and branch into deeper investigations. Starting from scratch meant understanding what already lived in the codebase and what needed to be built.

First thing I did was read through the existing analysis_store.py file. This was crucial. The database had a foundation—an analyses table and some basic query functions—but it was missing the intelligence needed for version tracking. Trends aren’t static; they split, deepen, get revisited. The existing code didn’t know how to handle parent-child relationships between analyses or track investigation depth.

So Phase 1 began: SQL migrations. I added four new columns to the database schema: version (which analysis iteration is this?), depth (how many levels down in the investigation?), time_horizon (looking at the past week, month, or year?), and parent_job_id (which analysis spawned this one?). These weren’t just decorative fields—they’d form the backbone of how the system understood analysis relationships.

Next came the tricky part: rewriting the store functions. The original save_analysis() was simple and dumb. I modified it to accept these new parameters and compute version numbers intelligently—if you’re analyzing the same trend again, it’s version 2, not version 1. I also added next_version() to calculate what version number should come next, find_analyses_by_trend() to fetch all versions of a particular trend, and list_analyses_grouped() to organize results by parent-child relationships.

Unexpectedly, the Pydantic schema updates took longer than anticipated. Each converter function—_row_to_analysis_summary(), _row_to_version_summary()—needed careful attention. One mistake in the field mapping, and the entire API layer would silently return wrong data.

By Phase 2, I was updating the API routes themselves. The AnalyzeRequest schema grew to accept parent analysis IDs. The _run_analysis() function now computed versions dynamically. Endpoints like get_analysis_for_trend returned all historical versions, while get_analyses gained a grouped query parameter to visualize parent-child hierarchies.

Here’s something worth knowing about relational database versioning: Most developers instinctively reach for row-level versioning tables (essentially duplicating data), but maintaining a parent relationship in a single table with version numbers is more elegant. You get the full history without denormalization headaches, though querying hierarchical data requires careful SQL. In this case, storing parent_job_id let us reconstruct the entire investigation tree without extra tables.

After Phase 2 wrapped up, I ran the test suite. Most tests passed. One pre-existing failure in an unrelated crawler test wasn’t my problem—legacy code that nobody had bothered fixing. The new code was solid.

What got shipped: a versioning system that lets analysts branch investigations, track which analyses spawned which children, and organize their work by depth and time horizon. The backend now understood that good research isn’t linear—it’s recursive, exploratory, and needs to remember where it came from.

Next up: Phase 3, which meant the frontend would finally talk to this data layer. But that’s another story.

😄 What do you get if you lock a monkey in a room with a typewriter for 8 hours? A regular expression.

Metadata

Session ID:
grouped_trend-analisis_20260208_1518
Branch:
feat/scoring-v2-tavily-citations
Dev Joke
Знакомство с C#: день 1 — восторг, день 30 — «зачем я это начал?»

Rate this content

0/1000