mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 23:00:16 +00:00
498b191afa
Move static reference data from Python modules to JSON files in data/ directory: - Bible metadata (testament lists, book abbreviations) → bible_metadata.json - Chapter explanations and popularity scores → chapter_explanations.json, popular_chapters.json - Featured verses for verse of the day → featured_verses.json - Resource slugs for sitemap generation → resource_slugs.json Benefits: easier content updates, better separation of data and code, enables non-developer content management. All 252 tests passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
292 lines
12 KiB
Python
292 lines
12 KiB
Python
"""Helper utilities for KJV Study."""
|
|
import re
|
|
import json
|
|
import hashlib
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Optional, Dict, List
|
|
from functools import lru_cache
|
|
|
|
from ..kjv import bible, VerseReference
|
|
from ..topics import get_all_topics
|
|
|
|
# Paths to data files
|
|
_DATA_DIR = Path(__file__).parent.parent / "data"
|
|
_CHAPTER_EXPLANATIONS_PATH = _DATA_DIR / "chapter_explanations.json"
|
|
_POPULAR_CHAPTERS_PATH = _DATA_DIR / "popular_chapters.json"
|
|
_FEATURED_VERSES_PATH = _DATA_DIR / "featured_verses.json"
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def _load_chapter_explanations() -> dict:
|
|
"""Load chapter explanations from JSON file. Cached since data never changes."""
|
|
with open(_CHAPTER_EXPLANATIONS_PATH, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
# Convert string keys to integers for chapter numbers
|
|
return {
|
|
book: {int(chapter): explanation for chapter, explanation in chapters.items()}
|
|
for book, chapters in data.items()
|
|
}
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def _load_popular_chapters() -> dict:
|
|
"""Load popular chapters from JSON file. Cached since data never changes."""
|
|
with open(_POPULAR_CHAPTERS_PATH, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
# Convert string keys to integers for chapter numbers
|
|
popular = {
|
|
book: {int(chapter): score for chapter, score in chapters.items()}
|
|
for book, chapters in data["popular_chapters"].items()
|
|
}
|
|
return {
|
|
"popular_chapters": popular,
|
|
"high_readership_books": data["high_readership_books"]
|
|
}
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def _load_featured_verses() -> list:
|
|
"""Load featured verses from JSON file. Cached since data never changes."""
|
|
with open(_FEATURED_VERSES_PATH, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
# Convert dict format to tuple format for compatibility
|
|
return [
|
|
(verse["book"], verse["chapter"], verse["verse"])
|
|
for verse in data["verses"]
|
|
]
|
|
|
|
|
|
@lru_cache(maxsize=512)
|
|
def create_slug(text: str) -> str:
|
|
"""
|
|
Convert text to URL-friendly slug.
|
|
Cached since same inputs generate same outputs and called frequently.
|
|
"""
|
|
slug = re.sub(r'[^\w\s-]', '', text.lower())
|
|
slug = re.sub(r'[-\s]+', '-', slug)
|
|
return slug.strip('-')
|
|
|
|
|
|
def get_verse_text(book: str, chapter: int, verse: int) -> str:
|
|
"""Get the actual text of a specific verse."""
|
|
try:
|
|
text = bible.get_verse_text(book, chapter, verse)
|
|
if text:
|
|
return text
|
|
return f"{book} {chapter}:{verse} text not found"
|
|
except:
|
|
return f"{book} {chapter}:{verse}"
|
|
|
|
|
|
def is_verse_reference(query: str) -> bool:
|
|
"""Check if query looks like a verse reference."""
|
|
# Pattern for verse references like "John 3:16", "1 John 4:8", "Genesis 1:1", etc.
|
|
verse_pattern = r'^(I{1,3}|1|2|3)?\s*[A-Za-z]+(\s+[A-Za-z]+)?\s+\d+:\d+$'
|
|
return bool(re.match(verse_pattern, query.strip()))
|
|
|
|
|
|
def parse_verse_reference(query: str) -> Optional[Dict]:
|
|
"""Parse a verse reference string and return verse info if found."""
|
|
try:
|
|
cleaned_query = query.strip()
|
|
verse_ref = VerseReference.from_string(cleaned_query)
|
|
verse_text = bible.get_verse_text(verse_ref.book, verse_ref.chapter, verse_ref.verse)
|
|
|
|
if verse_text:
|
|
return {
|
|
"book": verse_ref.book,
|
|
"chapter": verse_ref.chapter,
|
|
"verse": verse_ref.verse,
|
|
"text": verse_text,
|
|
"reference": f"{verse_ref.book} {verse_ref.chapter}:{verse_ref.verse}",
|
|
"url": f"/book/{verse_ref.book}/chapter/{verse_ref.chapter}#verse-{verse_ref.verse}",
|
|
"score": 100.0,
|
|
"highlighted_text": verse_text
|
|
}
|
|
except Exception as e:
|
|
print(f"Error parsing verse reference '{query}': {e}")
|
|
|
|
# Try alternative book name formats (Roman numerals to numbers)
|
|
try:
|
|
alternative_query = query.strip()
|
|
alternative_query = re.sub(r'^I\s+', '1 ', alternative_query)
|
|
alternative_query = re.sub(r'^II\s+', '2 ', alternative_query)
|
|
alternative_query = re.sub(r'^III\s+', '3 ', alternative_query)
|
|
|
|
if alternative_query != query.strip():
|
|
verse_ref = VerseReference.from_string(alternative_query)
|
|
verse_text = bible.get_verse_text(verse_ref.book, verse_ref.chapter, verse_ref.verse)
|
|
|
|
if verse_text:
|
|
return {
|
|
"book": verse_ref.book,
|
|
"chapter": verse_ref.chapter,
|
|
"verse": verse_ref.verse,
|
|
"text": verse_text,
|
|
"reference": f"{verse_ref.book} {verse_ref.chapter}:{verse_ref.verse}",
|
|
"url": f"/book/{verse_ref.book}/chapter/{verse_ref.chapter}#verse-{verse_ref.verse}",
|
|
"score": 100.0,
|
|
"highlighted_text": verse_text
|
|
}
|
|
except Exception as e2:
|
|
print(f"Alternative parsing also failed for '{query}': {e2}")
|
|
|
|
return None
|
|
|
|
|
|
@lru_cache(maxsize=256)
|
|
def get_related_content(book: str, chapter: int = None, verse: int = None) -> Dict:
|
|
"""
|
|
Get related study guides, topics, and resources for a given passage.
|
|
Cached since this does expensive dictionary lookups and topic searches.
|
|
"""
|
|
related = {
|
|
"study_guides": [],
|
|
"topics": [],
|
|
"people": [],
|
|
"resources": []
|
|
}
|
|
|
|
# Map books to related people
|
|
book_people_map = {
|
|
"Genesis": [{"name": "Abraham", "url": "/family-tree"}, {"name": "Jacob", "url": "/family-tree"}],
|
|
"Exodus": [{"name": "Moses", "url": "/biblical-prophets/moses"}],
|
|
"1 Samuel": [{"name": "Samuel", "url": "/biblical-prophets"}],
|
|
"2 Samuel": [{"name": "David", "url": "/family-tree"}],
|
|
"1 Kings": [{"name": "Elijah", "url": "/biblical-prophets/elijah"}],
|
|
"2 Kings": [{"name": "Elijah", "url": "/biblical-prophets/elijah"}, {"name": "Elisha", "url": "/biblical-prophets"}],
|
|
"Isaiah": [{"name": "Isaiah", "url": "/biblical-prophets/isaiah"}],
|
|
"Jeremiah": [{"name": "Jeremiah", "url": "/biblical-prophets/jeremiah"}],
|
|
"Ezekiel": [{"name": "Ezekiel", "url": "/biblical-prophets/ezekiel"}],
|
|
"Daniel": [{"name": "Daniel", "url": "/biblical-prophets/daniel"}],
|
|
"Jonah": [{"name": "Jonah", "url": "/biblical-prophets/jonah"}],
|
|
"Matthew": [{"name": "The Twelve Apostles", "url": "/the-twelve-apostles"}],
|
|
"Mark": [{"name": "The Twelve Apostles", "url": "/the-twelve-apostles"}],
|
|
"Luke": [{"name": "The Twelve Apostles", "url": "/the-twelve-apostles"}, {"name": "John the Baptist", "url": "/biblical-prophets/john-the-baptist"}],
|
|
"John": [{"name": "John", "url": "/the-twelve-apostles/john"}],
|
|
"Acts": [{"name": "Peter", "url": "/the-twelve-apostles/peter"}, {"name": "Paul", "url": "/the-twelve-apostles"}],
|
|
"Ruth": [{"name": "Ruth", "url": "/women-of-the-bible/ruth"}],
|
|
"Esther": [{"name": "Esther", "url": "/women-of-the-bible/esther"}],
|
|
}
|
|
|
|
if book in book_people_map:
|
|
related["people"] = book_people_map[book]
|
|
|
|
# Map books/passages to special resources
|
|
if book in ["Exodus", "Leviticus", "Numbers", "Deuteronomy"]:
|
|
related["resources"].append({"name": "Biblical Festivals", "url": "/biblical-festivals"})
|
|
related["resources"].append({"name": "Biblical Covenants", "url": "/biblical-covenants"})
|
|
|
|
if book in ["Genesis", "Exodus", "Numbers"]:
|
|
related["resources"].append({"name": "Biblical Timeline", "url": "/biblical-timeline"})
|
|
|
|
if book in ["Joshua", "Judges", "1 Samuel", "2 Samuel", "1 Kings", "2 Kings"]:
|
|
related["resources"].append({"name": "Biblical Maps", "url": "/biblical-maps"})
|
|
|
|
if book in ["Matthew", "Mark", "Luke", "John"]:
|
|
related["resources"].append({"name": "Parables of Jesus", "url": "/parables"})
|
|
|
|
# Add topic links based on common themes
|
|
topic_keywords = {
|
|
"Salvation": ["John", "Romans", "Ephesians", "Titus"],
|
|
"Prayer": ["Matthew", "Luke", "1 Thessalonians", "James"],
|
|
"Love": ["John", "1 Corinthians", "1 John"],
|
|
"Faith": ["Hebrews", "James", "Romans"],
|
|
"Hope": ["Romans", "1 Peter", "Hebrews"],
|
|
"Peace": ["Philippians", "John", "Romans"],
|
|
"Wisdom": ["Proverbs", "Ecclesiastes", "James"],
|
|
}
|
|
|
|
topics_data = get_all_topics()
|
|
for topic_name in topics_data.keys():
|
|
if topic_name in topic_keywords and book in topic_keywords[topic_name]:
|
|
related["topics"].append({"name": topic_name, "url": f"/topics/{topic_name}"})
|
|
|
|
return related
|
|
|
|
|
|
# Load popular chapters data from JSON
|
|
_popular_data = _load_popular_chapters()
|
|
POPULAR_CHAPTERS = _popular_data["popular_chapters"]
|
|
HIGH_READERSHIP_BOOKS = _popular_data["high_readership_books"]
|
|
|
|
|
|
@lru_cache(maxsize=512)
|
|
def get_chapter_popularity_score(book: str, chapter: int) -> int:
|
|
"""
|
|
Calculate popularity score for a chapter (1-10 scale) based on well-known verses.
|
|
Cached since calculation is deterministic and called frequently.
|
|
"""
|
|
if book in POPULAR_CHAPTERS and chapter in POPULAR_CHAPTERS[book]:
|
|
return POPULAR_CHAPTERS[book][chapter]
|
|
|
|
default_score = 4
|
|
|
|
if chapter == 1:
|
|
default_score += 1
|
|
|
|
if book in HIGH_READERSHIP_BOOKS:
|
|
default_score += 1
|
|
|
|
total_chapters = len(bible.get_chapters_for_book(book))
|
|
if total_chapters <= 5:
|
|
default_score += 1
|
|
|
|
return min(default_score, 6)
|
|
|
|
|
|
# Load chapter explanations from JSON
|
|
CHAPTER_EXPLANATIONS = _load_chapter_explanations()
|
|
|
|
|
|
def get_chapter_popularity_explanation(book: str, chapter: int) -> str:
|
|
"""Get explanation for why a chapter is popular or what it contains."""
|
|
if book in CHAPTER_EXPLANATIONS and chapter in CHAPTER_EXPLANATIONS[book]:
|
|
return CHAPTER_EXPLANATIONS[book][chapter]
|
|
|
|
if chapter == 1:
|
|
return f"Opening chapter of {book} - introduces key themes and characters"
|
|
|
|
if book in ["Matthew", "Mark", "Luke", "John"]:
|
|
return "Gospel account of Jesus' life and ministry"
|
|
elif book in ["Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy"]:
|
|
return "Torah/Pentateuch - foundational law and history of Israel"
|
|
elif book in ["Psalms", "Proverbs", "Ecclesiastes", "Song of Solomon"]:
|
|
return "Wisdom literature - poetry and practical life guidance"
|
|
elif book in ["Isaiah", "Jeremiah", "Ezekiel", "Daniel"]:
|
|
return "Major prophet - messages of judgment and hope"
|
|
elif book in ["Romans", "1 Corinthians", "2 Corinthians", "Galatians", "Ephesians",
|
|
"Philippians", "Colossians", "1 Thessalonians", "2 Thessalonians",
|
|
"1 Timothy", "2 Timothy", "Titus", "Philemon"]:
|
|
return "Pauline epistle - apostolic teaching for the early church"
|
|
elif book == "Acts":
|
|
return "History of the early church and spread of the gospel"
|
|
elif book == "Revelation":
|
|
return "Apocalyptic vision of the end times and Christ's victory"
|
|
else:
|
|
return f"Part of {book} - explore this chapter to discover its significance"
|
|
|
|
|
|
# Load featured verses from JSON
|
|
FEATURED_VERSES = _load_featured_verses()
|
|
|
|
|
|
def get_daily_verse() -> Dict:
|
|
"""Get the verse of the day based on the current date."""
|
|
today = datetime.now()
|
|
day_of_year = today.timetuple().tm_yday
|
|
verse_index = day_of_year % len(FEATURED_VERSES)
|
|
|
|
book, chapter, verse = FEATURED_VERSES[verse_index]
|
|
verse_text = bible.get_verse_text(book, chapter, verse)
|
|
|
|
return {
|
|
"book": book,
|
|
"chapter": chapter,
|
|
"verse": verse,
|
|
"text": verse_text,
|
|
"reference": f"{book} {chapter}:{verse}",
|
|
"url": f"/book/{book}/chapter/{chapter}#verse-{verse}"
|
|
}
|