"""Fishgap synthetic teaching example. No network, storage service or real customer data.

Place visitor-metrics-sample.csv next to this file and run with Python 3.9+.
The explicit coverage contract below applies only to this synthetic fixture.
For other data, confirm coverage independently; an absent date is not zero.
"""
import csv
import json
from datetime import date, datetime, timedelta, timezone
from pathlib import Path


def analyze(rows, start, end, confirmed_complete_days=()):
    start, end = date.fromisoformat(start), date.fromisoformat(end)
    if end < start or (end - start).days > 366:
        raise ValueError("Choose an ordered period of at most 367 days")
    days = [(start + timedelta(days=i)).isoformat() for i in range((end - start).days + 1)]
    complete = set(confirmed_complete_days)
    if not complete.issubset(days):
        raise ValueError("Coverage dates must belong to the selected period")
    daily_ids = {day: set() for day in days}
    observed_ids, transaction_ids = set(), set()
    for row in rows:
        transaction_id = str(row.get("transaction_id") or "").strip()
        customer_id = str(row.get("customer_id") or "").strip()
        if not transaction_id or not customer_id:
            raise ValueError("Missing transaction_id or customer_id")
        if transaction_id in transaction_ids:
            raise ValueError("Duplicate transaction_id; reconcile the input before counting")
        raw = str(row.get("transaction_time") or "").strip()
        stamp = datetime.fromisoformat(raw.replace("Z", "+00:00"))
        if stamp.utcoffset() is None:
            raise ValueError("Unknown source offset; do not assume UTC or local time")
        day = stamp.astimezone(timezone(timedelta(hours=8))).date().isoformat()
        if day not in daily_ids:
            raise ValueError("A record falls outside the selected business-date period")
        transaction_ids.add(transaction_id)
        observed_ids.add(customer_id)
        daily_ids[day].add(customer_id)
    complete_period = complete == set(days)
    observed_customer_days = sum(len(ids) for ids in daily_ids.values())
    return {
        "period": [days[0], days[-1]],
        "business_fixed_offset": "+08:00",
        "transactions_in_input": len(transaction_ids),
        "observed_unique_customer_ids": len(observed_ids),
        "observed_customer_days": observed_customer_days,
        "period_complete": complete_period,
        "average_daily_unique_ids": round(observed_customer_days / len(days), 4) if complete_period else None,
        "daily": [{"date": day, "observed_unique_ids": len(daily_ids[day]),
                   "complete_day_unique_ids": len(daily_ids[day]) if day in complete else None,
                   "coverage": "confirmed_complete" if day in complete else "unknown"} for day in days],
        "limitation": "Synthetic ID counts are not verified people, door traffic or a regional benchmark."
    }


if __name__ == "__main__":
    sample = Path(__file__).with_name("visitor-metrics-sample.csv")
    with sample.open(encoding="utf-8-sig", newline="") as stream:
        rows = list(csv.DictReader(stream))
    # The fixture explicitly covers all three days; day 3 has no transactions.
    result = analyze(rows, "2026-10-01", "2026-10-03",
                     ["2026-10-01", "2026-10-02", "2026-10-03"])
    print(json.dumps(result, ensure_ascii=False, indent=2))
