Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Event Sync Service

A service that ingests meeting records from two upstream systems (a CRM and a Calendar), reconciles the records that refer to the same real-world meeting, and serves a unified meeting list through a REST API and a simple web frontend — showing, for every piece of data, which source it came from and where the sources disagree.

Quick start

Requires Python 3.11+ and nothing else (no Node, no database).

./run.sh

That single command creates a virtualenv on first run, installs dependencies, and serves the app at http://localhost:8000. The reconciliation pipeline runs at startup from the files in data/.

To run the tests:

.venv/bin/python -m pytest tests/

What you'll see

  • 24 unified meetings built from 20 CRM records + 22 calendar records: 17 matched across both sources, 3 CRM-only, 4 calendar-only, and 1 intra-source duplicate collapsed.
  • Source badges on every meeting (CRM / Calendar / Both) and a per-field "from CRM / from Calendar / from Both" tag inside each meeting's details.
  • 5 conflicts surfaced, none resolved silently: a meeting the CRM says is in-person but the calendar says is on Zoom; three start-time disagreements; and a meeting cancelled in the CRM that the calendar still shows as confirmed. Expanding a conflicted field shows both source values side by side.
  • A data-quality report (bottom panel, or GET /api/report): every anomaly found in the input (malformed date, missing time, [at]-mangled email, non-email attendee, empty attendee list), the duplicate that was collapsed, and every cross-source match with its confidence score.

API

Endpoint Description
GET /api/meetings Unified list. Filters: ?source=crm|calendar|both, ?conflicts_only=true, ?q=<text>
GET /api/meetings/{id} One meeting with full per-field provenance and both raw source payloads
GET /api/report Data-quality anomalies, dedup decisions, match decisions with scores
GET /api/health Liveness check

Approach

The pipeline runs in three stages at startup (see docs/PLAN.md for the full design and docs/SPEC.md for every documented decision):

ingest  →  normalize  →  reconcile  →  in-memory store  →  REST API  →  frontend
  1. Ingest treats the input as untrusted: malformed values are repaired when the repair is unambiguous, skipped with a logged anomaly when not, and never crash the service.
  2. Normalize brings both schemas to a common shape: ISO dates (repairing 03-15/2025), timezone-normalized datetimes (naive timestamps assumed US Eastern; UTC-suffixed ones converted), cleaned attendee emails, and a name↔email mapping ("Sarah Chen"sarah.chen@firma.com).
  3. Reconcile does the real work, in three passes:
    • Intra-source dedup — two calendar entries for the same Pinnacle meeting collapse into one before cross-source matching (recurring-series instances are exempt: a weekly sync is many real meetings, not duplicates).
    • Matching — records don't share an ID, so candidate pairs (same day) are scored on weighted signals: participants (40%), start-time proximity (25%), title similarity (20%), company↔email-domain (15%). Pairs above a 0.45 threshold match greedily, best score first. Every accepted match and its score is visible in /api/report.
    • Merge — matched pairs merge field by field. Each field carries its value, its source, and — when sources materially disagree — a conflict object holding both values. Nulls on one side are enriched from the other. Nothing is dropped.

Key decisions

The full decision register — every ambiguity found in the data and how it's handled — is in docs/SPEC.md §5. The ones that shape the design:

  • Surface conflicts, don't resolve them. The system cannot know whether a meeting is really on Zoom or in the NYC office; the sales team can. Every material disagreement is preserved with both values and highlighted in the UI. An automatic resolver — however clever — would hide exactly the information a user needs most.
  • Display precedence: calendar wins operational facts (time, location, attendees); CRM wins business context (client, owner, status, notes). The calendar is what people actually attend; the CRM is where relationship lifecycle lives. Precedence only picks the displayed value — the losing value stays in the conflict object. A recency rule was considered and rejected: the sources expose created_at but no updated_at.
  • A 5-minute tolerance for start-time conflicts. Client meetings are operationally time-sensitive: a 30-minute disagreement between sources means someone shows up at the wrong time, so it must not pass silently. Verified against the data that the tight threshold adds only true conflicts, no noise.
  • "Conflict" means incompatible claims about the same fact — not any textual difference. "Conference Room B" vs "HQ - Conference Room B" is the same place at different specificity; "Zoom" vs "NYC Office" cannot both be true. Flagging cosmetic differences would bury the 5 real conflicts under noise.
  • Cancelled-in-CRM wins over confirmed-in-calendar, shown with a conflict flag: an explicit cancellation with a reschedule note is stronger evidence than a calendar entry nobody cleaned up.
  • No database, no frontend build toolchain, read-only API. The dataset is two static files read once at startup; the brief asks for a unified view. Each of these would be justified by a requirement this exercise doesn't have (incremental ingestion → database; write-back → a bidirectional sync design).

Known limitations

Documented in docs/SPEC.md §8, found while stress-testing the design:

  • Matching blocks candidate pairs by date, so a meeting rescheduled to a different day in only one source would appear as two single-source records instead of one meeting with a date conflict. Mitigations (not needed for this dataset): widen the blocking window to ±1 day, or add a participant-based blocking pass.
  • Display precedence can promote a stale value when the other source is fresher; without an updated_at field no automatic rule can do better, so the conflict badge is the deliberate mitigation.

With more time

  • Participant-based secondary blocking (closes the rescheduling gap above).
  • Persistence + incremental ingestion if the sources became live APIs.
  • Pagination and caching once the dataset outgrows a single response.

Testing

21 behavioral tests run the full pipeline against the real data files — one test per documented decision in the SPEC's ambiguity register (timezone normalization, dedup, enrichment, conflict preservation, malformed-data handling…), plus synthetic robustness cases (missing IDs, non-string fields, missing data files) added after an independent review pass.

AI collaboration

This project was built in close collaboration with AI (Claude Code), as encouraged by the brief. The process and the division of labor — including where AI proposals were rejected or corrected by human review — are documented in docs/AI_COLLABORATION.md. The planning artifacts it produced are docs/SPEC.md, docs/PLAN.md and docs/TASKS.md.

Time spent

~3 hours of focused time across two days. Most of it went into analysis and decision-making rather than typing: understanding the data's edge cases, deciding how each one should be handled (the ambiguity register in docs/SPEC.md §5), reviewing each implementation delivery, and verifying the final result. The implementation itself was largely executed by AI under that direction — see docs/AI_COLLABORATION.md for the exact division of labor and where human review changed the outcome.

About

Event Sync Service — reconciles CRM and calendar meeting data into a unified view with per-field provenance, served via REST API + web UI

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages