"""Dummy data for the on-time delivery control tower: twelve weeks of a delivery program whose volume grows about
fourfold, with a slump in on-time delivery and a recovery after fixes. Every name, figure and date is invented.

Usage: python3 generate.py (writes data.json next to this file; a fixed seed makes every run identical)
"""
import datetime
import json
import os
import random

R = random.Random(20261102)
START = datetime.date(2026, 7, 6)  # a Monday, 84 days before the last day
DAYS = 84
CITIES = ["Northport", "Westfield", "Lakeview", "Riverside", "Hillcrest", "Bayside"]
SHARE = [0.27, 0.21, 0.17, 0.15, 0.11, 0.09]   # each city's share of orders
STRAINED = {"Lakeview", "Bayside"}               # the two cities where carrier capacity runs short
CARRIERS = ["Carrier A", "Carrier B", "Carrier C"]
CAUSES = ["Missed pickup", "Weight error", "Carrier delay", "Address issue"]
STAGES = ["Awaiting pickup", "In transit", "Out for delivery", "Exception"]
TARGET = 0.85


def on_time_rate(day, city):
    """The program's arc: steady, a slump as volume outgrows pickups and labels, then recovery after fixes."""
    week = day // 7
    base = 0.88
    if 5 <= week <= 7:
        base = 0.88 - 0.06 * (week - 4)
    elif week == 8:
        base = 0.74
    elif week >= 9:
        base = min(0.89, 0.80 + 0.03 * (week - 8))
    if city in STRAINED and 5 <= week <= 8:
        base -= 0.07
    return max(0.5, min(0.97, base + R.uniform(-0.025, 0.025)))


def cause_mix(day, city):
    """How a day's late orders split across causes."""
    week = day // 7
    mix = [0.25, 0.2, 0.4, 0.15]
    if 5 <= week <= 8:
        mix = [0.38, 0.27, 0.27, 0.08]
    if city in STRAINED and 5 <= week <= 8:
        mix = [0.25, 0.15, 0.52, 0.08]
    return mix


def split(total, weights):
    counts = [int(total * w) for w in weights]
    counts[0] += total - sum(counts)
    return counts


def main():
    days = []
    for day in range(DAYS):
        date = START + datetime.timedelta(days=day)
        growth = 1 + 3 * (day / (DAYS - 1)) ** 1.15          # about 4x over the twelve weeks
        weekday = [1.08, 1.05, 1.0, 1.0, 1.04, 0.82, 0.7][date.weekday()]
        total = 140 * growth * weekday
        cities = []
        for i, city in enumerate(CITIES):
            orders = max(1, round(total * SHARE[i] * R.uniform(0.9, 1.1)))
            rate = on_time_rate(day, city)
            on_time = round(orders * rate)
            late = split(orders - on_time, cause_mix(day, city))
            canceled = round(orders * R.uniform(0.012, 0.03))
            gmv = round(orders * R.uniform(41, 49))
            cost = round(orders * R.uniform(6.4, 7.1), 1)
            promised = round(orders * R.uniform(2.9, 3.4), 1)  # sum of promised days
            cities.append([orders, on_time, *late, canceled, gmv, cost, promised])
        days.append({"date": date.isoformat(), "cities": cities})

    merchants = []
    for n in range(1, 41):
        city = R.choices(CITIES, SHARE)[0]
        orders = round(R.lognormvariate(5.6, 0.8))
        recent = max(0.55, min(0.98, R.gauss(0.84, 0.07) - (0.05 if city in STRAINED else 0)))
        prior = max(0.5, min(0.97, recent + R.gauss(-0.03, 0.05)))
        merchants.append({
            "name": f"Merchant {n:02d}", "city": city, "orders": orders, "onTime": round(recent, 3),
            "change": round(recent - prior, 3), "cause": R.choices(CAUSES, [0.3, 0.3, 0.3, 0.1])[0],
            "peerGroup": "Large" if orders > 500 else "Medium" if orders > 200 else "Small"})

    last = START + datetime.timedelta(days=DAYS - 1)
    queue = []
    for n in range(36):
        stage = R.choices(STAGES, [0.35, 0.35, 0.15, 0.15])[0]
        city = R.choices(CITIES, SHARE)[0]
        promised = last + datetime.timedelta(days=R.choice([0, 0, 1, 1, 1, 2, 2, 3]))
        issue = R.choices(CAUSES + ["At risk"], [0.25, 0.2, 0.2, 0.1, 0.25])[0]
        queue.append({
            "order": f"ORD-{R.randint(48000, 59999)}", "waybill": f"WB-{R.randint(10000000, 99999999)}",
            "merchant": f"Merchant {R.randint(1, 40):02d}", "city": city, "carrier": R.choice(CARRIERS),
            "stage": stage, "promised": promised.isoformat(), "issue": issue,
            "hoursLeft": (promised - last).days * 24 + R.choice([4, 8, 12, 16])})

    sources = [
        {"name": "Orders", "state": "good", "note": "Updated today at 06:00"},
        {"name": "Carrier A file", "state": "good", "note": "Updated today at 05:30"},
        {"name": "Carrier B file", "state": "warning", "note": "One day behind"},
        {"name": "Carrier C file", "state": "good", "note": "Updated today at 05:45"},
        {"name": "Cancellations", "state": "warning", "note": "Temporary source"},
        {"name": "Customer contacts", "state": "critical", "note": "Not available"},
    ]

    # The weekly summary the "write the summary" button shows, written from the last two weeks of this data
    def week_rate(w):
        rows = [c for d in days[w * 7:(w + 1) * 7] for c in d["cities"]]
        return sum(r[1] for r in rows) / sum(r[0] for r in rows)
    this, prev = week_rate(11), week_rate(10)
    low = min(range(len(CITIES)), key=lambda i: sum(d["cities"][i][1] for d in days[77:]) / sum(d["cities"][i][0] for d in days[77:]))
    summary = (f"On-time delivery reached {this:.1%} this week, up {100 * (this - prev):.1f} points on last week and "
               f"{'above' if this >= TARGET else 'below'} the {TARGET:.0%} target. {CITIES[low]} is the lowest city, where "
               f"carrier delays drive most late orders. Missed pickups and weight errors are down since pickup "
               f"windows moved and labels were checked before handoff. Next: add carrier capacity in "
               f"{' and '.join(sorted(STRAINED))}.")

    out = {"cities": CITIES, "carriers": CARRIERS, "causes": CAUSES, "stages": STAGES, "target": TARGET,
           "fields": ["orders", "onTime", *CAUSES, "canceled", "gmv", "cost", "promisedDays"],
           "days": days, "merchants": merchants, "queue": queue, "sources": sources, "summary": summary}
    with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "data.json"), "w") as f:
        json.dump(out, f, separators=(",", ":"))


if __name__ == "__main__":
    main()
