Skip to content

Saint search page, DayCommemoration/Saint many-to-many, and extensive saint-identity data cleanup - #175

Merged
brianglass merged 6 commits into
mainfrom
saint-search-mockup
Aug 13, 2026
Merged

Saint search page, DayCommemoration/Saint many-to-many, and extensive saint-identity data cleanup#175
brianglass merged 6 commits into
mainfrom
saint-search-mockup

Conversation

@brianglass

Copy link
Copy Markdown
Owner

Summary

  • Adds a saint search page (/saints/) and detail page, with search across name, full_name, transliteration-normalized spelling variants (e.g. "Athanasios" finds "Athanasius"), and linked commemoration titles.
  • Replaces DayCommemoration.saint (a single FK) with a saints many-to-many through DayCommemorationSaint (which carries a display order), so a commemoration naming multiple people can link to each of their real Saint identities instead of a synthetic joint pseudo-identity.
  • Uses the new relationship to fix ~20 fragmented saint identities discovered via the search feature — the recurring pattern is a saint's main biographical entry split from a same-person occasion (translation/uncovering of relics, a joint commemoration, a canonization) that was never linked to it. Includes Athanasius/Cyril, Nicholas of Myra, the Theotokos and John the Baptist's various feasts, the Three Holy Hierarchs, Archangel Gabriel, and others — see commit history for the full list.
  • Cross-checked two commemorations against the original abbamoses.com source data and fixed a story/title mismatch bug found along the way (a story had been attached to the wrong same-day terse title during an old matching pass).
  • Brings the MCP server's search_saints tool up to parity with the web search by extracting the shared matching logic into commemorations/search.py.
  • Assorted CSS fixes surfaced while testing the new page (hamburger menu clipping on short pages, a stale nav breakpoint, full-justified search result text).

Test plan

  • Full test suite passes (148 tests) against a from-scratch image build
  • Manually verified in-browser: search across transliteration variants, joint-commemoration searches, saint detail pages, hamburger menu on narrow layouts (Chrome and Firefox)
  • Each data merge individually verified via liturgics.Day(...) and the saint detail page before committing

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hf6j2xXQXywHVh3HAVRxB3

brianglass and others added 6 commits August 12, 2026 15:33
Adds /saints/ (search by name/full_name/commemoration title) and
/saints/<pk>/ (a saint's full commemoration history: every occasion
it's listed under, with links down to whichever of them actually have
story text -- reusing the empty-story detection from the
_has_story() work in #172).

Query matches Saint.name, Saint.full_name, or any of that saint's
DayCommemoration.title values, deduplicated to one row per saint --
mirrors the search logic already proven in mcp_svc/tools.py's
search_saints tool, just re-scoped from commemoration-level results to
saint-level identities so "click a match" has one obvious destination.

Moveable-feast occasions (pdist != 999) get a "Moveable (Pascha +/-N
days)" label instead of a month/day, since there's no year-independent
calendar date for those.

Genuinely a mockup: no tests yet, and searching "Seraphim of Sarov"
immediately surfaces a real, still-open data-quality gap (only one
Saint identity comes back, when the repose/relics-uncovering split
noted in docs/saint-model-refactor.md may still exist for other
saints) -- exactly the kind of thing this page seems useful for
finding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hf6j2xXQXywHVh3HAVRxB3
…identity investigation groundwork

Break search queries into AND'd terms so "John Theologian" finds "St. John
the Theologian"; prefer Saint.full_name for display since Saint.name is
often occasion-specific; add slug-based URLs (disambiguated by earliest
fixed-calendar date) via a backfill management command instead of
exposing raw pks; clean up 10 fixture rows where name/full_name had
drifted apart; drop the redundant commemorations list from the detail
page and restyle the remaining story headings to match the readings page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hf6j2xXQXywHVh3HAVRxB3
…-identity saints and two abbamoses title/story mismatches

Replace DayCommemoration.saint (FK) with a saints M2M through
DayCommemorationSaint (order controls multi-saint display order), so a
commemoration naming several people can link to each of their real solo
Saint identities instead of a synthetic joint pseudo-identity.

Use the new relationship to fix 7 fragmented saints where a joint or
duplicate commemoration was hiding a real story on an otherwise-orphaned
solo entry: Athanasius/Cyril, Basiliscus, Onesimus, Theodota (mother of
Cosmas and Damian), St Innocent, Hermas of the Seventy, and Juvenaly of
Alaska (spanning three dates).

Also cross-checked two mismatched entries against the original abbamoses
scrape and found the same root cause in both: a terse, independently
sourced title had absorbed the wrong same-day story from an old matching
pass. Moved Kyranna's story onto her existing (previously story-less)
Greek-tradition entry, and added a missing commemoration for James the
Confessor of the Studion whose story had been stranded under the
unrelated "James the Confessor of Catania" title.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hf6j2xXQXywHVh3HAVRxB3
Add Saint.normalized_name, derived from name/full_name by canonicalizing
known Greek<->Latin transliteration substitutions (c/k, y/i, word-final
os/us), so searching "Athanasios" finds "St Athanasius the Great" and
similar cross-spelling pairs found during the earlier dedup pass
(Dionysius/Dionysios, Sergius/Sergios, Cosmas/Kosmas, Isaac/Isaak).

Backfilled via a management command rather than Saint.save(), since
fixture loading bypasses custom save() the same way it does for slug --
confirmed by the test suite needing the same backfill call in setUp that
production gets from the Dockerfile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hf6j2xXQXywHVh3HAVRxB3
Same fragmented-identity pattern as the earlier Athanasius/Cyril-era
fixes -- a saint's full biographical entry and their "translation of the
relics"/"finding of the head" entry existed as two unlinked Saint rows.
These slipped past earlier dedup passes because those required an exact
name match after stripping occasion prefixes, and these pairs all differ
in trailing epithets (e.g. "Nicholas the Wonderworker" vs "Nicholas the
Wonderworker, Archbishop of Myra"). Found via a token-overlap scan
targeted at occasion-marked entries, each confirmed individually via
story cross-reference or a highly specific shared epithet before
merging:

- Nicholas of Myra (Dec 6 repose <-> May 9 translation to Bari)
- Nilus of Sora (Apr 7 repose <-> May 7 repose entry)
- Matrona of Chios (Oct 20 <-> Jul 15 finding of the head)
- Cyrus and John, Unmercenaries (Jan 31 <-> Jun 28, explicit
  "see January 31" cross-reference)
- Nicephorus, Patriarch of Constantinople (Jun 2 <-> Mar 13, explicit
  "his main commemoration is on June 2")
- Stephen the Protomartyr (Dec 27 <-> Aug 2 finding of relics)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hf6j2xXQXywHVh3HAVRxB3
…tion leaks, and merge more fragmented identities

Search improvements:
- Exclude Saint results with no story on any commemoration -- a dead end
  that led nowhere useful on the detail page.
- Fix title-based search matching leaking across saints sharing a joint
  commemoration (e.g. searching "Chrysostom" no longer also returns Basil
  and Gregory just because their shared "Synaxis of the Three Holy
  Hierarchs" title mentions him) -- refined so a term only counts as
  shared when no saint on the commemoration specifically owns it in their
  own name/full_name, so genuinely shared terms like "Hierarchs" still
  correctly match everyone in the group.
- Remove inherited full-justification on search result text.
- Extract the shared matching logic (term-splitting, transliteration,
  shared-title handling) into commemorations/search.py so the MCP
  server's search_saints tool uses the same logic as the web search
  instead of a separately-drifted, less capable implementation (it was
  missing multi-term matching, transliteration matching, and the
  joint-commemoration leak fix).

Data fixes -- more fragmented saint identities found via search and
merged following the same pattern as the Athanasius/Cyril case:
- Theotokos: the one non-icon-specific "Synaxis" entry merged into the
  canonical Theotokos row; icon-specific Synaxis entries (Three Hands,
  Miasenae, Evangelistria, etc.) deliberately left separate.
- John Chrysostom, Gregory the Theologian: each had an unlinked
  translation/removal-of-relics entry merged into their main entry; the
  "Synaxis of the Three Holy Hierarchs" now links directly to all three
  real saints (Basil, Gregory, Chrysostom) instead of a redundant joint
  stub.
- St Xenia of St Petersburg: canonization entry merged into her main
  entry.
- Archangel Gabriel: three unlinked entries (Leavetaking of Annunciation,
  the Mt Athos "Axion Estin" appearance, and the joint "Chief Captains"
  Synaxis with Michael) all merged/relinked into one canonical identity.

CSS fixes:
- body's overflow-x: hidden implicitly makes overflow-y compute as auto
  rather than visible, so on a short page (e.g. search with no results)
  body's box could end before the open hamburger dropdown's full height,
  clipping it. Added min-height: 100dvh so there's always room.
- Widened the hamburger-menu breakpoint (1250px -> 1350px) -- adding
  "saints" to the nav shrank the safety margin between the measured wrap
  point and the breakpoint to ~35px, thin enough for real-browser font
  rendering to tip it into visible wrapping before the hamburger kicks in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hf6j2xXQXywHVh3HAVRxB3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant