"""Generates three Word documents (in Croatian) describing Polleo Demand."""
from pathlib import Path
from docx import Document
from docx.shared import Pt, RGBColor, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH

OUT_DIR = Path(__file__).parent / "docs"
OUT_DIR.mkdir(exist_ok=True)


def new_doc(title, subtitle):
    d = Document()
    style = d.styles["Normal"]
    style.font.name = "Calibri"
    style.font.size = Pt(11)
    t = d.add_paragraph()
    t.alignment = WD_ALIGN_PARAGRAPH.CENTER
    run = t.add_run(title)
    run.bold = True
    run.font.size = Pt(22)
    run.font.color.rgb = RGBColor(0x1F, 0x3A, 0x5F)
    s = d.add_paragraph()
    s.alignment = WD_ALIGN_PARAGRAPH.CENTER
    r = s.add_run(subtitle)
    r.italic = True
    r.font.size = Pt(12)
    r.font.color.rgb = RGBColor(0x55, 0x55, 0x55)
    d.add_paragraph()
    return d


def h1(d, text):
    p = d.add_heading(text, level=1)
    for r in p.runs:
        r.font.color.rgb = RGBColor(0x1F, 0x3A, 0x5F)


def h2(d, text):
    p = d.add_heading(text, level=2)
    for r in p.runs:
        r.font.color.rgb = RGBColor(0x2E, 0x5E, 0x8C)


def h3(d, text):
    p = d.add_heading(text, level=3)


def p(d, text):
    d.add_paragraph(text)


def bullet(d, text):
    d.add_paragraph(text, style="List Bullet")


def numlist(d, text):
    d.add_paragraph(text, style="List Number")


def code(d, text):
    para = d.add_paragraph()
    run = para.add_run(text)
    run.font.name = "Consolas"
    run.font.size = Pt(9)
    run.font.color.rgb = RGBColor(0x33, 0x33, 0x33)


def table(d, headers, rows):
    t = d.add_table(rows=1 + len(rows), cols=len(headers))
    t.style = "Light Grid Accent 1"
    for i, h in enumerate(headers):
        cell = t.rows[0].cells[i]
        cell.text = h
        for r in cell.paragraphs[0].runs:
            r.bold = True
    for ri, row in enumerate(rows, start=1):
        for ci, val in enumerate(row):
            t.rows[ri].cells[ci].text = str(val)


# ============================================================
# DOC 1 — Kratki vodič kroz alat (workflow)
# ============================================================
def build_doc1():
    d = new_doc("Polleo Demand — Kratki vodič",
                "Tjedni i mjesečni workflow kroz alat (v3.7)")

    h1(d, "1. Što je Polleo Demand")
    p(d, "Polleo Demand je alat za planiranje potražnje i opskrbe za Polleo Sport. "
         "Podržava tjedni i mjesečni S&OP ciklus. Cilj je da ga koriste demand planneri, "
         "KAM-ovi i category manageri bez potrebe za poznavanjem Pythona ili tehnike.")
    p(d, "Alat se pokreće jednim klikom na Start_Polleo_Demand.bat. "
         "Otvara se web sučelje (Streamlit) u browseru.")

    h1(d, "2. Tjedni ritam — što radi Demand Planner")

    h2(d, "Ponedjeljak / utorak: Ažuriranje podataka")
    numlist(d, "Otvori Update sales — učitaj novi tjedni export prodaje iz ERP-a (csv/xlsx).")
    numlist(d, "Alat automatski čisti podatke, detektira promo spike-ove (wholesale) i "
               "ažurira sku_uplift.csv / cat_uplift.csv preko ERP promo kalendara.")
    numlist(d, "Ako imaš samo RIZ dokument (ili bilo koji add-on wholesale): "
               "uključi checkbox „Merge only wholesale (RIZ)“ — tada se novi redovi "
               "dodaju postojećima umjesto da ih prepišu.")
    numlist(d, "Kad update završi, status se ispisuje na istoj stranici.")

    h2(d, "Utorak: Pokretanje prognoze")
    numlist(d, "Otvori Run forecast.")
    numlist(d, "Alat pokreće channel-split forecast (retail + webshop + wholesale) "
               "na horizontu od 13 tjedana.")
    numlist(d, "Rezultat se sprema u forecast_for_supply.csv i koristi se na svim "
               "daljnjim stranicama (Demand planning, Revenue, Supply…).")

    h2(d, "Srijeda: Unos KAM/CM-ova")
    numlist(d, "Otvori KAM inputs → tab Slack cycle → Distribute templates.")
    numlist(d, "Alat generira Excel template za svakoga. KAM-ovi s buyer splitom "
               "dobivaju workbook s više sheetova, po jedan sheet po buyer-u:")
    bullet(d, "Selma (VP): 7 sheetova — Konzum, Spar, DM, Bipa, Kaufland, SBI, Ostalo")
    bullet(d, "Patrik (VP): 2 sheeta — SPAR, Mercator")
    bullet(d, "Ivan (MP): 1 sheet (nema buyer split)")
    bullet(d, "Patrik (MP): 1 sheet (nema buyer split)")
    numlist(d, "Template ima pre-popunjeni prethodni input (zeleno) i žuto označene "
               "nove tjedne. Pre-fill se od drugog ciklusa nadalje razdvaja PER BUYER "
               "— svaki sheet pokazuje samo inpute te trgovine/buyer-a.")
    numlist(d, "Slack distribuira file automatski na privatne poruke. KAM-ovi popune, "
               "vrate u isti thread.")
    numlist(d, "Collect + combine: alat čita sve sheetove iz svakog filea, buyer = "
               "ime sheeta. Sumira količine po SKU-u preko svih buyera i sprema u "
               "vp_input.csv / mp_input.csv. Detalj po (KAM, buyer) ostaje u "
               "vp_input_detail.csv za audit trail.")
    numlist(d, "Prošli tjedni (past CW kolone) se ČUVAJU u vp_input.csv pri svakom "
               "combinu — povijest unosa raste kroz vrijeme i vidi se u Demand Plan "
               "xlsx-u u RR-25..RR-0 prozoru.")

    h2(d, "Četvrtak: Planiranje potražnje")
    numlist(d, "Otvori Demand planning — tu uspoređuješ bazni forecast s KAM/CM inputom.")
    numlist(d, "Prilagodi overlay (override) gdje je potrebno — npr. najavljene akcije, "
               "novi listing, sezonske peak-ove.")
    numlist(d, "Consensus plan stranica prikazuje finalni, usklađeni plan koji ide u S&OP.")

    h2(d, "Petak: S&OP priprema")
    bullet(d, "Top 30 watchlist — artikli s najvećim odstupanjem ili rizikom.")
    bullet(d, "Forecast accuracy — FA, FA signed i BIAS po tier-u (Gold / Silver / Bronze), "
               "po kanalu. Weekly tab = prosjek per-week FA; Monthly tab = sum-then-divide. "
               "FA TOP 10 Impactors sekcija pokazuje 3 najveće kategorije tog tjedna i "
               "10 SKU-ova s najvećom apsolutnom greškom u svakoj.")
    bullet(d, "S&OP meeting stranica — executive pregled za sastanak.")

    h2(d, "Demand Plan xlsx — vizualni cues")
    bullet(d, "Crveno obojene RR ćelije = tjedan u povijesti kad je MP upisao on-top "
               "(promo je bila aktivna). Brz vizualni signal gdje su prošle akcije.")
    bullet(d, "VP on-top i MP on-top redovi pokazuju i POVIJESNE vrijednosti u "
               "RR-25..RR-0 prozoru — vidiš što je bilo obećano za prošle tjedne.")
    bullet(d, "PROMO kolona (desno od Review) — filter na njoj da odmah izvučeš "
               "sve SKU-ove s najavljenom akcijom u idućih 13 tjedana.")

    h1(d, "3. Mjesečni ritam")
    bullet(d, "Revenue — konsolidirani prihod (plan × cijena) po kategoriji i kanalu.")
    bullet(d, "SKU management — dodavanje novih SKU-ova, ažuriranje ABC/XYZ klasa, "
               "promjena statusa (aktivan / neaktivan).")
    bullet(d, "ERP promos — pregled i ručna korekcija ERP promo kalendara ako je potrebno.")

    h1(d, "4. Workflow Supply (Category Manager)")

    h2(d, "Jednokratno / povremeno")
    numlist(d, "Supply upload — WH stock (stock.csv), store stock (HR / AT / SLO — tri "
               "odvojena file-a), open POs (incoming_supply.csv), cijene (sku_costs.csv).")
    numlist(d, "Supply master — popuni lead time, MOQ, dobavljač po SKU-u.")

    h2(d, "Tjedno")
    numlist(d, "Stock projection — globalna slika stanja zaliha × forecast × incoming kroz "
               "idućih 13 tjedana. Uključuje i long-tail (clothing / fitness / gadgeti) "
               "kako bi CFO i WH imali cjelovitu sliku.")
    numlist(d, "Coverage — koliko tjedana pokriva trenutni stock po SKU-u (samo WH stock).")
    numlist(d, "Alerts — SKU-ovi ispod minimuma, overstock, stockout rizik.")
    numlist(d, "Order entry — predloženi order qty (MOQ, lead-time, target coverage).")
    numlist(d, "Inventory health — prekobrojne zalihe i nepokretne artikle.")

    h1(d, "5. Glavne stranice u sidebaru")
    table(d,
          ["Stranica", "Uloga"],
          [
              ["Dashboard", "Pregled ključnih KPI-jeva i statusa tjedna"],
              ["Update sales", "Učitavanje tjednog ERP prodajnog exporta"],
              ["Run forecast", "Pokretanje baznog 13-tjednog forecasta"],
              ["Demand planning", "Usporedba forecasta i KAM/CM inputa, override"],
              ["VP input / MP input", "Pregled agregiranih KAM / CM brojki"],
              ["KAM inputs", "Generiranje i kombiniranje KAM template-a (Slack)"],
              ["Revenue", "Revenue plan × cijena, po kategoriji i kanalu"],
              ["SKU management", "Master popis, tier-ovi, status"],
              ["Download", "Preuzimanje svih relevantnih CSV/Excel exporta"],
              ["Forecast accuracy", "MAPE po tier-u, po kanalu, kroz povijest"],
              ["Top 30 watchlist", "Prioritetni SKU-ovi za S&OP diskusiju"],
              ["Consensus plan", "Finalni usklađeni demand plan"],
              ["S&OP meeting", "Executive pogled za mjesečni sastanak"],
              ["ERP promos", "Pregled ERP promo kalendara"],
              ["Supply — Projection", "Globalna stock roll-forward slika"],
              ["Supply — Coverage", "Coverage po SKU-u (WH only)"],
              ["Supply — Alerts / Order entry", "Operativni supply workflow"],
              ["Supply — Upload", "Unos stock, stores, incoming, cijena"],
          ])

    h1(d, "6. Zlatna pravila")
    bullet(d, "Ako je nešto čudno u forecastu → prvo provjeri sales_clean.csv "
               "(da je update prošao do kraja).")
    bullet(d, "Ako forecast dramatično skače → vjerojatno je promo uplift. "
               "Provjeri erp_promo_calendar.csv i sku_uplift.csv.")
    bullet(d, "Ako Total Demand za promo tjedan izgleda premali → normalno: "
               "od v3.7 MP on-top ZAMJENJUJE retail baseline (ne zbraja se na njega), "
               "jer u promo tjednu sav retail volume ide kroz MP brojku. "
               "Wholesale baseline i webshop ostaju netaknuti.")
    bullet(d, "Ako je stock kriv → provjeri jesi li uploadao sva tri store file-a "
               "(HR / AT / SLO) i WH (stock.csv) odvojeno.")
    bullet(d, "Nikad ne brisati povijesne CSV-ove — svi skripta lanac se oslanja na njih.")
    bullet(d, "Backtest (run_backtest.py) se pokreće prije svake veće promjene u forecastu.")

    out = OUT_DIR / "01_Kratki_Vodic_Workflow.docx"
    d.save(out)
    return out


# ============================================================
# DOC 2 — Detaljna arhitektura podataka i pipeline
# ============================================================
def build_doc2():
    d = new_doc("Polleo Demand — Arhitektura podataka",
                "Detaljan opis data file-ova, pipeline-a i međuovisnosti")

    h1(d, "1. Pregled sustava")
    p(d, "Polleo Demand je modularni monolit pisan u Pythonu 3.12, pokretan "
         "lokalno preko Streamlit-a. Svi podaci žive kao CSV file-ovi u data/ "
         "direktoriju — nema baze podataka. Ovakav dizajn omogućuje lako "
         "backup-iranje, verzioniranje i inspekciju podataka izvan alata.")
    p(d, "Forecasting engine koristi Nixtla StatsForecast biblioteku i kombinira "
         "više modela: AutoARIMA, AutoCES, AutoTheta, CrostonOptimized, ADIDA, IMAPA, TSB. "
         "Holt / Holt-Winters je eksplicitno uklonjen zbog nestabilnosti.")

    h1(d, "2. Direktorijska struktura")
    code(d,
         "polleo-demand/\n"
         "  app.py                     # Streamlit web sučelje, sve stranice\n"
         "  update_sales.py            # Čišćenje tjednog ERP prodajnog exporta\n"
         "  build_erp_promo.py         # Gradi erp_promo_calendar.csv iz rabatne.xlsx\n"
         "  recalc_uplift_erp.py       # Računa uplift iz ERP kalendara (SKU + cat)\n"
         "  forecast_engine.py         # Glavni forecasting engine (StatsForecast + GBR)\n"
         "  run_backtest.py            # Backtest forecast vs actual\n"
         "  compute_xyz.py             # ABC / XYZ klasifikacija\n"
         "  constants.py               # Centralizirani pragovi i guardrails\n"
         "  week_utils.py              # ISO week encode/decode helpers\n"
         "  slack_agent.py             # Slack integracija za KAM template distribuciju\n"
         "  Start_Polleo_Demand.bat    # One-click launcher\n"
         "  data/                      # Svi CSV-ovi\n"
         "  .env                       # Slack token (gitignored)\n")

    h1(d, "3. Ključni data file-ovi")

    h2(d, "3.1 Prodaja i čišćenje")
    table(d,
          ["File", "Opis"],
          [
              ["sales_clean.csv",
               "Glavna prodajna povijest — retail / webshop / wholesale qty + promo flagovi"],
              ["erp_promo_calendar.csv",
               "Ground-truth promo kalendar iz ERP-a (SKU + tjedan + tip)"],
              ["sku_uplift.csv",
               "Per-SKU promo uplift faktori (retail + wholesale spike)"],
              ["cat_uplift.csv",
               "Category-level uplift fallback kad nema dovoljno SKU podataka"],
          ])

    h2(d, "3.2 Planning input")
    table(d,
          ["File", "Opis"],
          [
              ["vp_input.csv",
               "Wholesale (VP) inputi agregirani po SKU-u. CW kolone rastu kroz "
               "vrijeme — past CW kolone se ČUVAJU pri svakom combinu (history)."],
              ["vp_input_detail.csv",
               "Wholesale input po pojedinom KAM-u i buyer-u (audit trail). "
               "Kolone: sku, type (on-top / regular increase), kam, buyer, CW*. "
               "Buyer kolona uvedena u v3.7 — prazno za KAM-ove bez buyer splita."],
              ["mp_input.csv",
               "Retail (MP) inputi agregirani po SKU-u. Ista logika čuvanja history-ja."],
              ["mp_input_detail.csv", "Retail input po pojedinom CM-u (isti format kao VP detail)."],
          ])

    h2(d, "3.3 Master podaci")
    table(d,
          ["File", "Opis"],
          [
              ["sku_plan_list.csv",
               "SKU master — oznaka (ABC tier), XYZ klasa, CV, wholesale share"],
              ["sku_category_map.csv", "Mapiranje SKU → kategorija"],
              ["sku_subcat_map.csv", "Mapiranje SKU → subkategorija"],
              ["sku_prices.csv", "Cijene po SKU-u"],
          ])

    h2(d, "3.4 Supply")
    table(d,
          ["File", "Opis"],
          [
              ["stock.csv", "WH stock snapshot (samo warehouse, ne trgovine)"],
              ["stock_stores.csv", "Store stock — Hrvatska"],
              ["stock_stores_at.csv", "Store stock — Austrija"],
              ["stock_stores_slo.csv", "Store stock — Slovenija"],
              ["incoming_supply.csv", "Otvoreni PO-ovi (supplier + qty + ETA tjedan)"],
              ["supply_master.csv", "Supply master — dobavljač, lead time, MOQ"],
              ["sku_costs.csv", "Nabavne cijene (cost_price, RUC)"],
              ["forecast_for_supply.csv",
               "Forecast output konsumiran od strane Supply modula"],
          ])

    h2(d, "3.5 Ostalo")
    table(d,
          ["File", "Opis"],
          [
              ["backtest_fa.csv", "Backtest rezultati (forecast vs actual po SKU/tjedan/model)"],
              ["kam_cm_config.json", "KAM / CM konfiguracija"],
              ["config_toml.txt", "Konfiguracija aplikacije"],
          ])

    h1(d, "4. Pipeline — tjedni tok podataka")

    h2(d, "4.1 Dijagram ovisnosti")
    code(d,
         "ERP export (xlsx)  ──►  update_sales.py  ──►  sales_clean.csv\n"
         "                                                │\n"
         "rabatne.xlsx  ──►  build_erp_promo.py  ──►  erp_promo_calendar.csv\n"
         "                                                │\n"
         "                         recalc_uplift_erp.py  ◄─┤\n"
         "                                │\n"
         "                                ▼\n"
         "                    sku_uplift.csv + cat_uplift.csv\n"
         "                                │\n"
         "                                ▼\n"
         "sales_clean.csv  +  sku_uplift  +  KAM inputs  ──►  forecast_engine.py\n"
         "                                                          │\n"
         "                                                          ▼\n"
         "                                              forecast_for_supply.csv\n"
         "                                                          │\n"
         "                         stock.csv + store stock + incoming + master\n"
         "                                                          │\n"
         "                                                          ▼\n"
         "                                              Supply stranice (Coverage, Alerts…)\n")

    h2(d, "4.2 Redoslijed skripti (tipični tjedan)")
    numlist(d, "build_erp_promo.py — ako je stigao novi rabatne.xlsx; radi se po potrebi, "
               "ne svaki tjedan.")
    numlist(d, "update_sales.py — učitava novi prodajni export, čisti outliere, "
               "automatski na kraju zove recalc_uplift_erp.py.")
    numlist(d, "recalc_uplift_erp.py — regenerira sku_uplift.csv i cat_uplift.csv "
               "na bazi ERP kalendara.")
    numlist(d, "forecast_engine.py (preko Run forecast stranice) — generira 13-tjedni "
               "forecast po kanalu.")
    numlist(d, "run_backtest.py — ad-hoc, kad se nešto mijenja u modelu; "
               "generira backtest_fa.csv.")

    h1(d, "5. Konstante i guardrails (constants.py)")
    p(d, "Svi ključni pragovi su centralizirani u constants.py. Ne mijenjati bez backtest-a.")
    table(d,
          ["Konstanta", "Vrijednost", "Što radi"],
          [
              ["PROMO_UPLIFT_FALLBACK", "1.35",
               "Fallback uplift kad SKU nema dovoljno promo tjedana"],
              ["UPLIFT_MIN_PROMO_WEEKS", "2",
               "Minimalni broj promo tjedana za SKU-level uplift"],
              ["UPLIFT_MIN_NORMAL_WEEKS", "3",
               "Minimalni broj običnih tjedana za baseline"],
              ["FORECAST_CAP_MULT", "2.0",
               "Max forecast = 2× prosjek zadnjih 13 tjedana"],
              ["FORECAST_FLOOR_MULT", "0.5",
               "Min forecast = 50% medijan zadnjih 8 tjedana"],
              ["WS_CAP_MULT", "1.5", "Cap za wholesale forecast"],
              ["WS_SPIKE_MULT", "3.0", "Wholesale spike treshold (3× medijan)"],
              ["FORECAST_WEEKS", "13", "Horizont forecasta"],
              ["TRAILING_WEEKS", "26", "Rolling window za agregacije"],
          ])

    h1(d, "6. Pretpostavke o čišćenju prodaje")
    bullet(d, "Promo prag: 10% popusta (povišen s 5% jer je 5% generiralo previše lažnih pozitiva).")
    bullet(d, "Čišćenje se PRESKAČE ako je >40% tjedana flag-ano kao promo "
               "(vjerojatno je baseline već nizak), ILI ako su zadnja 4+ uzastopna tjedna promo.")
    bullet(d, "Wholesale spike = tjedan > 3× medijan → tretira se kao jednokratni event, "
               "ne kao baseline.")
    bullet(d, "Statistički promo flagovi (is_any_promo, retail_discount_pct, is_wholesale_spike) "
               "se i dalje pišu u sales_clean.csv jer ih koristi GBR model u forecast_engine.py "
               "kao features.")

    h1(d, "7. KAM / CM workflow — detalji")
    numlist(d, "Konfiguracija je u kam_cm_config.json. Svaka osoba ima:")
    bullet(d, "display_name, role (VP ili MP), type (KAM ili CM)")
    bullet(d, "categories — 'ALL' ili lista kategorija za koje je zadužen")
    bullet(d, "slack_user_id — za automatsko slanje preko Slack DM-a")
    bullet(d, "buyers (opcionalno) — lista trgovina / buyer-a za multi-sheet split")
    p(d, "Trenutni config:")
    table(d,
          ["Osoba", "Role", "Buyers (sheetovi)"],
          [
              ["Selma", "VP (KAM)", "Konzum, Spar, DM, Bipa, Kaufland, SBI, Ostalo"],
              ["Patrik VP", "VP (KAM)", "SPAR, Mercator"],
              ["Ivan", "MP (CM)", "— (single sheet)"],
              ["Patrik MP", "MP (CM)", "— (single sheet)"],
          ])
    numlist(d, "Alat generira Excel workbook za svaku osobu:")
    bullet(d, "Ako je buyers definiran → jedan sheet po buyer-u (ista struktura tablice)")
    bullet(d, "Ako nije → jedan sheet 'VP Input' / 'MP Input'")
    bullet(d, "Zelene ćelije = prethodni input; žute ćelije = novi tjedni za unos")
    bullet(d, "Pre-fill od drugog ciklusa razdvaja per-buyer (Konzum inputi samo na "
               "Konzum sheet, itd.). Prvi ciklus nakon uvođenja buyer splita = prazno.")
    numlist(d, "Slack automatski šalje preko slack_agent.py (privatni DM svakoj osobi).")
    numlist(d, "Collect+combine: alat čita SVE sheetove (preskače _meta), buyer = ime sheeta. "
               "Sumira po SKU-u za vp_input.csv; detail CSV zadržava (kam, buyer) atribuciju.")
    numlist(d, "Combine MERGA s postojećim vp_input.csv / mp_input.csv — past CW kolone "
               "koje nisu u novom submissionu ostaju netaknute. Tako history raste.")

    h1(d, "8. Supply modul — ulazni uploadi")
    p(d, "Supply upload stranica podržava 5 kategorija uploada, sa pametnim "
         "prepoznavanjem hrvatskih ERP kolona:")
    table(d,
          ["Sekcija", "File → kolone"],
          [
              ["1. WH stock", "stock.csv → sku (Šifra), on_hand (Zaliha)"],
              ["2. Store stock (HR/AT/SLO)",
               "stock_stores*.csv — tri odvojena file-a, jedan po zemlji"],
              ["3. Incoming supply", "incoming_supply.csv → sku, year, week, qty"],
              ["4. Supply master", "supply_master.csv → sku, supplier, lead_time, moq"],
              ["5. Cost prices", "sku_costs.csv → sku, cost_price (Nabavna), ruc (Marza)"],
          ])
    p(d, "Alias mapa (automatsko prepoznavanje) za cijene i stock:")
    code(d,
         'sku         ← sifra, ifra, code, artikl, artikal\n'
         'cost_price  ← nabavna, nabavna_cijena, costprice, cost, cijena\n'
         'ruc         ← marza, margin, ruc_pct\n'
         'moq         ← minimum_order_qty, min_order, min_qty\n'
         'on_hand     ← zaliha, stock, stanje, qty')

    h1(d, "9. Napomene za održavanje")
    bullet(d, "Python 3.12 je stabilna verzija — NE koristiti 3.14 (nema scipy wheela).")
    bullet(d, "pip install zahtijeva flag --break-system-packages.")
    bullet(d, "Sve st.cache_data pozive čisti helper clear_all_caches() u app.py.")
    bullet(d, "Konvencija: year*100 + week koristi se za ISO week encoding (week_utils.py).")

    out = OUT_DIR / "02_Arhitektura_Podataka.docx"
    d.save(out)
    return out


# ============================================================
# DOC 3 — Detaljna logika forecast + supply
# ============================================================
def build_doc3():
    d = new_doc("Polleo Demand — Logika forecasta i supply-a",
                "Detaljan opis modela, guardrails-a i biznis pravila")

    h1(d, "1. Arhitektura forecasta")

    h2(d, "1.1 Channel-split pristup")
    p(d, "Prognoza se radi odvojeno po kanalima, pa se zbraja na kraju. Razlog: "
         "retail, webshop i wholesale imaju drastično drugačije obrasce.")
    bullet(d, "Retail — stabilna, tjedna dinamika, promo-sensitive")
    bullet(d, "Webshop — slični obrasci kao retail ali s manjim volumenom i više sezonalnosti")
    bullet(d, "Wholesale — lumpy / intermittent, veliki B2B order-i, manje predvidivo; "
               "koristi spike detection umjesto promo detection-a")

    h2(d, "1.2 Model pool")
    p(d, "Koriste se Nixtla StatsForecast modeli, odabrani po SKU-u automatski "
         "(najbolji po in-sample MAPE-u):")
    table(d,
          ["Model", "Namjena"],
          [
              ["AutoARIMA", "Kontinuirani podaci s trendom i sezonalnošću"],
              ["AutoCES", "Complex Exponential Smoothing"],
              ["AutoTheta", "Theta method — dobar za srednje volatilne SKU-ove"],
              ["CrostonOptimized", "Intermittent demand (puno nula)"],
              ["ADIDA", "Aggregate-Disaggregate Intermittent Demand Approach"],
              ["IMAPA", "Varijanta ADIDA"],
              ["TSB", "Teunter-Syntetos-Babai (smooth intermittent)"],
              ["GBR (sklearn)", "Tree-based, koristi promo flagove kao features"],
          ])
    p(d, "Holt / Holt-Winters su eksplicitno UKLONJENI zbog potvrđene nestabilnosti — "
         "generirali su eksplozivne forecaste na Bronze SKU-ovima. Ne vraćati.")

    h2(d, "1.3 Uplift model (ERP-driven)")
    p(d, "Promo uplift se računa iz erp_promo_calendar.csv (recalc_uplift_erp.py):")
    numlist(d, "Za svaki SKU, uzmi tjedne kad je bio u promo po ERP-u (ne statistički).")
    numlist(d, "Prosjek prodaje u promo tjednima / prosjek u non-promo tjednima = uplift faktor.")
    numlist(d, "Ako SKU nema ≥ 2 promo tjedana ili ≥ 3 non-promo tjedna, fallback na "
               "category-level uplift iz cat_uplift.csv.")
    numlist(d, "Ako ni to ne uspije → fallback 1.35 (PROMO_UPLIFT_FALLBACK).")
    p(d, "Ovaj pristup je zamijenio stari statistički detektor (threshold 10% discount) "
         "jer je ERP kalendar autoritativan. Statistički flagovi su i dalje prisutni u "
         "sales_clean.csv kao GBR features, ali više ne driveaju uplift.")

    h2(d, "1.4 MP on-top kao zamjena retail baseline-a (v3.7)")
    p(d, "Ključno biznis pravilo: kad CM (MP) upiše on-top za neki tjedan, taj broj "
         "predstavlja CJELOKUPNI očekivani retail sell-through za tog tjedna, NE "
         "inkrementalno povrh baseline-a. U promo tjednu regular retail baseline se "
         "ne događa uz promo — promo ga zamjenjuje.")
    p(d, "Implementacija u forecast_engine.py:")
    bullet(d, "Split mode (wholesale-dominant SKU, ws_share ≥ 0.5): fc_retail[j] = 0 "
               "za svaki tjedan j gdje je mp_vals[j] > 0. Wholesale baseline i webshop "
               "ostaju. Fc = fc_retail + fc_wholesale.")
    bullet(d, "Total mode (retail-dominant SKU): fc[j] *= (1 − retail_phys_share) kad "
               "je mp_vals[j] > 0. retail_phys_share = r_hist.sum() / full_sum.")
    bullet(d, "VP on-top ostaje ISTINSKI INKREMENTALNO na wholesale baseline — velika "
               "narudžba B2B kupca se zbraja, ne zamjenjuje.")
    bullet(d, "Samo fizički retail se zeroira — webshop se tretira odvojeno i ostaje "
               "u baseline-u.")
    p(d, "Stara 'cannibalization' logika (parcijalno skaliranje baseline-a na temelju "
         "kombiniranog ontop/baseline omjera) je UKLONJENA u v3.7. Zamijenjena je "
         "eksplicitnom MP-retail zamjenom koja je konceptualno čišća.")

    h1(d, "2. Guardrails — zašto ih NE dirati")

    h2(d, "2.1 Forecast cap (2×)")
    p(d, "Max(forecast) = 2 × prosjek zadnjih 13 tjedana (FORECAST_CAP_MULT = 2.0).")
    p(d, "Razlog: bez cap-a, intermittent SKU-ovi s rijetkim ali velikim promo "
         "eventima znali su generirati forecast 5-10× veći od realnog. "
         "Wholesale ima zasebni cap (WS_CAP_MULT = 1.5) jer je dinamika drugačija.")

    h2(d, "2.2 Forecast floor (50%)")
    p(d, "Min(forecast) = 0.5 × medijan zadnjih 8 tjedana (FORECAST_FLOOR_MULT = 0.5).")
    p(d, "Razlog: pojedini modeli (posebno Croston) znaju dati forecast blizu nule kad "
         "ima nekoliko nul-tjedana zaredom, što vodi u stockout. Floor to sprječava.")

    h2(d, "2.3 Promo detection skip")
    p(d, "Cleaning se PRESKAČE ako:")
    bullet(d, ">40% tjedana je flag-ano kao promo — znači da je „baseline“ zapravo "
               "već nizak, čišćenje bi distorziralo model.")
    bullet(d, "4+ uzastopna zadnja tjedna su promo — znači trenutni run je realna nova baza.")

    h1(d, "3. ABC / XYZ klasifikacija")

    h2(d, "3.1 Oznaka (ABC tier)")
    bullet(d, "01 GOLD — top revenue SKU-ovi, ~20% artikala / ~70% prometa")
    bullet(d, "02 SILVER — srednji, ~30% artikala / ~20% prometa")
    bullet(d, "03 BRONZE — dugi rep, ~50% artikala / ~10% prometa")

    h2(d, "3.2 XYZ klasa (volatilnost)")
    bullet(d, "X — stabilna potražnja, CV < 0.5")
    bullet(d, "Y — srednje volatilna, 0.5 ≤ CV < 1.0")
    bullet(d, "Z — visoko volatilna / intermittent, CV ≥ 1.0")

    h2(d, "3.3 Kako tier utječe na workflow")
    bullet(d, "Gold SKU-ovi — ručni review u Demand planning, uvijek u Top 30 watchlistu.")
    bullet(d, "Silver — periodični pregled, fokus na promo aktivnosti.")
    bullet(d, "Bronze — strukturno drag accuracy-ja (niska volumena → visok CV). "
               "Ne tretirati loš MAPE ovdje kao failure modela.")
    bullet(d, "Long-tail (untiered) — ~2500 SKU-ova iz clothing/fitness/gadget. "
               "Ne forecasta se; uključen u Stock Projection preko trailing 13-week avg.")

    h1(d, "4. Supply — coverage i order logika")

    h2(d, "4.1 Stock roll-forward")
    code(d,
         "opening(t)   = stock(t-1)\n"
         "closing(t)   = opening(t) - demand(t) + incoming(t)\n"
         "stock(t)     = closing(t)")
    p(d, "Demand = forecast_for_supply.csv po SKU/tjedan. Incoming = incoming_supply.csv.")

    h2(d, "4.2 Coverage")
    p(d, "Coverage (tjedni) = on_hand / avg_weekly_forecast.")
    p(d, "Koristi samo WH stock (stock.csv), ne store stock — jer je CM koji gleda "
         "coverage odgovoran za replenishment u WH, ne store-ove.")

    h2(d, "4.3 Suggested order qty")
    code(d,
         "Q_order = max(MOQ, T_cov × D_avg − S_on_hand − S_incoming_in_LT)")
    p(d, "Gdje je:")
    bullet(d, "T_cov — target coverage (default 8 tjedana, konfigurabilno)")
    bullet(d, "D_avg — prosječna tjedna potražnja iz forecasta")
    bullet(d, "S_on_hand — trenutni WH stock")
    bullet(d, "S_incoming_in_LT — PO-ovi koji dolaze unutar lead-time prozora")
    bullet(d, "MOQ — minimum order quantity od dobavljača")

    h2(d, "4.4 Stock projection — long-tail pristup")
    p(d, "Global Stock Projection stranica pokriva CJELOVITU sliku za WH + CFO:")
    numlist(d, "Forecasted SKU-ovi (Gold/Silver/Bronze) — standardni forecast × uplift.")
    numlist(d, "Long-tail SKU-ovi (~2500 artikala) — sintetički forecast iz trailing "
               "13-week avg sales jer nemaju dovoljno podataka za StatsForecast.")
    numlist(d, "Stock se zbraja iz WH + svih 3 store lokacija (HR + AT + SLO).")
    numlist(d, "Closing value € = closing qty × cost_price iz sku_costs.csv.")

    h1(d, "5. Accuracy monitoring")

    h2(d, "5.1 Metrike — definicije (v3.7)")
    p(d, "Forecast Accuracy dashboard prati 3 metrike na svakoj razini (SKU, kategorija, "
         "tier, globalno):")
    table(d,
          ["Metrika", "Formula", "Interpretacija"],
          [
              ["FA (capped)",
               "max(0, 1 − |forecast − actual| / actual) × 100",
               "Magnituda greške, kapirano 0–100%. 100% = perfect."],
              ["FA signed",
               "forecast / actual × 100",
               "<100% = under-forecast (prodali više); >100% = over-forecast. "
               "Može prelaziti 100%, za razliku od FA."],
              ["BIAS",
               "(forecast − actual) / actual × 100",
               "Smjer greške. Negativno = pod-predvidjeli."],
          ])

    h2(d, "5.2 Weekly vs Monthly agregacija (direktor rule)")
    bullet(d, "Weekly tab: PROSJEK per-week metrika. Za svaki selektirani tjedan se "
               "prvo izračuna totals-based metrika, pa se uzme mean preko tjedana. "
               "FA dva tjedna (80%, 92%) → prikazano 86%.")
    bullet(d, "Monthly tab: SUM-THEN-DIVIDE. Svi actuali i forecastovi se prvo "
               "zbroje preko tjedana, pa se izračuna FA iz totala. Različito od "
               "weekly — reflektira volume-weighted sliku za S&OP.")
    bullet(d, "Kategorijska tablica ispod sažetka prati istu konvenciju (weekly = "
               "avg, monthly = totals).")

    h2(d, "5.3 FA TOP 10 Impactors sekcija")
    p(d, "Ispod sažetaka, za jedan odabrani tjedan, prikazuju se 3 tablice rame uz "
         "rame — po jedna za 3 najveće kategorije (po actual volumenu). Svaka tablica "
         "pokazuje 10 SKU-ova s NAJVEĆOM apsolutnom greškom (|forecast − actual|) — "
         "oni koji najviše 'ruše' FA te kategorije.")
    p(d, "Sortiranje po apsolutnoj grešci (ne po lowest FA%) jer mali SKU s actualom "
         "2 i FA 0% ima zanemariv impact; SKU s actualom 5000 i FA 40% je pravi krivac.")

    h2(d, "5.4 Očekivani rasponi (prema iskustvu)")
    bullet(d, "Gold: FA 75–90% (MAPE 10–25%) — target zona, najviše leverage-a.")
    bullet(d, "Silver: FA 60–75% — OK za planning svrhu.")
    bullet(d, "Bronze: FA 0–50% — strukturno; ne optimizirati model za ovo.")

    h2(d, "5.5 Kad istražiti")
    bullet(d, "Per-tier FA pomakne > 2pp između dva backtest run-a — pauzirati, istražiti.")
    bullet(d, "Pojedini Gold SKU ima FA < 50% — vjerojatno promo event koji nije u ERP "
               "kalendaru, ili MP on-top nije upisan.")
    bullet(d, "FA signed daleko od 100% sustavno (npr. 60% ili 140%) — bias problem, "
               "ne random volatility. Treba Planner Factor correction u Demand Plan-u.")

    h2(d, "5.6 Monthly caption — koji tjedni ulaze u mjesec")
    p(d, "Kad se u Monthly tab-u odabere mjesec, caption ispod selectbox-a eksplicitno "
         "lista tjedne koji su u njemu po ISO konvenciji (mapping po ponedjeljku tjedna). "
         "Flag-ira parcijalne tjedne (†) i tjedne koji prelaze u susjedni mjesec (*). "
         "Transparentno — planner točno zna što mjesec sadrži.")

    h1(d, "6. Sigurnosne i operativne napomene")
    bullet(d, "Slack token živi u .env file-u (gitignored). Nikad ga ne stavljati u .bat.")
    bullet(d, "Backup: cijeli data/ folder se može zipati jer je sve u CSV-ovima.")
    bullet(d, "Verzioniranje: git commit-aj nakon svake veće promjene u forecast_engine.py "
               "ili update_sales.py.")
    bullet(d, "Rollback: ako se nešto pokvari, pre_cleanup backupi postoje "
               "(backtest_fa.pre_cleanup.csv npr.).")

    h1(d, "7. Poznate slabosti (zna se, ne diraj bez razgovora)")
    bullet(d, "Bronze accuracy strukturno loša — neće se popraviti modelom, treba "
               "ABC strategiju (npr. target service level niži za Bronze).")
    bullet(d, "Long-tail forecast = trailing avg. Dovoljno za CFO pregled, "
               "nedovoljno za detaljno planiranje; to je OK jer se ti artikli "
               "operativno ne tretiraju kao forecastable.")
    bullet(d, "sku_costs.csv povijesno imao rupe za ~39% long-taila; u v3.7 uveden "
               "5-tier fallback za imputaciju cijena (sub_cat + gramaža → sub_cat + "
               "keywords → sub_cat per-gram → cat per-gram → cat flat median). "
               "Smanjuje rupe ali ne zamjenjuje stvarne podatke — treba popuniti ERP.")
    bullet(d, "Promo detection na wholesale i dalje statistički (spike 3× medijan) jer "
               "ERP kalendar nema wholesale promocije.")
    bullet(d, "Trenutni tjedan (npr. CW17 kad je CW_START = 18) u Revenue/RUC grafu "
               "se proxy-a s CW18 vrijednošću → pokazuje isti broj dva puta. "
               "Prava rješenja: snapshotati forecast per run u forecast_history.csv. "
               "Planirano, nije implementirano.")

    h1(d, "8. Planirane nadogradnje")
    bullet(d, "Promo Planner — nova stranica za demand plannera da unaprijed unese "
               "najavljene akcije prije nego dođu u ERP kalendar.")
    bullet(d, "Docker deployment — cijeli stack u container, deploy na IT-evu Docker infrastrukturu.")
    bullet(d, "Model selection po XYZ klasi — X → ARIMA, Y → CES, Z → Croston (trenutno "
               "automatski odabir preko in-sample MAPE, može se optimizirati).")

    out = OUT_DIR / "03_Logika_Forecast_i_Supply.docx"
    d.save(out)
    return out


if __name__ == "__main__":
    files = [build_doc1(), build_doc2(), build_doc3()]
    print("Generated:")
    for f in files:
        print(f"  {f}  ({f.stat().st_size:,} B)")
