Seams — which module writes which table🔗
Curated (the Evergreen layer of Living Documentation), derived from
src/polit_media_radar/persistence/repositories.pyplus a grep of direct (non-repository) writers. Unlike modules.md and the ER diagrams underdb/, this map is not regenerated by a single tool — when the persistence layer changes, re-runtools/extract_seams.pyand re-check this file.
A seam is the point where a module persists state into a table, almost always through a repository class in the persistence layer. The diagram shows the backbone seams; the table below is the exhaustive list. Convention:
- solid arrow = writer (the module inserts/updates the table through the repository)
- dashed arrow = reader / FK lookup
- dotted arrow + blue table = direct write that bypasses the repository layer (an architectural smell worth knowing)
- grey dashed table = writer pending: the repository exists as a skeleton (
raise NotImplementedError), the table is in the schema, but nothing writes it yet - yellow table = the
candidates/fetch_runsspine shared by both contexts
flowchart LR
classDef spine fill:#ffe,stroke:#b80,stroke-width:3px
classDef skeleton stroke-dasharray:5 5,fill:#eee,color:#888
classDef direct fill:#eef,stroke:#66c
ingestion["ingestion"]
cli["cli"]
services["services"]
reports["reports"]
subgraph radar["legacy context (radar.*)"]
candidates["candidates"]:::spine
fetch_runs["fetch_runs"]:::spine
articles["articles"]
comments["comments"]
article_restrictions["article_restrictions<br/>(no writer)"]:::skeleton
end
subgraph radar_ss["SocialScan context (radar.ss_*)"]
ss_snapshots["ss_snapshots"]
ss_posts["ss_posts"]
ss_ai_reports["ss_ai_reports"]
ss_trends_daily["ss_trends_daily"]
ss_themes["ss_themes"]:::skeleton
ss_wave_runs["ss_wave_runs"]:::skeleton
ss_post_tonality["ss_post_tonality"]:::direct
weekly_corpus_summary["weekly_corpus_summary"]:::direct
end
ingestion -->|ArticleRepository| articles
ingestion -->|CandidatesRepository| candidates
ingestion -->|FetchRunsRepository| fetch_runs
ingestion -->|CommentsRepository| comments
ingestion -->|SsSnapshotsRepository| ss_snapshots
ingestion -->|SsPostsRepository| ss_posts
ingestion -->|SsTrendsDailyRepository| ss_trends_daily
cli -->|SsAiReportsRepository| ss_ai_reports
ss_snapshots -.->|FK| fetch_runs
ss_posts -.->|FK| candidates
reports -.->|reads| ss_ai_reports
cli -.->|direct write, no repo| ss_post_tonality
services -.->|direct write, no repo| weekly_corpus_summary
subgraph legend["Legend"]
L1["module"] -->|writer| L2["table"]
L3["module"] -.->|reader / FK| L4["table"]
L5["writer pending"]:::skeleton
L6["direct write (no repo)"]:::direct
L7["spine"]:::spine
end
Exhaustive repository → table map🔗
One row per repository in repositories.py. "Reads" lists secondary tables the repository touches for FK lookups / joins. ⚠️ = writer-pending skeleton (NotImplementedError).
Legacy context (radar.*)🔗
| Repository | Writes | Reads |
|---|---|---|
ArticleRepository |
articles |
article_revisions |
CandidatesRepository |
candidates |
— |
FetchRunsRepository |
fetch_runs |
— |
MetricsSnapshotRepository |
article_metrics_snapshots |
— |
CommentsRepository |
comments |
— |
CommentAuthorsRepository |
comment_authors |
mv_comment_authors_stats (view) |
ReactionSnapshotsRepository |
reaction_snapshots |
— |
ForwardsRepository |
forwards |
— |
ChannelSnapshotsRepository |
channel_snapshots |
— |
ArticleRevisionsRepository |
article_revisions |
— |
ArticleEntitiesRepository |
article_hashtags, article_mentions |
— |
| (no repository) | article_restrictions — no writer found (schema present, unused) |
— |
SocialScan context (radar.ss_* + weekly_corpus_summary)🔗
| Repository | Writes | Reads |
|---|---|---|
SsSnapshotsRepository |
ss_snapshots |
ss_district_summaries |
SsPostsRepository |
ss_posts |
— |
SsPostClassificationsRepository |
ss_post_classifications |
— |
SsThemesRepository |
ss_themes ⚠️ |
— |
SsRecommendationsRepository |
ss_recommendations ⚠️ |
— |
SsJudgeResultsRepository |
ss_judge_results ⚠️ |
— |
SsWaveRunsRepository |
ss_wave_runs ⚠️ |
— |
SsDistrictSummariesRepository |
ss_district_summaries |
— |
SsTrendsRepository |
ss_trends |
— |
SsTrendsDailyRepository |
ss_trends_daily (raw SQL, no ORM model) |
— |
SsAiReportsRepository |
ss_ai_reports |
— |
SsOwnPostsRepository |
ss_own_posts |
candidates, ss_snapshots |
SsNetworkPostsRepository |
ss_network_posts |
ss_snapshots |
SsNarodnayaVerdictsRepository |
ss_narodnaya_verdicts |
ss_snapshots |
SsSanityCorrectionsRepository |
ss_sanity_corrections |
ss_narodnaya_verdicts |
SsPostEventsRepository |
ss_post_events |
ss_snapshots |
SsChannelArchetypesRepository |
ss_channel_archetypes |
— |
SsMentionRelevanceRepository |
ss_mention_relevance |
— |
SsChannelFollowersWeeklyRepository |
ss_channel_followers_weekly |
— |
Writes that bypass the repository layer🔗
These tables are written directly from a module, not through a repository — a deviation from the clean module → repository → table pattern, worth tracking:
| Table | Written by | Note |
|---|---|---|
ss_post_tonality |
cli/tonality.py |
tonality metric backfill writes raw, no repo — accepted exception (ADR-0018) |
weekly_corpus_summary |
services/weekly_corpus_summary.py |
service writes directly — accepted exception (ADR-0018) |
Derived from repositories.py (via tools/extract_seams.py) + grep for direct writers. SC-002's automated drift gate covers only the tool-generated diagrams (modules, db/); this curated map must be re-verified by hand when the persistence layer changes.