Skip to content

Commit 913c0a2

Browse files
miamsclaude
andcommitted
feat(census): add AI-powered census transcription with schema-based extraction
Major new feature: Census Transcription Tab with Gemini vision model integration Key changes: - Add census transcription UI tab with image selection and preview - Implement YAML-based census schemas for years 1790-1950 - Create schema-driven prompt builder, response parser, and data validator - Add LLM interaction logger for debugging (llm_interactions.log) - Add HTML results display in browser tab with census-style formatting - Add full-screen image viewing in browser for detailed inspection - Optimize SQL queries from N+1 to batch pattern (3 queries vs 1400+) New components: - src/rmcitecraft/ui/tabs/census_transcription.py - Main UI tab - src/rmcitecraft/schemas/census/*.yaml - Census year schemas - src/rmcitecraft/services/census/ - Schema-based transcription services - src/rmcitecraft/llm/llm_logger.py - Dedicated LLM logging - src/rmcitecraft/models/census_schema.py - Schema dataclasses Testing: - Unit tests for all census service components - Integration tests for transcription service - Functional tests for schema loading 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 00b2985 commit 913c0a2

53 files changed

Lines changed: 9646 additions & 418 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

GEMINI.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# GEMINI.md
2+
3+
This document provides a comprehensive overview of the RMCitecraft project, its architecture, and development conventions to be used as a guide for future interactions.
4+
5+
## Project Overview
6+
7+
RMCitecraft is a Python-based desktop application designed to automate citation formatting and image management for the RootsMagic genealogy software. It primarily targets US Federal Census (1790-1950) and Find a Grave records. The application provides a user-friendly interface built with NiceGUI to streamline the process of creating *Evidence Explained* compliant citations and organizing associated images.
8+
9+
The core value proposition is to save genealogists significant time and effort by automating tedious manual tasks, such as reformatting citations, renaming image files, and linking media to records within the RootsMagic database.
10+
11+
## Architecture
12+
13+
RMCitecraft is built on a modern Python stack and employs a service-oriented architecture.
14+
15+
* **Frontend:** The user interface is a web-based UI powered by **NiceGUI** (native mode). The UI is structured into tabs for different functionalities like batch processing and citation management.
16+
* **Backend:** Python 3.11+ handles core logic, database interactions, and browser automation.
17+
* **Database:** The application directly interacts with **SQLite** databases (RootsMagic 8/9 format).
18+
* **CRITICAL:** Uses the ICU extension for `RMNOCASE` collation.
19+
* **State Persistence:** Application state is saved to `~/.rmcitecraft/batch_state.db` for crash recovery.
20+
* **Browser Automation:** **Playwright** is used to extract data from websites like FamilySearch and Find a Grave.
21+
* **LLM Integration:** **LangChain** integrates LLMs (Claude, GPT, Ollama) for intelligent citation parsing.
22+
* **Asynchronous Operations:** Extensive use of `asyncio` for non-blocking I/O and web automation.
23+
24+
### Robustness Features
25+
* **Adaptive Timeouts:** Dynamic adjustments for unreliable network connections.
26+
* **Page Health Monitoring:** Detects and recovers from browser crashes.
27+
* **Atomic Transactions:** Ensures database integrity during batch writes.
28+
29+
## Critical Protocols
30+
31+
### 1. Database Safety
32+
* **Working Copy:** Operations must be performed on a working copy (e.g., `data/Iiams.rmtree`), NEVER on the production database.
33+
* **RMNOCASE Collation:** You **must** load the ICU extension before querying the database.
34+
```python
35+
from src.rmcitecraft.database.connection import connect_rmtree
36+
conn = connect_rmtree('data/Iiams.rmtree') # Loads ICU extension automatically
37+
```
38+
* **Free-Form Citations:** For census citations, RootsMagic stores data in the **SourceTable.Fields BLOB**, not CitationTable TEXT fields.
39+
* **Shared Events:** Census records are often shared via `WitnessTable`. Check both `EventTable` and `WitnessTable`.
40+
41+
### 2. Citation Formatting
42+
* **Standard:** Output must strictly adhere to *Evidence Explained* format.
43+
* **Validation:** `FormattedCitationValidator` enforces rules (e.g., `footnote != short_footnote`).
44+
* **Census Variations:**
45+
* 1790-1840: Household head only.
46+
* 1850-1870: Individual, no ED.
47+
* 1880-1940: ED, sheet, family number.
48+
* 1950: Uses "stamp" instead of "sheet".
49+
50+
## Key Components
51+
52+
```
53+
src/rmcitecraft/
54+
├── config/ # Settings (settings.py)
55+
├── database/ # Database access layer
56+
│ ├── connection.py # ICU extension, RMNOCASE
57+
│ ├── batch_state_repository.py # Find a Grave state
58+
│ └── census_batch_state_repository.py # Census state
59+
├── services/ # Business logic
60+
│ ├── batch_processing.py # Workflow controller
61+
│ ├── familysearch_automation.py # FamilySearch browser automation
62+
│ ├── findagrave_automation.py # Find a Grave automation
63+
│ └── citation_formatter.py # Evidence Explained formatting
64+
├── ui/
65+
│ ├── tabs/ # Main UI tabs (BatchProcessing, CitationManager)
66+
│ └── components/ # Reusable components
67+
├── validation/
68+
│ └── data_quality.py # FormattedCitationValidator
69+
└── main.py # Application entry point
70+
```
71+
72+
## Development Setup
73+
74+
1. **Install UV:** `curl -LsSf https://astral.sh/uv/install.sh | sh`
75+
2. **Clone:** `git clone <repo_url>` and `cd RMCitecraft`
76+
3. **Install:** `uv sync`
77+
4. **Configure:** Copy `config/.env.example` to `.env` and set `RM_DATABASE_PATH`.
78+
79+
## Building and Running
80+
81+
| Command | Description |
82+
|---------|-------------|
83+
| `rmcitecraft start` | Start application (interactive) |
84+
| `rmcitecraft start -d` | Start in background |
85+
| `rmcitecraft status` | Check application status |
86+
| `rmcitecraft stop` | Stop application |
87+
| `uv run python sqlite-extension/python_example.py` | Verify DB connection |
88+
89+
## Development Conventions
90+
91+
### Before Modifying Code
92+
1. **Read source:** Understand the specific module.
93+
2. **Check tests:** Look for existing tests in `tests/unit/` and `tests/integration/`.
94+
3. **Database Ops:** Review `docs/reference/DATABASE_PATTERNS.md` before writing SQL.
95+
4. **Batch Processing:** Check state schema in `docs/reference/BATCH_STATE_DATABASE_SCHEMA.md`.
96+
97+
### Testing Strategy
98+
* **Unit Tests (`tests/unit/`):** Fast, isolated logic tests.
99+
* **Integration Tests (`tests/integration/`):** Component interaction.
100+
* **E2E Tests (`tests/e2e/`):** Browser automation (requires Chrome).
101+
* **Database Integrity:** Use comparison-based testing to catch undocumented RootsMagic conventions.
102+
103+
### Code Quality
104+
* **Lint/Format:** `uv run ruff check . && uv run ruff format .`
105+
* **Type Check:** `uv run mypy src/`
106+
* **Test:** `uv run pytest`
107+
108+
## Key Documentation
109+
* **`CLAUDE.md`**: Primary developer guide and project status.
110+
* **`AGENTS.md`**: Machine-readable instructions.
111+
* **`docs/reference/schema-reference.md`**: RootsMagic database schema.
112+
* **`docs/reference/DATABASE_PATTERNS.md`**: SQL patterns and examples.
113+
* **`docs/BATCH_PROCESSING_PHASE1_IMPLEMENTATION.md`**: Details on the batch processing UI.
114+
* **`docs/analysis/`**: In-depth analysis of Census eras and data structures.

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Genealogists using RootsMagic spend significant time manually formatting citatio
6464
### System
6565
- **Platform**: macOS (Apple Silicon optimized)
6666
- **Python**: 3.11+
67-
- **Database**: RootsMagic 8 or 9 (.rmtree SQLite database)
67+
- **Database**: RootsMagic 11 (tested), should work well for versions 9, 10, 11.
6868

6969
### Optional
7070
- **LLM API Key**: For citation parsing (Anthropic Claude, OpenAI, or local Ollama)
@@ -130,6 +130,13 @@ rmcitecraft help
130130
- **[PRD.md](PRD.md)** - Complete product requirements
131131
- **[docs/reference/schema-reference.md](docs/reference/schema-reference.md)** - RootsMagic database schema
132132

133+
## Caveats and Known Issues
134+
1. The 1950 Census tested the household-based form in a few jurisdictions in Ohio and Michigan, instead of the traditional multi-family form. The household form is not as well-parsed by FamilySearch, so RMCiteCraft asks for some fields. The ED can be identified using the Information button when displaying the image. These forms do not have sheet/page numbers, nor line numbers. Instead, I substitute a sequential stamped number and the image number, and I note the family form. For example:
135+
136+
137+
1950 U.S. census, Genesee County, Michigan, Burton Township, enumeration district (ED) 25-11, stamp 366, image 372 of 441, Brady G Ijames and Charlotte Ijames; imaged, "United States Census, 1950, household form," *FamilySearch*, (https://www.familysearch.org/ark:/61903/1:1:6JJZ-JB42  : accessed 25 November 2025).
138+
139+
133140
## License
134141

135142
MIT License - See LICENSE file for details.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# RMCitecraft Architecture Diagram
2+
3+
This diagram illustrates the high-level architecture of the RMCitecraft application, showing the relationships between the UI, controllers, services, data access layer, and external systems.
4+
5+
```mermaid
6+
graph TD
7+
%% Styling
8+
classDef ui fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
9+
classDef logic fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
10+
classDef service fill:#fff3e0,stroke:#ef6c00,stroke-width:2px;
11+
classDef db fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px;
12+
classDef ext fill:#eeeeee,stroke:#616161,stroke-width:2px,stroke-dasharray: 5 5;
13+
14+
subgraph "Frontend (NiceGUI)"
15+
A["Main Entry (main.py)"] --> B[Tab Manager]
16+
B --> C[Census Batch Tab]
17+
B --> D[Find a Grave Tab]
18+
B --> E[Citation Manager Tab]
19+
end
20+
class A,B,C,D,E ui;
21+
22+
subgraph "Application Logic"
23+
C --> F[Census Batch Controller]
24+
D --> G[Find a Grave Controller]
25+
E --> H[Citation Manager Controller]
26+
end
27+
class F,G,H logic;
28+
29+
subgraph "Core Services"
30+
F --> I[FamilySearch Automation]
31+
F --> J[LLM Extractor]
32+
G --> K[Find a Grave Automation]
33+
L[Image Processing Service]
34+
M[File Watcher]
35+
end
36+
class I,J,K,L,M service;
37+
38+
subgraph "Data Access Layer"
39+
N[Census State Repository]
40+
O[Find a Grave State Repository]
41+
P[RootsMagic Repository]
42+
Q[Database Connection]
43+
end
44+
class N,O,P,Q db;
45+
46+
subgraph "External Systems"
47+
R[FamilySearch.org]
48+
S[FindAGrave.com]
49+
T["LLM APIs (Claude/OpenAI)"]
50+
U[(Local State DB)]
51+
V[(RootsMagic DB + ICU)]
52+
W[File System]
53+
end
54+
class R,S,T,U,V,W ext;
55+
56+
%% Relationships
57+
I -- Playwright --> R
58+
K -- Playwright --> S
59+
J -- LangChain --> T
60+
F -- Persist State --> N
61+
N -- SQLite --> U
62+
G -- Persist State --> O
63+
O -- SQLite --> U
64+
F -- Read/Write --> P
65+
P -- Uses --> Q
66+
Q -- SQL/ICU --> V
67+
M -- Notify --> L
68+
L -- Move/Rename --> W
69+
```

src/rmcitecraft/llm/base.py

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,17 @@ def transcribe_census_image(
303303
"""
304304
Transcribe and extract data from census image.
305305
306+
.. deprecated::
307+
Use CensusTranscriber or CensusTranscriptionService instead.
308+
These provide year-specific schemas loaded from YAML files with
309+
much better accuracy and maintainability.
310+
311+
Example:
312+
from rmcitecraft.services.census_transcriber import CensusTranscriber
313+
314+
transcriber = CensusTranscriber(provider=self)
315+
result = transcriber.transcribe_census(image_path, census_year)
316+
306317
Args:
307318
image_path: Path to census image
308319
census_year: Year of census (affects expected fields)
@@ -316,12 +327,48 @@ def transcribe_census_image(
316327
NotImplementedError: If not supported
317328
LLMError: For other errors
318329
"""
330+
import warnings
331+
warnings.warn(
332+
"transcribe_census_image is deprecated. Use CensusTranscriber or "
333+
"CensusTranscriptionService for better accuracy with year-specific schemas.",
334+
DeprecationWarning,
335+
stacklevel=2
336+
)
337+
319338
if not self.supports(ModelCapability.VISION, model):
320-
raise NotImplementedError(f"Census transcription requires vision support")
339+
raise NotImplementedError("Census transcription requires vision support")
340+
341+
# Use the new service internally for better results
342+
try:
343+
from rmcitecraft.services.census.prompt_builder import CensusPromptBuilder
344+
from rmcitecraft.services.census.response_parser import CensusResponseParser
345+
from rmcitecraft.services.census.schema_registry import CensusSchemaRegistry
346+
347+
schema = CensusSchemaRegistry.get_schema(census_year)
348+
builder = CensusPromptBuilder()
349+
parser = CensusResponseParser()
350+
351+
prompt = builder.build_transcription_prompt(schema)
352+
response = self.complete_with_image(prompt, image_path, model, **kwargs)
353+
354+
data = parser.parse_response(response.text)
355+
persons = parser.extract_persons(data)
356+
metadata = parser.extract_metadata(data)
357+
confidence = data.pop("confidence", 0.7) if isinstance(data, dict) else 0.7
321358

322-
# Define schema based on census year
359+
return ExtractionResponse(
360+
data={"page_info": metadata, "records": persons},
361+
confidence=confidence,
362+
metadata={"census_year": census_year, "raw_response": response.text},
363+
)
364+
except (ImportError, FileNotFoundError):
365+
# Fallback to legacy behavior if new service not available
366+
pass
367+
368+
# Legacy implementation (kept for backward compatibility)
369+
import json
323370
if census_year >= 1850 and census_year <= 1880:
324-
schema = {
371+
schema_dict = {
325372
"dwelling_number": "string",
326373
"family_number": "string",
327374
"name": "string",
@@ -333,7 +380,7 @@ def transcribe_census_image(
333380
"page": "string",
334381
}
335382
elif census_year >= 1900:
336-
schema = {
383+
schema_dict = {
337384
"sheet": "string",
338385
"enumeration_district": "string",
339386
"family_number": "string",
@@ -346,7 +393,7 @@ def transcribe_census_image(
346393
"birthplace": "string",
347394
}
348395
else:
349-
schema = {
396+
schema_dict = {
350397
"head_of_household": "string",
351398
"free_white_males": "object",
352399
"free_white_females": "object",
@@ -355,20 +402,44 @@ def transcribe_census_image(
355402
"page": "string",
356403
}
357404

358-
import json
359405
prompt = f"""Transcribe this {census_year} US Federal Census image.
360406
361407
Extract the following information:
362-
{json.dumps(schema, indent=2)}
408+
{json.dumps(schema_dict, indent=2)}
363409
364410
Provide the data in JSON format with a confidence score (0.0-1.0).
365411
Focus on accurately transcribing names, ages, and locations."""
366412

367413
response = self.complete_with_image(prompt, image_path, model, **kwargs)
368414

369415
try:
370-
data = json.loads(response.text)
371-
confidence = data.pop('confidence', 0.7)
416+
import re
417+
text = response.text.strip()
418+
419+
json_match = re.search(r'```(?:json)?\s*\n?([\s\S]*?)\n?```', text)
420+
if json_match:
421+
text = json_match.group(1).strip()
422+
else:
423+
obj_start = text.find('{')
424+
arr_start = text.find('[')
425+
426+
if obj_start != -1 and (arr_start == -1 or obj_start < arr_start):
427+
text = text[obj_start:]
428+
elif arr_start != -1:
429+
text = text[arr_start:]
430+
431+
data = json.loads(text)
432+
433+
if isinstance(data, list):
434+
confidence = 0.7
435+
for record in data:
436+
if isinstance(record, dict) and 'confidence' in record:
437+
confidence = record.pop('confidence')
438+
break
439+
data = {'records': data}
440+
else:
441+
confidence = data.pop('confidence', 0.7)
442+
372443
return ExtractionResponse(
373444
data=data,
374445
confidence=confidence,

0 commit comments

Comments
 (0)