import io, segno, os, json
HERE=os.path.dirname(os.path.abspath(__file__))

RAMP = ["#13314f","#2d5a82","#4d82a8","#7aa6c4","#a9803f","#c6a463","#ddd6c6"]

def qr(data,color="#13314f"):
    b=io.BytesIO()
    segno.make(data,error='m').save(b,kind='svg',scale=10,border=2,dark=color,light=None,xmldecl=False,svgns=True)
    return b.getvalue().decode().strip().replace('width="330" height="330"','width="100%" height="100%"')

AVATAR = open(os.path.join(HERE,'avatar.svg')).read().replace('width="200" height="200"','width="100%" height="100%"')

def conic(alloc):
    stops=[];cum=0
    for i,(_,pct,_) in enumerate(alloc):
        stops.append(f"{RAMP[i]} {cum}% {cum+pct}%");cum+=pct
    return "background:conic-gradient("+",".join(stops)+");"

def legend(alloc):
    rows=""
    for i,(cat,pct,gb) in enumerate(alloc):
        rows+=(f'<tr><td class="sw"><span class="swatch" style="background:{RAMP[i]}"></span></td>'
               f'<td class="cat">{cat}</td><td class="pct">{pct}%</td><td class="vol">{gb}</td></tr>')
    return rows

def summary(rows):
    out=""
    for label,tp,pr,ch,ytd in rows:
        out+=(f'<tr><td class="lead">{label}</td><td class="fig">{tp}</td><td class="fig">{pr}</td>'
              f'<td class="fig chg-pos">{ch}</td><td class="fig">{ytd}</td></tr>')
    return out

def chips(cs):
    return "".join(f'<span class="chip">{c}</span>' for c in cs)

def cmp(rows):
    """Diverging axis vs. the average home. Bars swing left (below) / right (above);
    colour is by sentiment (good=green, bad=red, neutral=grey) — independent of direction,
    so 'smaller is better' metrics (latency, downtime) read green when they swing left.
    Real units stay on the bar alongside the % deviation."""
    head=('<div class="cmp-axislabels"><div></div><div class="ends">'
          '<span>&#9666; below average</span><div class="ctr"><span>Average home</span></div>'
          '<span>above average &#9656;</span></div></div>')
    out=""
    for r in rows:
        d=r["delta"]; w=min(abs(d),100)/100*46.0
        side="right" if d>=0 else "left"
        sign="+" if d>0 else ("&minus;" if d<0 else "")
        dd=f'{sign}{abs(d):g}%'
        vd=f'<div class="vd">{r["verdict"]}</div>' if r.get("verdict") else ""
        out+=(f'<div class="cmp-row"><div class="cmp-metric">{r["metric"]}{vd}</div>'
              f'<div class="cmp-track"><div class="cmp-bar {r["sentiment"]} {side}" style="width:{w:.1f}%">'
              f'<span class="cmp-val">{r["value"]} &middot; {dd}</span></div></div></div>')
    return head+out

def handled_block(items):
    out=""
    for when,what,who in items:
        out+=(f'<div class="hx"><div class="when">{when}</div>'
              f'<div class="what">{what}<div class="who">{who}</div></div></div>')
    return f'<div class="handled">{out}</div>'

def suggestions_block(cfg):
    if "affirmation" in cfg:
        a=cfg["affirmation"]
        return (f'<div class="affirm"><div class="ic">{a["ic"]}</div><div>'
                f'<div class="t">{a["t"]}</div><div class="d">{a["d"]}</div></div></div>')
    items=""
    for ic,q,r,ask in cfg["suggestions"]:
        items+=(f'<div class="sg"><div class="ic">{ic}</div><div><div class="q">{q}</div>'
                f'<div class="r">{r}</div><div class="ask">{ask}</div></div></div>')
    return f'<div class="suggest">{items}</div>'

STYLE = open(os.path.join(HERE,'style.css')).read()

def build(cfg):
    qr_live = qr(cfg["live_url"]); qr_stmt = qr(cfg["stmt_url"])
    # Optional sections — rendered only when honest data is present, otherwise omitted
    # entirely (no faked donut, no invented peer cohort, no phantom Alliance match).
    sec_alloc = (f'''\n\n    <div class="section"><div class="section-head"><div class="section-title">Traffic Allocation</div>
      <div class="section-note">{cfg["total_gb"]} GB total · by category</div></div>
      <div class="alloc"><div class="donut-wrap"><div class="donut" style="{conic(cfg["alloc"])}"></div>
        <div class="donut-center"><div class="big">{cfg["total_gb"]}<span style="font-size:13px"> GB</span></div>
          <div class="lbl">Total Volume</div></div></div>
        <table class="legend">{legend(cfg["alloc"])}</table></div></div>''' if cfg.get("alloc") else "")
    sec_profile = (f'''\n\n    <div class="section"><div class="section-head"><div class="section-title">Your Household Profile</div>
      <div class="section-note">your network's "macros" this month</div></div>
      <div class="profile"><div class="profile-badge"><div class="emoji">{cfg["emoji"]}</div>
        <div class="of">Profile of the Month</div></div>
        <div><div class="profile-name">{cfg["arch_name"]}</div><div class="profile-tag">{cfg["arch_tag"]}</div>
          <div class="profile-body">{cfg["arch_body"]}</div>
          <div class="chips">{chips(cfg["chips"])}</div>
          <div class="macro-line">{cfg["macro_line"]}</div></div></div></div>''' if cfg.get("arch_name") else "")
    sec_compare = (f'''\n\n    <div class="section"><div class="section-head"><div class="section-title">How You Compare</div>
      <div class="section-note">vs. similar households — {cfg["cohort"]}</div></div>
      {cmp(cfg["compare"])}</div>''' if cfg.get("compare") else "")
    if cfg.get("suggestions") or cfg.get("affirmation"):
        sug_note = "what we'd gently suggest this month" if "suggestions" in cfg else "our read this month"
        sec_sug = f'''\n\n    <div class="section"><div class="section-head"><div class="section-title">{cfg["sug_title"]}</div>
      <div class="section-note">{sug_note}</div></div>
      {suggestions_block(cfg)}</div>'''
    else:
        sec_sug = ""
    if cfg.get("ally_name"):
        qr_conn = qr(cfg["conn_url"])
        match_span = (f'\n          <span class="ally-match">Matched to: {cfg["arch_name"]}</span>' if cfg.get("arch_name") else "")
        sec_ally = f'''\n\n    <div class="section"><div class="section-head"><div class="section-title">Connect in the Alliance</div>
      <div class="section-note">optional · always opt-in</div></div>
      <div class="ally-intro">{cfg["ally_intro"]}</div>
      <div class="ally-card"><div class="avatar">{AVATAR}</div>
        <div><div class="ally-name">{cfg["ally_name"]}</div><div class="ally-role">{cfg["ally_role"]}</div>
          <div class="ally-loc">{cfg["ally_loc"]}</div><div class="ally-blurb">{cfg["ally_blurb"]}</div>{match_span}</div>
        <div class="ally-qr"><div class="frame">{qr_conn}</div><div class="cap">Scan to connect</div></div></div>
      <div class="ally-opt">Prefer to keep to yourself? The Alliance directory is opt-in — you'll only ever appear, or be matched, if you choose to.</div></div>'''
    else:
        sec_ally = ""
    html = f'''<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>A777ance — Network Activity Statement — {cfg["holder"]}</title>
<style>{STYLE}</style></head><body><div class="page">
  <div class="header"><div class="header-top">
      <div class="brandwrap"><div class="monogram"><span>A7</span></div>
        <div><div class="brand">A777ANCE</div><div class="brand-sub">Residential Network Services</div></div></div>
      <div class="doc-title"><div class="label">Network Activity Statement</div>
        <div class="period">{cfg["period"]}</div></div></div>
    <div class="acct-bar">
      <div class="acct-field"><div class="k">Account Holder</div><div class="v">{cfg["holder"]}</div></div>
      <div class="acct-field"><div class="k">Service Node</div><div class="v">HP t630</div></div>
      <div class="acct-field"><div class="k">Account No.</div><div class="v">{cfg["acct"]}</div></div>
      <div class="acct-field"><div class="k">Statement Date</div><div class="v">{cfg["stmt_date"]}</div></div></div></div>
  <div class="body">
    <div class="section"><div class="section-head"><div class="section-title">Account Summary</div>
      <div class="section-note">All figures for the statement period</div></div>
      <table class="summary"><thead><tr><th class="lead">Activity</th><th>This Period</th><th>Prior Period</th><th>Change</th><th>Year to Date</th></tr></thead>
      <tbody>{summary(cfg["summary"])}</tbody></table></div>

    <div class="section"><div class="section-head"><div class="section-title">Handled For You This Month</div>
      <div class="section-note">work done on your behalf — usually before you noticed</div></div>
      {handled_block(cfg["handled"])}
      <div class="handled-foot">{cfg["handled_foot"]}</div></div>{sec_alloc}{sec_profile}{sec_compare}{sec_sug}{sec_ally}

    <div class="section"><div class="section-head"><div class="section-title">See For Yourself</div>
      <div class="section-note">scan with your phone camera</div></div>
      <div class="live"><div class="tile"><div class="qr-frame">{qr_live}</div>
        <div class="tile-text"><div class="t">Live network status</div>
          <div class="d">Your real-time dashboard — uptime, response time, and service health, updated to the minute.</div>
          <div class="lock">&#128274; Private to your account</div></div></div>
        <div class="tile"><div class="qr-frame">{qr_stmt}</div>
        <div class="tile-text"><div class="t">This statement, online</div>
          <div class="d">The full digital version of this report, with month-by-month history you can scroll back through.</div>
          <div class="lock">&#128274; Password protected</div></div></div></div></div>

    <div class="section"><div class="section-head"><div class="section-title">Service Status</div>
      <div class="section-note">all systems verified through statement close</div></div>
      <div class="status-grid">
        <div class="status-item"><span class="status-dot"></span>DNS filtering active</div>
        <div class="status-item"><span class="status-dot"></span>DNS queries encrypted</div>
        <div class="status-item"><span class="status-dot"></span>DNSSEC validation on</div>
        <div class="status-item"><span class="status-dot"></span>VPN tunnel healthy</div>
        <div class="status-item"><span class="status-dot"></span>Firewall — no incidents</div>
        <div class="status-item"><span class="status-dot"></span>Traffic shaping active</div></div>
      <div class="privacy"><strong>A note on privacy.</strong> Individual websites and domains are never logged, stored, or reported. The categories, profile, and suggestions above are derived from aggregate volume by type only — counted in real time and discarded. Your network appliance runs entirely within your home; no browsing data is sent to A777ance or any third party.</div></div>
  </div>
  <div class="footer"><div class="fb">A777ANCE</div>
    <div>Managed on-premises · HP t630 appliance · Account {cfg["acct"]} &nbsp;·&nbsp; <a href="mailto:hello@a777ance.com">hello@a777ance.com</a> · (555) 000-0000</div></div>
</div></body></html>'''
    return html

# ───────── ARCHETYPE CONFIGS ─────────
ARCHES = {
"prime-time": dict(
  holder="Marcus Bennett", acct="A77-001", period="Statement Period: May 1 – May 31, 2026", stmt_date="June 1, 2026",
  live_url="https://status.a777ance.com/bennett", stmt_url="https://my.a777ance.com/A77-001", conn_url="https://alliance.a777ance.com/connect/m-reyes",
  handled=[("May 28","<b>Your living-room smart-TV</b> hung on a bad connection; it was cleared remotely at 11:40 pm.","Fixed in your home by <span class='nm'>Jose</span> · while you slept"),
           ("May 17","Cloudflare pushed a security update to its encrypted-DNS service. <b>Your appliance was updated the same day</b> — your private lookups kept flowing, a little faster than before.","Patched on your t630 by <span class='nm'>Jose</span>"),
           ("May 14","When Spectrum changed its routing, <b>your home's DNS was re-pointed in minutes</b> — the movie you were streaming never skipped.","Done on your line by <span class='nm'>Jose</span> · you never noticed")],
  handled_foot="Three things happened to your home this month. You felt none of them — that's the point.",
  summary=[("Queries resolved","52,441","48,902","+7.2%","251,330"),("Threats &amp; trackers blocked","9,847","8,123","+21.2%","47,221"),
           ("Protection rate","18.8%","16.6%","+2.2 pts","18.1%"),("Network availability","99.9%","99.7%","+0.2 pts","99.8%"),
           ("Peak loaded latency","12 ms","14 ms","&minus;2 ms","13 ms"),("Secure VPN sessions","14","9","+5","68")],
  total_gb="487",
  alloc=[("Streaming &amp; Video",41,"199.7 GB"),("Social &amp; Messaging",18,"87.7 GB"),("Cloud &amp; Backups",14,"68.2 GB"),
         ("Gaming &amp; Downloads",11,"53.6 GB"),("Smart Home / IoT",8,"39.0 GB"),("Shopping &amp; Web",5,"24.4 GB"),("Other",3,"14.4 GB")],
  emoji="&#127909;", arch_name="The Prime-Time Household", arch_tag="built around the evening screen",
  arch_body='Streaming led your month at <b>41%</b> of all traffic — your largest "macro" by a wide margin, with social a distant second. A classic evening-and-weekend rhythm: quiet daytime, lively nights. Gaming held steady at 11%, and your smart-home devices kept a low, constant hum in the background.',
  chips=["&#127902; <b>Screen-forward</b>","&#127769; Evening-peaked","&#127918; Steady gamer","&#127968; Always-on IoT"],
  macro_line='Think of these as your network\'s macros — like a diet, it\'s the <b>balance</b> that defines the month. Vs. April you shifted <b>+4 pts</b> toward gaming and eased off social.',
  cohort="suburban, 4–6 devices",
  compare=[dict(metric="Daily data volume", value="15.7 GB/day", delta=18, sentiment="neutral"),
           dict(metric="Streaming share", value="41%", delta=-15, sentiment="neutral"),
           dict(metric="Threats blocked / day", value="330/day", delta=57, sentiment="good", verdict="working harder for you"),
           dict(metric="Loaded latency", value="12 ms", delta=-57, sentiment="good", verdict="less than half the average")],
  sug_title="Have You Tried?",
  suggestions=[("&#128225;","Have you tried a second Wi-Fi point near the living room?","Your 4K streaming concentrates in one room on weeknights. A mesh node there would smooth playback when several screens are on at once.","Ask us &rarr; we keep a couple on hand"),
               ("&#127909;","Have you considered a streaming-priority tuning?","We can weight your traffic shaping so video never competes with a backup or a big download after 7pm — no buffering during movie night.","Ask us &rarr; a 5-minute change, no cost"),
               ("&#9203;","Have you set up per-device downtime?","Your profile shows steady late-evening gaming. Gentle per-device schedules are easy to add if you'd ever want them — and just as easy to remove.","Ask us &rarr; or self-serve in your portal")],
  ally_intro="Your A777ance membership is also a network of people. Based on your household profile, we think you'd hit it off with a member who knows exactly the setup you're running:",
  ally_name="Marco Reyes", ally_role="Home-theater &amp; media specialist", ally_loc="A777ance member · 2.3 miles away",
  ally_blurb="&ldquo;I'm really good at home-theater calibration, mesh Wi-Fi layouts, and dialing in Plex. Would love to connect.&rdquo;"),

"home-office": dict(
  holder="Sarah Chen", acct="A77-014", period="Statement Period: May 1 – May 31, 2026", stmt_date="June 1, 2026",
  live_url="https://status.a777ance.com/chen", stmt_url="https://my.a777ance.com/A77-014", conn_url="https://alliance.a777ance.com/connect/p-nair",
  handled=[("May 19","<b>The power adapter on your appliance</b> was reading flaky; it was swapped before it could fail and take your home office down.","Replaced in your home by <span class='nm'>Jose</span> · caught early"),
           ("May 11","A flaw turned up in a widely-used router service this month. <b>Your appliance was checked and hardened the same morning</b> — your home was never exposed.","Patched on your t630 by <span class='nm'>Jose</span> · before the news broke"),
           ("May 3","Ahead of your launch week, <b>your video calls were pinned to top priority on your line</b> — not one dropped call since.","Set up on your network by <span class='nm'>Jose</span> · at your request")],
  handled_foot="Your connection is your livelihood. We watch your home like it's ours.",
  summary=[("Queries resolved","61,930","58,400","+6.0%","298,110"),("Threats &amp; trackers blocked","11,204","10,880","+3.0%","53,470"),
           ("Protection rate","18.1%","18.6%","&minus;0.5 pts","18.4%"),("Network availability","99.99%","99.9%","+0.09 pts","99.96%"),
           ("Peak loaded latency","9 ms","11 ms","&minus;2 ms","10 ms"),("Secure VPN sessions","42","38","+4","214")],
  total_gb="612",
  alloc=[("Cloud &amp; Backups",27,"165.2 GB"),("Video Conferencing",22,"134.6 GB"),("Streaming &amp; Video",17,"104.0 GB"),
         ("Web &amp; SaaS",14,"85.7 GB"),("Social &amp; Messaging",11,"67.3 GB"),("Smart Home / IoT",6,"36.7 GB"),("Other",3,"18.5 GB")],
  emoji="&#128188;", arch_name="The Home Office", arch_tag="the network clocks in at 9 a.m.",
  arch_body='Your traffic peaks in the <b>workday</b>, not the evening — cloud sync and video calls together made up <b>49%</b> of the month. Uptime and upload stability matter more here than raw volume: a dropped second on a call costs more than a slow movie. Your network delivered, holding sub-10ms latency through the busiest hours.',
  chips=["&#9728; <b>Daytime-peaked</b>","&#128222; Call-heavy","&#9729; Cloud-reliant","&#11014; Upload-sensitive"],
  macro_line='These macros say "professional, not playful." Vs. April, video conferencing rose <b>+3 pts</b> as cloud backups held flat — a busier month of calls.',
  cohort="home-based workers, 3–5 devices",
  compare=[dict(metric="Workday data volume", value="24.1 GB/day", delta=31, sentiment="neutral"),
           dict(metric="Video-call share", value="22%", delta=69, sentiment="neutral"),
           dict(metric="Downtime this month", value="4 min", delta=-97, sentiment="good", verdict="vs ~2 hours for peers"),
           dict(metric="Upload responsiveness", value="9 ms", delta=-59, sentiment="good", verdict="calls stay crisp under load")],
  sug_title="Have You Tried?",
  suggestions=[("&#128267;","Have you considered a battery backup (UPS)?","Your livelihood runs over this connection. A small UPS keeps the appliance and router alive through brief outages — so a flickering power line never drops you mid-call.","Ask us &rarr; we can spec one to your gear"),
               ("&#128249;","Have you tried locking video calls to top priority?","We can pin conferencing above everything in your traffic shaping, so a cloud backup kicking off never steals from a live Zoom.","Ask us &rarr; a 5-minute change, no cost"),
               ("&#128268;","Have you thought about a wired link to your desk?","All-day calls are happiest off Wi-Fi. If your desk is near the appliance, a single cable removes wireless jitter entirely.","Ask us &rarr; we'll check the run")],
  ally_intro="Your A777ance membership is also a network of people. Based on your household profile, we think you'd hit it off with a member who lives the work-from-home setup too:",
  ally_name="Priya Nair", ally_role="Remote-work network specialist", ally_loc="A777ance member · 1.1 miles away",
  ally_blurb="&ldquo;I set up rock-solid home offices — UPS, wired backhaul, call-priority QoS. Happy to compare notes anytime.&rdquo;"),

"connected-family": dict(
  holder="The Okafor Family", acct="A77-008", period="Statement Period: May 1 – May 31, 2026", stmt_date="June 1, 2026",
  live_url="https://status.a777ance.com/okafor", stmt_url="https://my.a777ance.com/A77-008", conn_url="https://alliance.a777ance.com/connect/t-bishop",
  handled=[("May 12","<b>Two new devices</b> — a tablet and a console — were added to your secure network, no setup needed from you.","Onboarded in your home by <span class='nm'>Jose</span>"),
           ("May 9","<b>A smart-plug in your home</b> started flooding the network with chatter; it was isolated before anyone felt a lag.","Diagnosed in your home by <span class='nm'>Jose</span>"),
           ("May 8","Pi-hole shipped a filtering upgrade. <b>Your home's blocklists were updated the same day</b> — all 18 devices, in one sweep.","Patched on your t630 by <span class='nm'>Jose</span>")],
  handled_foot="A busy house, kept quiet. Eighteen devices, one calm network — and someone watching it.",
  summary=[("Queries resolved","88,260","90,140","&minus;2.1%","441,300"),("Threats &amp; trackers blocked","17,540","16,990","+3.2%","84,120"),
           ("Protection rate","19.9%","18.8%","+1.1 pts","19.1%"),("Network availability","99.95%","99.9%","+0.05 pts","99.93%"),
           ("Peak loaded latency","14 ms","16 ms","&minus;2 ms","15 ms"),("Secure VPN sessions","21","19","+2","96")],
  total_gb="934",
  alloc=[("Streaming &amp; Video",30,"280.2 GB"),("Smart Home / IoT",20,"186.8 GB"),("Gaming &amp; Downloads",16,"149.4 GB"),
         ("Social &amp; Messaging",14,"130.8 GB"),("Cloud &amp; Backups",10,"93.4 GB"),("Shopping &amp; Web",6,"56.0 GB"),("Other",4,"37.4 GB")],
  emoji="&#128106;", arch_name="The Connected Family", arch_tag="many devices, one happy network",
  arch_body='With <b>18 devices</b> under one roof — phones, tablets, consoles, and a small army of smart-home gadgets — yours is one of the busiest networks we manage. Smart Home / IoT alone was 20% of traffic. And yet it stayed fast and tidy all month: no congestion, no slowdowns, even on weekend evenings when everything is on at once.',
  chips=["&#128241; <b>Device-dense (18)</b>","&#128126; IoT-heavy","&#128118; Family hours","&#127777; Weekend-peaked"],
  macro_line='A wonderfully balanced spread — no single macro dominates, which is exactly what you want in a full house. Vs. April, volume eased <b>2%</b> even as device count grew.',
  cohort="large families, 12+ devices",
  compare=[dict(metric="Total monthly volume", value="934 GB", delta=99, sentiment="neutral"),
           dict(metric="Devices on the network", value="18", delta=100, sentiment="neutral"),
           dict(metric="Threats blocked / day", value="566/day", delta=77, sentiment="good", verdict="keeping a big house clean"),
           dict(metric="Responsiveness under load", value="14 ms", delta=-53, sentiment="good", verdict="steady despite the crowd")],
  sug_title="Our Read This Month",
  affirmation=dict(ic="&#9989;", t="Nothing to change this month.",
    d="We looked hard — with 18 devices and nearly a terabyte of traffic, your network stayed fast, filtered, and congestion-free all month. There's genuinely nothing we'd tune right now. Beautifully boring. We'll keep watching."),
  ally_intro="Your A777ance membership is also a network of people. Based on your household profile, we think you'd hit it off with a member who runs a busy, device-dense home like yours:",
  ally_name="Tom Bishop", ally_role="Smart-home &amp; multi-device specialist", ally_loc="A777ance member · 3.0 miles away",
  ally_blurb="&ldquo;I wrangle big smart-home setups — VLANs for IoT, guest networks, parental schedules. Always glad to swap tips.&rdquo;"),
}

# ───────── data-driven pipeline ─────────
# Each statement is rendered from a JSON file in ../data/clients/. The configs above
# are the built-in SAMPLE data: on first run they seed the JSON files, then rendering
# always reads from JSON. The on-box collector (tools/collect/) overwrites these JSON
# files with a home's real figures — that's the only handoff between data and template.
# Real customer data is PRIVATE: pass --data-dir/--out-dir to render a home's real
# statement into a private location, never into this public tree (see --help).
DATA = os.path.join(HERE, "..", "data", "clients")
OUT  = os.path.join(HERE, "..", "client")
NAMES = {"prime-time":"archetype-prime-time", "home-office":"archetype-home-office",
         "connected-family":"archetype-connected-family"}

def seed():
    os.makedirs(DATA, exist_ok=True)
    for key, cfg in ARCHES.items():
        p = os.path.join(DATA, NAMES[key] + ".json")
        if not os.path.exists(p):
            with open(p, "w") as f:
                json.dump(cfg, f, indent=2, ensure_ascii=False)
            print("seeded", os.path.relpath(p, HERE))

def render_all(data_dir=DATA, out_dir=OUT):
    os.makedirs(out_dir, exist_ok=True)
    for fn in sorted(os.listdir(data_dir)):
        if not fn.endswith(".json"):
            continue
        with open(os.path.join(data_dir, fn)) as f:
            cfg = json.load(f)
        html = build(cfg)
        with open(os.path.join(out_dir, fn[:-5] + ".html"), "w") as f:
            f.write(html)
        print("wrote", os.path.join(out_dir, fn[:-5] + ".html"), len(html), "bytes")

if __name__ == "__main__":
    import argparse
    ap = argparse.ArgumentParser(
        description="Render client statements (<home>.json -> <home>.html). Point "
                    "--data-dir/--out-dir at a PRIVATE location to render a real customer's "
                    "statement outside this public repo.")
    ap.add_argument("--data-dir", default=DATA,
                    help="dir of <home>.json files to render (default: ../data/clients)")
    ap.add_argument("--out-dir", default=OUT,
                    help="dir to write <home>.html into (default: ../client)")
    ap.add_argument("--no-seed", action="store_true",
                    help="don't seed the built-in sample homes (already implied for a custom --data-dir)")
    args = ap.parse_args()
    data_dir, out_dir = os.path.abspath(args.data_dir), os.path.abspath(args.out_dir)
    # Seed the three sample homes ONLY into the default public data dir — never a private
    # one, so a real customer's dir is never polluted with the built-in mockups.
    if data_dir == os.path.abspath(DATA) and not args.no_seed:
        seed()
    render_all(data_dir, out_dir)
