Skip to content

rboman/match_my_contacts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

40 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

match-my-contacts

Local-first tool for centralizing contacts, importing race results, and matching the two without depending on external services every time.

Problem Statement

After a race with several thousand participants, the goal is to answer one question quickly: which contacts took part, and what was their result? The project is designed from the start as three independent, reusable building blocks:

  1. contacts: import and store contacts locally.
  2. race_results: fetch and normalize race results.
  3. matching: match already-stored data and produce an actionable table.

A possible future extension is long-document analysis, for example meeting PDFs, to find passages that mention specific contacts.

Architecture Choices

  • Local source of truth: SQLite.
  • Secondary exports: JSON/CSV when useful.
  • Code organized by reusable domains, not as a single script.
  • Local project, no remote backend.
  • No ORM by default: sqlite3 is enough.

Current State

The first contacts building block now supports multiple local contact sources:

  • Desktop OAuth via Google People API (google_people)
  • Snapshot import of Google Contacts CSV exports (google_contacts_csv)
  • Multiple sources can coexist in the same SQLite database without automatic merging
  • Resync and reimport are isolated by source and source_account
  • Local inspection without network calls
  • JSON export of the local state

Local storage currently relies on three tables:

  • contacts
  • contact_methods
  • sync_runs

The second race_results building block is now implemented for ACN Timing:

  • parsing a public ACN Timing URL
  • fetching event metadata through Chronorace
  • fetching the results table through the Chronorace API used by ACN
  • raw JSON snapshots under raw/acn_timing/ in the configured data_dir
  • local SQLite storage in race_results.sqlite3 inside the configured data_dir
  • manual dataset aliases so you do not have to rely only on dataset_id

The third matching building block is now available:

  • 100% local matching between contacts and race_results
  • name normalization for accents, casing, and punctuation
  • exact matching first, then fuzzy matching with rapidfuzz
  • ambiguity guardrails via a minimum score and a minimum gap between candidates
  • terminal display and CSV export
  • sorting and filtering on matches (time, team, athlete, etc.)
  • reusable manual aliases on contacts
  • manual reviews to accept or reject a match result by result

The current matching quality is considered good enough for day-to-day use. The product priority is now to keep improving the local GUI, especially around manual review workflows.

Installation

python -m venv .venv
source .venv/bin/activate
pip install -e .[gui]

Python 3.10+ is supported.

Configurable Data Directory

The project now uses a machine-local config file to choose where local state is stored.

Config file:

~/.config/match_my_contacts/config.toml

On Windows, the equivalent location is:

$env:APPDATA\match_my_contacts\config.toml

The first CLI or GUI run creates this file automatically if it does not exist yet, pointing to data/ at the project root.

Format:

data_dir = "/absolute/path/to/match_my_contacts_data"
credentials_path = "/absolute/path/to/credentials.json"

All local state then derives from that directory:

  • contacts.sqlite3
  • race_results.sqlite3
  • google/token.json
  • raw/acn_timing/
  • exports/

This makes it possible to point several machines to a shared Dropbox-backed folder, as long as only one machine uses those SQLite databases at a time.

To inspect the active config and resolved paths:

match-my-contacts config show

To install the PySide6 desktop GUI as well:

pip install -e .[gui]

On Linux with an X11 session, Qt may also require the system library libxcb-cursor0:

sudo apt install libxcb-cursor0

Prepare Google Contacts Access

  1. Create a Google Cloud project.
  2. Enable Google People API.
  3. Create OAuth credentials for a Desktop application.
  4. Download the credentials.json file.

The credentials file can stay outside the repository. The OAuth token generated by the CLI is stored locally under google/token.json in the configured data_dir. If credentials.json is present at the project root, the sync command uses it automatically even if the CLI is launched from another directory.

Useful Commands

Test the CLI:

match-my-contacts hello

Synchronize Google contacts into SQLite:

match-my-contacts contacts sync
match-my-contacts contacts sync-google
match-my-contacts contacts sync --credentials /path/to/credentials.json

Import a Google Contacts CSV export into the local database:

match-my-contacts contacts import-google-csv --csv-path /path/to/google-contacts.csv
match-my-contacts contacts empty-db
match-my-contacts contacts vacuum-db

Fetch an ACN Timing results table:

match-my-contacts race-results fetch-acn --url 'https://www.acn-timing.com/?lng=FR#/events/2157220339092161/ctx/20260412_liege/generic/197994_1/home/LIVE1'

List local race datasets:

match-my-contacts race-results list-datasets
match-my-contacts race-results add-alias --dataset-id 1 --alias liege-15k-2026
match-my-contacts race-results list-results --dataset liege-15k-2026 --query dupont

Run local matching:

match-my-contacts matching run --dataset liege-15k-2026
match-my-contacts matching list --dataset liege-15k-2026 --team TEAMULIEGE --sort time
match-my-contacts matching export-csv --dataset liege-15k-2026 --output data/exports/matches.csv

Correct edge cases:

match-my-contacts contacts list --query noel
match-my-contacts contacts add-alias --contact-id 42 --alias "Jean Noel"
match-my-contacts matching run --dataset liege-15k-2026 --include-ambiguous --limit 20
match-my-contacts matching accept --dataset liege-15k-2026 --result-id 1234 --contact-id 42
match-my-contacts matching reject --dataset liege-15k-2026 --result-id 5678 --note "same name"
match-my-contacts matching list-reviews --dataset liege-15k-2026

List local contacts:

match-my-contacts contacts list
match-my-contacts contacts list --query dupont
match-my-contacts contacts list --source google_people
match-my-contacts contacts list-sources

Export local state as JSON:

match-my-contacts contacts export-json --output data/exports/contacts.json

Run tests:

pytest -q

Launch the local GUI:

match-my-contacts-gui

The current GUI is intentionally simple, but already useful for daily work:

  • local desktop UI in PySide6
  • Contacts, Race Results, Matching, and Config tabs
  • Help menu with About and Credits
  • single central table
  • Google sync from the Contacts tab
  • summary dialog after Sync Google
  • local contacts auto-load on startup when the database already exists
  • CSV import targeted at the real Google Contacts export format
  • Empty DB... button with explicit confirmation
  • VACUUM DB button to compact contacts.sqlite3 when needed
  • visible-column selection for the contacts table
  • optional source visibility in the contacts table
  • detailed contact dialog on double-click with source metadata
  • ACN import from the GUI
  • dataset alias creation
  • contacts JSON export
  • local matching filters and CSV export
  • local config inspection and editing
  • no network auto-sync on startup
  • manual reviews still remain CLI-only for now

Dropbox Migration

  1. run the CLI or GUI once to create the local config file
  2. edit config.toml so data_dir points to a shared Dropbox folder
  3. copy the current contents of data/ into that shared folder
  4. restart match-my-contacts or match-my-contacts-gui
  5. verify that contacts, datasets, aliases, and exports are all present

Precautions:

  • do not open the same SQLite database on two machines at the same time
  • let Dropbox finish syncing before switching machines
  • if Dropbox creates conflict copies, inspect the .sqlite3 files and conflicted copies before continuing

Practical usage guide:

  • read USAGE.md

Resume file for a future Codex session:

  • read HANDOFF.md

Recent GUI updates:

  • local contacts auto-load on startup when the database already exists
  • Sync Google button in the Contacts tab
  • summary dialog after Sync Google
  • Empty DB... button with confirmation and matching-review purge
  • VACUUM DB button to compact the local database
  • CSV import targeted at the real Google Contacts export format
  • persistent visible-column selection in the contacts table
  • optional source column in the contacts table
  • detailed contact dialog on double-click with source metadata
  • Help menu with About and Credits

Short Roadmap

  1. Keep consolidating the GUI as the daily local control surface.
  2. Add manual match review to the GUI next.
  3. Keep the business logic and CLI stable while the GUI grows.
  4. Extend race_results to other providers if needed.

About

๐Ÿ” Find contacts in race results

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages