ride-receipts-llm
Run a reproducible 3-stage pipeline:
- initialize/validate SQLite schema (fixed; do not edit)
- fetch full receipt emails into JSONL
- extract structured rides with LLM (one-shot + repair)
- upsert into SQLite
Prerequisites and safety
- Require
gog CLI installed and authenticated for the selected Gmail account.
- Prefer configured account:
skills.entries.ride-receipts-llm.config.gmailAccount; if missing, ask user for account explicitly.
- Ask for date scope before fetch: all-time, after
YYYY-MM-DD, or between dates.
- Treat receipt content as sensitive financial/location data.
- Before extraction, explicitly confirm user is okay sending raw email HTML to the active LLM.
- Extraction uses raw
text_html from emails; do not claim local-only parsing.
- Never hallucinate fields; keep unknown values
null.
Paths
- Schema (do not modify):
skills/ride-receipts-llm/references/schema_rides.sql
- Emails JSONL:
./data/ride_emails.jsonl
- Extracted rides JSONL:
./data/rides_extracted.jsonl
- SQLite DB:
./data/rides.sqlite
0) Initialize DB
python3 skills/ride-receipts-llm/scripts/init_db.py \
--db ./data/rides.sqlite \
--schema skills/ride-receipts-llm/references/schema_rides.sql
1) Fetch Gmail receipts → JSONL
python3 skills/ride-receipts-llm/scripts/fetch_emails_jsonl.py \
--account <gmail-account> \
--after YYYY-MM-DD \
--before YYYY-MM-DD \
--max-per-provider 5000 \
--out ./data/ride_emails.jsonl
- Omit
--after / --before when not needed.
- Output rows include provider metadata, snippet, and raw
text_html.
2) LLM extraction contract
Read ./data/ride_emails.jsonl; write one JSON object per line to ./data/rides_extracted.jsonl.
Per email:
- Run one-shot extraction for all fields.
- Quality-gate:
amount,currency,pickup,dropoff,payment_method,distance_text,duration_text,start_time_text,end_time_text.
- If any are missing, run repair pass(es) for missing fields only.
- Merge additively; never replace existing non-null values with
null.
Schema (one line per ride):
{
"provider": "Uber|Bolt|Yandex|Lyft",
"source": {"gmail_message_id": "...", "email_date": "YYYY-MM-DD HH:MM", "subject": "..."},
"ride": {
"start_time_text": "...",
"end_time_text": "...",
"total_text": "...",
"currency": "EUR|PLN|USD|BYN|RUB|UAH|null",
"amount": 12.34,
"pickup": "...",
"dropoff": "...",
"pickup_city": "...",
"pickup_country": "...",
"dropoff_city": "...",
"dropoff_country": "...",
"payment_method": "...",
"driver": "...",
"distance_text": "...",
"duration_text": "...",
"notes": "..."
}
}
Rules:
- Use
text_html as primary source; fallback to snippet only if text_html is empty.
- Keep addresses/time strings verbatim.
- Keep
amount numeric; if only textual total exists, set amount: null and preserve text in total_text.
3) Insert extracted rides → SQLite
python3 skills/ride-receipts-llm/scripts/insert_rides_sqlite_jsonl.py \
--db ./data/rides.sqlite \
--schema skills/ride-receipts-llm/references/schema_rides.sql \
--rides-jsonl ./data/rides_extracted.jsonl
Schema is idempotent via UNIQUE(provider, gmail_message_id) ON CONFLICT REPLACE.
1---2name: ride-receipts-llm3description: Build, refresh, export, and query a local SQLite ride-history database from Gmail ride receipt emails (Uber, Bolt, Yandex Go, Lyft) using LLM extraction from full email HTML. Use when asked to ingest receipts, rebuild/update `rides.sqlite`, investigate ride totals/routes, or extend provider coverage. Requires `gog` Gmail CLI plus authenticated Google account access; processes sensitive receipt data and sends raw email HTML to the active LLM.4---56# ride-receipts-llm78Run a reproducible 3-stage pipeline:9100) initialize/validate SQLite schema (fixed; do not edit)111) fetch full receipt emails into JSONL122) extract structured rides with LLM (one-shot + repair)133) upsert into SQLite1415## Prerequisites and safety1617- Require `gog` CLI installed and authenticated for the selected Gmail account.18- Prefer configured account: `skills.entries.ride-receipts-llm.config.gmailAccount`; if missing, ask user for account explicitly.19- Ask for date scope before fetch: all-time, after `YYYY-MM-DD`, or between dates.20- Treat receipt content as sensitive financial/location data.21- Before extraction, explicitly confirm user is okay sending raw email HTML to the active LLM.22- Extraction uses raw `text_html` from emails; do not claim local-only parsing.23- Never hallucinate fields; keep unknown values `null`.2425## Paths2627- Schema (do not modify): `skills/ride-receipts-llm/references/schema_rides.sql`28- Emails JSONL: `./data/ride_emails.jsonl`29- Extracted rides JSONL: `./data/rides_extracted.jsonl`30- SQLite DB: `./data/rides.sqlite`3132## 0) Initialize DB3334```bash35python3 skills/ride-receipts-llm/scripts/init_db.py \36 --db ./data/rides.sqlite \37 --schema skills/ride-receipts-llm/references/schema_rides.sql38```3940## 1) Fetch Gmail receipts → JSONL4142```bash43python3 skills/ride-receipts-llm/scripts/fetch_emails_jsonl.py \44 --account <gmail-account> \45 --after YYYY-MM-DD \46 --before YYYY-MM-DD \47 --max-per-provider 5000 \48 --out ./data/ride_emails.jsonl49```5051- Omit `--after` / `--before` when not needed.52- Output rows include provider metadata, snippet, and raw `text_html`.5354## 2) LLM extraction contract5556Read `./data/ride_emails.jsonl`; write one JSON object per line to `./data/rides_extracted.jsonl`.5758Per email:591. Run one-shot extraction for all fields.602. Quality-gate: `amount,currency,pickup,dropoff,payment_method,distance_text,duration_text,start_time_text,end_time_text`.613. If any are missing, run repair pass(es) for missing fields only.624. Merge additively; never replace existing non-null values with `null`.6364Schema (one line per ride):6566```json67{68 "provider": "Uber|Bolt|Yandex|Lyft",69 "source": {"gmail_message_id": "...", "email_date": "YYYY-MM-DD HH:MM", "subject": "..."},70 "ride": {71 "start_time_text": "...",72 "end_time_text": "...",73 "total_text": "...",74 "currency": "EUR|PLN|USD|BYN|RUB|UAH|null",75 "amount": 12.34,76 "pickup": "...",77 "dropoff": "...",78 "pickup_city": "...",79 "pickup_country": "...",80 "dropoff_city": "...",81 "dropoff_country": "...",82 "payment_method": "...",83 "driver": "...",84 "distance_text": "...",85 "duration_text": "...",86 "notes": "..."87 }88}89```9091Rules:92- Use `text_html` as primary source; fallback to `snippet` only if `text_html` is empty.93- Keep addresses/time strings verbatim.94- Keep `amount` numeric; if only textual total exists, set `amount: null` and preserve text in `total_text`.9596## 3) Insert extracted rides → SQLite9798```bash99python3 skills/ride-receipts-llm/scripts/insert_rides_sqlite_jsonl.py \100 --db ./data/rides.sqlite \101 --schema skills/ride-receipts-llm/references/schema_rides.sql \102 --rides-jsonl ./data/rides_extracted.jsonl103```104105Schema is idempotent via `UNIQUE(provider, gmail_message_id) ON CONFLICT REPLACE`.