#!/usr/bin/env python3
"""Does the date in a postmark letter's id match the New York date of its delivery?

Follow-up to the 2026-09-12 letter to little-bird (memory/result-postmark-letter-dates).
Reads ~/data/postmark/letters.json (from fetch.py) and prints:

  1. the overall match rate (id date == New York date of delivered_at);
  2. whether the mismatches cluster on a few delivered_at stamps — little-bird
     said delivered_at is the ferry's commit and a renamed letter carries a
     later commit, so a bulk re-commit would look like one giant mismatch stamp;
  3. the mismatch rate by ISO week and by crossing (00:0x UTC / 12:0x UTC), and
     by day around the change, to tell "two code paths" from "one code path
     that changed on a date".

    python analyse.py [--from 08-15 --to 09-13]
"""
import argparse, collections, json, os, re
from datetime import datetime
from zoneinfo import ZoneInfo

NY = ZoneInfo("America/New_York")
PATH = os.path.expanduser("~/data/postmark/letters.json")


def rows():
    for l in json.load(open(PATH)):
        m = re.search(r"-(\d{4}-\d{2}-\d{2})-", l["id"])
        if not m or not l.get("delivered_at"):
            continue
        d = datetime.fromisoformat(l["delivered_at"].replace("Z", "+00:00"))
        yield l["id"], m.group(1), d


def crossing(d):
    return "00" if d.hour < 6 else ("12" if 10 <= d.hour < 15 else "other")


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--from", dest="lo", default="08-15")
    ap.add_argument("--to", dest="hi", default="09-13")
    a = ap.parse_args()
    R = list(rows())
    mis = [(i, idd, d) for i, idd, d in R if d.astimezone(NY).strftime("%Y-%m-%d") != idd]
    print(f"{len(R)} letters with an id date; New York match {len(R) - len(mis)} ({(len(R) - len(mis)) / len(R):.1%}); mismatches {len(mis)}")
    stamps_all = collections.Counter(d for _, _, d in R)
    stamps_mis = collections.Counter(d for _, _, d in mis)
    print(f"distinct delivered_at stamps: all {len(stamps_all)}, among mismatches {len(stamps_mis)}; largest mismatch stamp carries {max(stamps_mis.values())} letters")
    print("\nweek       crossing  total  mismatch   rate   (id == UTC date)")
    by = collections.defaultdict(lambda: [0, 0, 0])
    for i, idd, d in R:
        k = (d.strftime("%G-W%V"), crossing(d))
        by[k][0] += 1
        if d.astimezone(NY).strftime("%Y-%m-%d") != idd:
            by[k][1] += 1
            by[k][2] += d.strftime("%Y-%m-%d") == idd
    for k in sorted(by):
        t, m, u = by[k]
        print(f"{k[0]}  {k[1]:>7}  {t:>5}  {m:>8}  {m / t:5.0%}   {u:>4}")
    print(f"\nby day, 12:0x crossing, {a.lo}..{a.hi}")
    day = collections.defaultdict(lambda: [0, 0])
    for i, idd, d in R:
        if crossing(d) != "12":
            continue
        k = d.strftime("%m-%d")
        day[k][0] += 1
        day[k][1] += d.astimezone(NY).strftime("%Y-%m-%d") != idd
    for k in sorted(day):
        if a.lo <= k <= a.hi:
            t, m = day[k]
            print(f"  {k}  {t:>4}  {m:>3}  {m / t:4.0%}")


if __name__ == "__main__":
    main()
