mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 23:00:16 +00:00
9d989e2189
- Create dedicated accessibility page at /about/accessibility - Add skip link for keyboard/screen reader users - Add keyboard navigation to interlinear landing page - Implement 2D grid navigation for theological studies on homepage - Add accessibility section to homepage with keyboard shortcut info - Fix verse text color contrast on fruits of the spirit page - Fix malformed proverbs commentary data - Update about page with accessibility link 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
324 lines
12 KiB
Python
324 lines
12 KiB
Python
"""About routes - stats, cross-references index, and about page."""
|
|
import json
|
|
import re
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, Request
|
|
from fastapi.responses import HTMLResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from ..kjv import bible
|
|
from ..utils.books import OT_BOOKS, NT_BOOKS
|
|
|
|
router = APIRouter()
|
|
templates = None
|
|
|
|
|
|
def init_templates(t: Jinja2Templates):
|
|
"""Initialize templates for about routes."""
|
|
global templates
|
|
templates = t
|
|
|
|
|
|
# =============================================================================
|
|
# Routes
|
|
# =============================================================================
|
|
|
|
@router.get("/about/stats", response_class=HTMLResponse)
|
|
async def stats(request: Request):
|
|
"""Hidden statistics page - comprehensive site metrics"""
|
|
data_dir = Path(__file__).parent.parent / "data"
|
|
|
|
# Bible statistics
|
|
total_verses = bible.get_verse_count()
|
|
total_books = len(bible.get_books())
|
|
total_chapters = len(bible.get_chapters())
|
|
|
|
# Calculate words in Bible
|
|
total_words = sum(len(verse.text.split()) for verse in bible.iter_verses())
|
|
|
|
# Count unique book types
|
|
ot_books = len(OT_BOOKS)
|
|
nt_books = len(NT_BOOKS)
|
|
|
|
# Data file statistics
|
|
total_json_files = len(list(data_dir.glob('**/*.json')))
|
|
|
|
# Verse commentary statistics
|
|
verse_commentary_files = len(list((data_dir / 'verse_commentary').glob('*.json')))
|
|
total_commentary_verses = 0
|
|
total_commentary_words = 0
|
|
for file in (data_dir / 'verse_commentary').glob('*.json'):
|
|
data = json.load(open(file))
|
|
commentary = data.get('commentary', {})
|
|
for chapter in commentary.values():
|
|
for verse_data in chapter.values():
|
|
total_commentary_verses += 1
|
|
# Count words in analysis + historical
|
|
analysis = verse_data.get('analysis', '')
|
|
historical = verse_data.get('historical', '')
|
|
# Strip HTML tags for accurate word count
|
|
clean_analysis = re.sub(r'<[^>]+>', '', analysis)
|
|
clean_historical = re.sub(r'<[^>]+>', '', historical)
|
|
total_commentary_words += len(clean_analysis.split()) + len(clean_historical.split())
|
|
|
|
# Cross-reference statistics
|
|
cross_reference_files = len(list((data_dir / 'cross_references').glob('*.json')))
|
|
total_cross_refs = 0
|
|
verses_with_cross_refs = 0
|
|
for file in (data_dir / 'cross_references').glob('*.json'):
|
|
data = json.load(open(file))
|
|
verses_with_cross_refs += len(data)
|
|
for verse_refs in data.values():
|
|
total_cross_refs += len(verse_refs)
|
|
|
|
# Red letter statistics
|
|
red_letter_data = json.load(open(data_dir / 'red_letter_verses.json'))
|
|
total_red_letter_verses = len(red_letter_data['verses'])
|
|
|
|
# Study resources
|
|
study_guide_files = len(list((data_dir / 'study_guides').glob('*.json')))
|
|
topic_files = len(list((data_dir / 'topics').glob('*.json')))
|
|
resource_files = len(list((data_dir / 'resources').glob('*.json')))
|
|
story_files = len(list((data_dir / 'stories').glob('*.json')))
|
|
|
|
# Bible Stories statistics
|
|
total_stories = 0
|
|
stories_with_kids = 0
|
|
total_story_characters = set()
|
|
total_story_themes = set()
|
|
total_story_words = 0
|
|
total_kids_story_words = 0
|
|
for file in (data_dir / 'stories').glob('*.json'):
|
|
try:
|
|
story_data = json.load(open(file))
|
|
stories = story_data.get('stories', [])
|
|
total_stories += len(stories)
|
|
for story in stories:
|
|
# Count words in narrative
|
|
narrative = story.get('narrative', '')
|
|
total_story_words += len(narrative.split())
|
|
if story.get('kids_narrative'):
|
|
stories_with_kids += 1
|
|
total_kids_story_words += len(story.get('kids_narrative', '').split())
|
|
for char in story.get('characters', []):
|
|
total_story_characters.add(char)
|
|
for theme in story.get('themes', []):
|
|
total_story_themes.add(theme)
|
|
except (json.JSONDecodeError, IOError):
|
|
continue
|
|
|
|
# Interlinear data size
|
|
interlinear_file = data_dir / 'interlinear.json.gz'
|
|
interlinear_size_mb = interlinear_file.stat().st_size / 1024 / 1024 if interlinear_file.exists() else 0
|
|
|
|
# Calculate total data directory size
|
|
total_data_size = sum(f.stat().st_size for f in data_dir.glob('**/*') if f.is_file())
|
|
total_data_size_mb = total_data_size / 1024 / 1024
|
|
|
|
# Book abbreviations
|
|
bible_metadata_file = data_dir / 'bible_metadata.json'
|
|
bible_metadata = json.load(open(bible_metadata_file))
|
|
total_abbreviations = len(bible_metadata.get('book_abbreviations', {}))
|
|
|
|
# Biographies
|
|
bio_data = json.load(open(data_dir / 'biographies.json'))
|
|
total_biographies = len(bio_data.get('biographies', {}))
|
|
|
|
# Reading plans
|
|
reading_plan_files = len(list((data_dir / 'reading_plans').glob('*.json')))
|
|
|
|
# Strong's concordance
|
|
strongs_dir = data_dir / 'strongs'
|
|
if strongs_dir.exists():
|
|
hebrew_data = json.load(open(strongs_dir / 'hebrew.json'))
|
|
greek_data = json.load(open(strongs_dir / 'greek.json'))
|
|
total_hebrew_entries = len(hebrew_data)
|
|
total_greek_entries = len(greek_data)
|
|
else:
|
|
total_hebrew_entries = 0
|
|
total_greek_entries = 0
|
|
|
|
stats_data = {
|
|
'bible': {
|
|
'total_verses': total_verses,
|
|
'total_books': total_books,
|
|
'ot_books': ot_books,
|
|
'nt_books': nt_books,
|
|
'total_chapters': total_chapters,
|
|
'total_words': total_words,
|
|
'avg_words_per_verse': round(total_words / total_verses, 1),
|
|
'avg_verses_per_chapter': round(total_verses / total_chapters, 1),
|
|
},
|
|
'commentary': {
|
|
'files': verse_commentary_files,
|
|
'verses_covered': total_commentary_verses,
|
|
'total_words': total_commentary_words,
|
|
'avg_words_per_verse': round(total_commentary_words / total_commentary_verses, 1) if total_commentary_verses > 0 else 0,
|
|
'coverage_percent': round((total_commentary_verses / total_verses) * 100, 1),
|
|
},
|
|
'cross_references': {
|
|
'files': cross_reference_files,
|
|
'verses_with_refs': verses_with_cross_refs,
|
|
'total_references': total_cross_refs,
|
|
'avg_refs_per_verse': round(total_cross_refs / verses_with_cross_refs, 1) if verses_with_cross_refs > 0 else 0,
|
|
'coverage_percent': round((verses_with_cross_refs / total_verses) * 100, 1),
|
|
},
|
|
'red_letter': {
|
|
'total_verses': total_red_letter_verses,
|
|
'percent_of_bible': round((total_red_letter_verses / total_verses) * 100, 1),
|
|
},
|
|
'study_resources': {
|
|
'study_guides': study_guide_files,
|
|
'topics': topic_files,
|
|
'resources': resource_files,
|
|
'stories': story_files,
|
|
'biographies': total_biographies,
|
|
'reading_plans': reading_plan_files,
|
|
},
|
|
'bible_stories': {
|
|
'categories': story_files,
|
|
'total_stories': total_stories,
|
|
'stories_with_kids': stories_with_kids,
|
|
'unique_characters': len(total_story_characters),
|
|
'unique_themes': len(total_story_themes),
|
|
'total_words': total_story_words,
|
|
'kids_words': total_kids_story_words,
|
|
},
|
|
'language_tools': {
|
|
'hebrew_entries': total_hebrew_entries,
|
|
'greek_entries': total_greek_entries,
|
|
'total_strongs': total_hebrew_entries + total_greek_entries,
|
|
'interlinear_size_mb': round(interlinear_size_mb, 1),
|
|
},
|
|
'data': {
|
|
'total_json_files': total_json_files,
|
|
'total_size_mb': round(total_data_size_mb, 1),
|
|
'book_abbreviations': total_abbreviations,
|
|
}
|
|
}
|
|
|
|
books = bible.get_books()
|
|
breadcrumbs = [
|
|
{"text": "Home", "url": "/"},
|
|
{"text": "About", "url": "/about"},
|
|
{"text": "Statistics", "url": None}
|
|
]
|
|
|
|
return templates.TemplateResponse(
|
|
"stats.html",
|
|
{
|
|
"request": request,
|
|
"books": books,
|
|
"stats": stats_data,
|
|
"breadcrumbs": breadcrumbs,
|
|
}
|
|
)
|
|
|
|
|
|
@router.get("/about/cross-references", response_class=HTMLResponse)
|
|
async def cross_references_index(request: Request):
|
|
"""Cross-references index - list all verses with cross-references"""
|
|
data_dir = Path(__file__).parent.parent / "data" / "cross_references"
|
|
|
|
# Build index of all verses with cross-references, grouped by book
|
|
crossref_index = defaultdict(lambda: defaultdict(list))
|
|
|
|
for file in sorted(data_dir.glob('*.json')):
|
|
with open(file, 'r') as f:
|
|
data = json.load(f)
|
|
|
|
for verse_key, refs in data.items():
|
|
# Parse verse key: "Book:Chapter:Verse"
|
|
parts = verse_key.split(':')
|
|
if len(parts) == 3:
|
|
book, chapter, verse = parts
|
|
crossref_index[book][int(chapter)].append({
|
|
'verse': int(verse),
|
|
'ref_count': len(refs)
|
|
})
|
|
|
|
# Sort books in biblical order (OT then NT)
|
|
biblical_order = OT_BOOKS + NT_BOOKS
|
|
book_order = {book: i for i, book in enumerate(biblical_order)}
|
|
|
|
# Convert to regular dict and sort
|
|
crossref_index = {
|
|
book: {
|
|
chapter: sorted(verses, key=lambda x: x['verse'])
|
|
for chapter, verses in sorted(chapters.items())
|
|
}
|
|
for book, chapters in sorted(crossref_index.items(), key=lambda x: book_order.get(x[0], 999))
|
|
}
|
|
|
|
# Calculate statistics
|
|
total_books = len(crossref_index)
|
|
total_verses = sum(
|
|
len(verses)
|
|
for chapters in crossref_index.values()
|
|
for verses in chapters.values()
|
|
)
|
|
total_refs = sum(
|
|
sum(v['ref_count'] for v in verses)
|
|
for chapters in crossref_index.values()
|
|
for verses in chapters.values()
|
|
)
|
|
|
|
books = bible.get_books()
|
|
breadcrumbs = [
|
|
{"text": "Home", "url": "/"},
|
|
{"text": "About", "url": "/about"},
|
|
{"text": "Cross-References Index", "url": None}
|
|
]
|
|
|
|
return templates.TemplateResponse(
|
|
"cross_references_index.html",
|
|
{
|
|
"request": request,
|
|
"books": books,
|
|
"crossref_index": crossref_index,
|
|
"total_books": total_books,
|
|
"total_verses": total_verses,
|
|
"total_refs": total_refs,
|
|
"breadcrumbs": breadcrumbs,
|
|
}
|
|
)
|
|
|
|
|
|
@router.get("/about", response_class=HTMLResponse)
|
|
async def about(request: Request):
|
|
"""About page - site information, creator, data sources, theological approach"""
|
|
books = bible.get_books()
|
|
breadcrumbs = [
|
|
{"text": "Home", "url": "/"},
|
|
{"text": "About", "url": None}
|
|
]
|
|
return templates.TemplateResponse(
|
|
"about.html",
|
|
{
|
|
"request": request,
|
|
"books": books,
|
|
"breadcrumbs": breadcrumbs,
|
|
}
|
|
)
|
|
|
|
|
|
@router.get("/about/accessibility", response_class=HTMLResponse)
|
|
async def accessibility(request: Request):
|
|
"""Accessibility page - keyboard navigation, screen readers, text-to-speech"""
|
|
books = bible.get_books()
|
|
breadcrumbs = [
|
|
{"text": "Home", "url": "/"},
|
|
{"text": "About", "url": "/about"},
|
|
{"text": "Accessibility", "url": None}
|
|
]
|
|
return templates.TemplateResponse(
|
|
"accessibility.html",
|
|
{
|
|
"request": request,
|
|
"books": books,
|
|
"breadcrumbs": breadcrumbs,
|
|
}
|
|
)
|