Skip to content

Architecture

Hoda Rezvanjoo edited this page May 28, 2026 · 1 revision

Layers

                        ┌──────────────────────────┐
   Streamlit UI ─────── │   app.py (wizard + UI)   │
                        └────────────┬─────────────┘
                                     │ reads/writes
                        ┌────────────▼─────────────┐
   State machine ────── │   core/wizard_state.py   │
                        └────────────┬─────────────┘
                                     │ consumed by
                ┌────────────────────┼────────────────────┐
                ▼                    ▼                    ▼
        ┌───────────────┐    ┌───────────────┐    ┌───────────────┐
        │  Module 1–4   │    │   Module 5    │    │  Module 6–7   │
        │  (inputs)     │──▶ │  (LP solver)  │──▶ │ (forecast +   │
        │               │    │               │    │ interpretation)│
        └───────────────┘    └───────────────┘    └───────────────┘
                │                    │                    │
                ▼                    ▼                    ▼
        ┌───────────────────────────────────────────────────────┐
        │   core/csv_import.py · core/kpi_config.py             │
        │       (KPI schema + last-mile parser)                 │
        └───────────────────────────────────────────────────────┘

Data flow

  1. Module 1 validates objectives + budget. Captures goal-value weights, test-and-learn carve-out, seasonality multipliers.
  2. Module 2 assigns priority 1 / 2 to each platform per objective. "Active platforms" are those with at least one priority slot.
  3. Module 3 collects per-platform historical budget + raw KPI values, either manually or via CSV.
  4. Module 4 derives cpu_per_goal[platform][goal][var] — cost per one unit of each count KPI. Flags outliers (CPU > 100× the per-goal median) and drops them.
  5. Module 5 builds the LP, runs three scenario solves, returns binding constraints + shadow prices + an objective decomposition, and on request runs Monte Carlo robustness analysis.
  6. Module 6 turns each scenario's allocation into per-KPI forecasts with data-driven confidence bands.
  7. Module 7 classifies the plan, scores confidence on a 40–100 scale, builds a diversification-capped Plan B, summarises risks.

Design principles

  • One direction of data flow. Modules depend only on numbered predecessors. No circular imports between M1–M7.
  • Single state object. WizardState is the source of truth. Modules read snapshots but never mutate state — each module is testable in isolation.
  • LP is one module, not many. Solver, diagnostics, Monte Carlo, and shrinkage live together in module5.py so the logic that explains the LP sits next to the LP itself.
  • Configuration is data, not branches. Module7Policy is a frozen dataclass of every interpretation threshold. Tighten one field to make the report more conservative; no code change required.
  • Every constraint is named. Each LpConstraint carries a meaningful label so binding-constraint diagnostics work automatically — you can ask "why is platform X capped?" and get a literal answer.

Clone this wiki locally