Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"/> | |
| <title>Genesis AI — Detailed Interaction Flowchart</title> | |
| <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script> | |
| <style> | |
| *{box-sizing:border-box;margin:0;padding:0} | |
| body{font-family:'Segoe UI',Arial,sans-serif;background:#f0f4f1;color:#1a1a2e;padding:32px} | |
| h1{font-size:22px;font-weight:700;color:#1a6b2f;border-bottom:3px solid #1a6b2f;padding-bottom:10px;margin-bottom:4px} | |
| .doc-sub{font-size:12px;color:#888;margin-bottom:28px} | |
| /* ── section card ── */ | |
| .card{background:#fff;border-radius:14px;box-shadow:0 2px 16px rgba(0,0,0,.08);padding:26px 30px;margin-bottom:28px} | |
| .card-title{font-size:13px;font-weight:700;letter-spacing:.7px;text-transform:uppercase; | |
| color:#1a6b2f;margin-bottom:4px;display:flex;align-items:center;gap:8px} | |
| .card-desc{font-size:11.5px;color:#666;margin-bottom:18px;line-height:1.6} | |
| /* ── mermaid ── */ | |
| .mermaid{font-size:12px} | |
| .mermaid svg{width:100%;height:auto} | |
| /* ── badge ── */ | |
| .badge{display:inline-block;font-size:10px;font-weight:700;padding:2px 8px; | |
| border-radius:10px;margin-right:3px;white-space:nowrap} | |
| .fe {background:#e3f2fd;color:#1565c0} | |
| .be {background:#fff3e0;color:#c84b00} | |
| .py {background:#e8f5e9;color:#1b5e20} | |
| .json{background:#f3e5f5;color:#6a1b9a} | |
| .val{background:#fce4ec;color:#880e4f} | |
| /* ── table ── */ | |
| table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:10px} | |
| thead tr{background:#1a6b2f;color:#fff} | |
| thead th{padding:9px 12px;text-align:left;font-weight:600} | |
| tbody tr:nth-child(even){background:#f7faf7} | |
| tbody td{padding:8px 12px;border-bottom:1px solid #e5ede5;vertical-align:top;line-height:1.5} | |
| td:first-child{font-weight:700;color:#1a6b2f;white-space:nowrap} | |
| td code{font-family:Consolas,monospace;font-size:10.5px;background:#eef4ee;padding:2px 5px;border-radius:3px} | |
| /* ── two-col grid ── */ | |
| .two-col{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:14px} | |
| .col-head{font-size:11px;font-weight:700;letter-spacing:.5px;text-transform:uppercase; | |
| color:#888;margin-bottom:8px;padding-bottom:4px;border-bottom:2px solid #e5ede5} | |
| .entry{font-size:11.5px;margin-bottom:6px;display:flex;gap:8px;align-items:flex-start;line-height:1.5} | |
| .entry code{font-family:Consolas,monospace;font-size:10.5px;background:#eef4ee;padding:1px 5px;border-radius:3px} | |
| .arrow{color:#1a6b2f;font-weight:700;font-size:13px;flex-shrink:0;margin-top:1px} | |
| /* ── step pill ── */ | |
| .step-pill{display:inline-flex;align-items:center;gap:6px;background:#1a6b2f;color:#fff; | |
| border-radius:20px;padding:3px 12px;font-size:11px;font-weight:700;margin-bottom:10px} | |
| /* ── legend row ── */ | |
| .legend{display:flex;flex-wrap:wrap;gap:10px;margin-top:14px;font-size:11px} | |
| .leg{display:flex;align-items:center;gap:5px;color:#555} | |
| .leg-dot{width:12px;height:12px;border-radius:2px;flex-shrink:0} | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Genesis AI — Detailed Interaction Flowchart</h1> | |
| <p class="doc-sub">Exact functions, APIs, validations and data keys for every user action</p> | |
| <!-- ██████████████████████████████████████████████████████ --> | |
| <!-- PART A — FILE UPLOAD FLOW --> | |
| <!-- ██████████████████████████████████████████████████████ --> | |
| <div class="card"> | |
| <div class="card-title">PART A — File Upload: Complete Flow</div> | |
| <p class="card-desc">From the moment the user clicks "Upload" to the moment JSON data is in React state — every function, API call, validation and Python function.</p> | |
| <div class="mermaid"> | |
| flowchart TD | |
| U([" 👤 User clicks\n Upload button "]) --> H1 | |
| subgraph FE1 [" FRONTEND — Header.jsx "] | |
| H1["Header()\nstate: uploadOpen=false → true\nonClick: setUploadOpen(true)"] | |
| H2["UploadModal opens\nPOST /api/upload/reset\n→ clears previous job state"] | |
| H1 --> H2 | |
| end | |
| subgraph FE2 [" FRONTEND — UploadModal.jsx "] | |
| V1{"User picks file\n(drag-drop or browse)"} | |
| V2["onDrop(e)\ne.dataTransfer.files[0]"] | |
| V3["handleFile(file)\n── VALIDATION ──\n① file.name.endsWith('.xlsx'/'.xlsm'/'.xls')\n② if invalid → setError('Please upload .xlsx')\n return early"] | |
| V4["phase = 'uploading'\nsetStatus(null), setError(null)"] | |
| V5["POST /api/upload\nfetch('/api/upload', {\n method: 'POST',\n body: FormData({ file })\n})"] | |
| V6{"res.ok?"} | |
| V7["setError(detail from JSON)\nphase = null"] | |
| V8["phase = 'running'\nstartPolling()"] | |
| V1 -- drag --> V2 | |
| V1 -- browse --> V3 | |
| V2 --> V3 | |
| V3 -- valid --> V4 --> V5 | |
| V3 -- invalid --> V7 | |
| V5 --> V6 | |
| V6 -- No --> V7 | |
| V6 -- Yes --> V8 | |
| end | |
| subgraph FE3 [" FRONTEND — UploadModal.jsx (polling) "] | |
| P1["startPolling()\nsetInterval every 1500 ms"] | |
| P2["GET /api/upload/status\n→ { status, message, elapsed_s }"] | |
| P3["GET /api/upload/logs\n→ { logs }"] | |
| P4["setStatus(message)\nsetLogs(logs)"] | |
| P5{"status == 'done'\nor 'error'?"} | |
| P6["clearInterval\nphase = 'done' or 'error'"] | |
| P7["handleReload()\nonSuccess(filename)\n→ Header.handleSuccess()"] | |
| P1 --> P2 --> P3 --> P4 --> P5 | |
| P5 -- No, keep polling --> P2 | |
| P5 -- Yes --> P6 --> P7 | |
| V8 --> P1 | |
| end | |
| subgraph FE4 [" FRONTEND — Header.jsx (on success) "] | |
| S1["handleSuccess(filename)\nsetActiveFile(filename)\nlocalStorage.setItem('genesis_active_file', filename)\nreload() ← from DataContext"] | |
| S2["Header badge updates:\n'Simulating / filename.xlsx'"] | |
| S1 --> S2 | |
| end | |
| subgraph FE5 [" FRONTEND — DataContext.jsx (reload) "] | |
| DC1["loadAll()\nsetLoading(true), setProgress(0)"] | |
| DC2["for each key in KEYS[16]\n fetchKey(key)"] | |
| DC3["fetchKey(key)\n① fetch('/api/data/{key}')\n if res.ok → return res.json()\n② catch → fetch('/data/{key}.json')\n if res.ok → return res.json()\n③ else → null"] | |
| DC4["setProgress(i+1 / 16 × 100)"] | |
| DC5["setData(results)\nsetLoading(false)"] | |
| DC1 --> DC2 --> DC3 --> DC4 --> DC5 | |
| end | |
| subgraph BE [" BACKEND — upload_server.py "] | |
| B1["POST /api/upload\n① validate extension\n② save to _uploads/file.xlsx\n③ _job['filename'] = filename\n④ Thread(_run_pipeline).start()"] | |
| B2["GET /api/upload/status\nreturns _job dict snapshot"] | |
| B3["GET /api/data/{key}\n① check DATA_KEY_MAP\n② read output/{key}.json\n③ return JSONResponse"] | |
| B4["_run_pipeline(xlsx_path)\nStreams subprocess stdout live\nUpdates _job['logs'] per line\nCopies output/*.json → public/data/\n_job['status'] = 'done'"] | |
| end | |
| subgraph PY [" PYTHON PIPELINE — pipeline/run.py "] | |
| PY1["load_source_data(xlsx)\npipeline/loader.py\nReads sheet: 1_Source_Data\nPrice = Value / Volume\nDrops null rows"] | |
| PY2["read_brand_config(xlsx)\npipeline/run.py\nReads sheet: 2_Brand_Config\nFocal brand, competitors"] | |
| PY3["build_wide_pivot()\npipeline/modelling.py\nOne table per CH × Region\nFocal + competitor columns"] | |
| PY4["run_diagnostics()\npipeline/diagnostics.py\nCorrelation, CV, RPI flags\nWrites *_Diagnostics.xlsx"] | |
| PY5["run_elasticity_models()\npipeline/modelling.py\nOLS all spec combos\nSelects best Adj-R²"] | |
| PY6["assign_proxies()\npipeline/proxies.py\nBorrow elasticity for\nwrong-sign grains"] | |
| PY7["compute_freq_anchors()\npipeline/proxies.py\nDominant pack per\nCH × Region"] | |
| PY8["build_grain_metrics()\npipeline/exporters/stats.py\nVol sal, val share,\nmarket share per grain"] | |
| EXPORT["5 exporter functions\n(see Part B diagram)"] | |
| PY1-->PY2-->PY3-->PY4-->PY5-->PY6-->PY7-->PY8-->EXPORT | |
| end | |
| H2 --> FE2 | |
| V5 --> B1 | |
| B1 --> B4 | |
| B4 --> PY1 | |
| P2 --> B2 | |
| DC3 --> B3 | |
| P7 --> FE4 | |
| S1 --> DC1 | |
| style FE1 fill:#e3f2fd,stroke:#1565c0,color:#0d1b4b | |
| style FE2 fill:#e3f2fd,stroke:#1565c0,color:#0d1b4b | |
| style FE3 fill:#e3f2fd,stroke:#1565c0,color:#0d1b4b | |
| style FE4 fill:#e3f2fd,stroke:#1565c0,color:#0d1b4b | |
| style FE5 fill:#e3f2fd,stroke:#1565c0,color:#0d1b4b | |
| style BE fill:#fff3e0,stroke:#c84b00,color:#3e2000 | |
| style PY fill:#e8f5e9,stroke:#2e7d32,color:#1b2e1b | |
| </div> | |
| <div class="legend"> | |
| <div class="leg"><div class="leg-dot" style="background:#bbdefb;border:1.5px solid #1565c0"></div>React Frontend</div> | |
| <div class="leg"><div class="leg-dot" style="background:#ffe0b2;border:1.5px solid #c84b00"></div>FastAPI Backend</div> | |
| <div class="leg"><div class="leg-dot" style="background:#c8e6c9;border:1.5px solid #2e7d32"></div>Python Pipeline</div> | |
| </div> | |
| </div> | |
| <!-- ── UPLOAD VALIDATION TABLE ── --> | |
| <div class="card"> | |
| <div class="card-title">Part A — Frontend Validations on Upload</div> | |
| <table> | |
| <thead><tr><th>Check</th><th>Where</th><th>Function</th><th>Condition</th><th>On Fail</th></tr></thead> | |
| <tbody> | |
| <tr> | |
| <td>File type</td> | |
| <td><span class="badge fe">FE</span></td> | |
| <td><code>handleFile(file)</code> — UploadModal.jsx</td> | |
| <td>filename ends with <code>.xlsx</code>, <code>.xlsm</code>, or <code>.xls</code></td> | |
| <td>setError("Please upload an .xlsx / .xlsm / .xls file"); returns early, no API call</td> | |
| </tr> | |
| <tr> | |
| <td>Server extension check</td> | |
| <td><span class="badge be">BE</span></td> | |
| <td><code>upload_excel()</code> — upload_server.py</td> | |
| <td>Same extension check server-side (double guard)</td> | |
| <td>HTTP 400 — detail shown in modal error state</td> | |
| </tr> | |
| <tr> | |
| <td>Concurrent run guard</td> | |
| <td><span class="badge be">BE</span></td> | |
| <td><code>upload_excel()</code> — upload_server.py</td> | |
| <td><code>_job['status'] != 'running'</code></td> | |
| <td>HTTP 409 — "A pipeline run is already in progress"</td> | |
| </tr> | |
| <tr> | |
| <td>HTTP response check</td> | |
| <td><span class="badge fe">FE</span></td> | |
| <td><code>handleFile()</code> — UploadModal.jsx</td> | |
| <td><code>res.ok === true</code> after POST</td> | |
| <td>Reads <code>res.json().detail</code> and shows as error; stops polling</td> | |
| </tr> | |
| <tr> | |
| <td>Pipeline error check</td> | |
| <td><span class="badge fe">FE</span></td> | |
| <td><code>startPolling()</code> — UploadModal.jsx</td> | |
| <td><code>status != 'error'</code></td> | |
| <td>clearInterval; phase = 'error'; shows logs panel with failure detail</td> | |
| </tr> | |
| <tr> | |
| <td>Data key unknown</td> | |
| <td><span class="badge be">BE</span></td> | |
| <td><code>get_data(key)</code> — upload_server.py</td> | |
| <td>key in DATA_KEY_MAP</td> | |
| <td>HTTP 404 — DataContext catches, key value = null; tab shows empty state</td> | |
| </tr> | |
| <tr> | |
| <td>JSON file missing</td> | |
| <td><span class="badge be">BE</span></td> | |
| <td><code>get_data(key)</code> — upload_server.py</td> | |
| <td><code>output/{key}.json</code> exists on disk</td> | |
| <td>HTTP 404 — DataContext falls back to <code>/data/{key}.json</code> static file</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- ── PYTHON PIPELINE TABLE ── --> | |
| <div class="card"> | |
| <div class="card-title">Part A — Python Functions Invoked (Pipeline)</div> | |
| <table> | |
| <thead><tr><th>#</th><th>File</th><th>Function</th><th>Input</th><th>Output</th><th>What it does</th></tr></thead> | |
| <tbody> | |
| <tr><td>1</td><td><code>pipeline/loader.py</code></td><td><code>load_source_data(path)</code></td><td>xlsx path</td><td>DataFrame</td><td>Reads <em>1_Source_Data</em> sheet; computes Price = Value/Volume; parses dates; drops nulls in Brand/Month/Value/Volume</td></tr> | |
| <tr><td>2</td><td><code>pipeline/run.py</code></td><td><code>read_brand_config(path)</code></td><td>xlsx path</td><td>config dict</td><td>Reads <em>2_Brand_Config</em> sheet; extracts focal brand name + competitor list</td></tr> | |
| <tr><td>3</td><td><code>pipeline/modelling.py</code></td><td><code>build_wide_pivot(df, focal, comps, ch, rg, pack_order)</code></td><td>DataFrame</td><td>wide DataFrame</td><td>One wide monthly table per CH×Region grain. Adds Vol_F, Price_F, Dist_F, Price_<comp>, Cat_Vol, Vol_Up, Vol_Down columns</td></tr> | |
| <tr><td>4</td><td><code>pipeline/diagnostics.py</code></td><td><code>run_diagnostics(all_data, competitors)</code></td><td>wide DataFrame</td><td>Excel file</td><td>5-sheet diagnostics: descriptive stats, correlation/collinearity, RPI trends, cannibalization, summary flags</td></tr> | |
| <tr><td>4a</td><td><code>pipeline/diagnostics.py</code></td><td><code>safe_corr(a, b)</code></td><td>two Series</td><td>(r, p)</td><td>Pearson r with ≥5 non-null pairs guard</td></tr> | |
| <tr><td>4b</td><td><code>pipeline/diagnostics.py</code></td><td><code>safe_trend(s)</code></td><td>Series</td><td>(slope, r², p)</td><td>OLS trend over time index</td></tr> | |
| <tr><td>5</td><td><code>pipeline/modelling.py</code></td><td><code>run_elasticity_models(all_data, comps, pack_order)</code></td><td>wide DataFrame</td><td>(all_results_df, best_df)</td><td>Exhaustive OLS spec search per grain; selects best Adj-R² with own-price coef < 0; clamps elasticity to [−6, 0]</td></tr> | |
| <tr><td>5a</td><td><code>pipeline/modelling.py</code></td><td><code>ols(y, X)</code></td><td>arrays</td><td>coef dict</td><td>numpy.linalg.lstsq OLS; returns betas, t-stats, p-values, R², Adj-R²</td></tr> | |
| <tr><td>6</td><td><code>pipeline/proxies.py</code></td><td><code>assign_proxies(best_df, pack_order)</code></td><td>best_df</td><td>final_df</td><td>Wrong-sign grains: interpolate adjacent packs → borrow same CH/RG → borrow any region. Adds Final_OwnE, IsProxy, ProxyMethod</td></tr> | |
| <tr><td>7</td><td><code>pipeline/proxies.py</code></td><td><code>compute_freq_anchors(df, focal, ppa_pml)</code></td><td>DataFrame</td><td>anchors dict</td><td>Dominant pack per CH×Region by vol %; tie-break by lowest price/ml if PPA supplied</td></tr> | |
| <tr><td>8</td><td><code>pipeline/exporters/stats.py</code></td><td><code>build_grain_metrics(df, focal, comps)</code></td><td>DataFrame</td><td>metrics dict</td><td>Vol sal, val share, MS yr25/yr24, price/ml, base vol/val — enrichment for model export</td></tr> | |
| <tr><td>9</td><td><code>pipeline/exporters/stats.py</code></td><td><code>build_model_export(final_df, grain_metrics)</code></td><td>final_df</td><td>→ <code>models.json</code></td><td>Flat list of grain elasticities + market metrics</td></tr> | |
| <tr><td>10</td><td><code>pipeline/exporters/stats.py</code></td><td><code>build_stats_json(df, focal, final_df, anchors, growth_decomp)</code></td><td>DataFrame</td><td>→ <code>stats.json</code></td><td>Brand-level KPIs: vol growth, avg elasticity, market share, anchor count</td></tr> | |
| <tr><td>11</td><td><code>pipeline/exporters/market.py</code></td><td><code>build_trend_json(df, focal, comps)</code></td><td>DataFrame</td><td>→ <code>trend.json</code></td><td>Monthly time-series rows for all brands</td></tr> | |
| <tr><td>12</td><td><code>pipeline/exporters/market.py</code></td><td><code>build_ms_json(df, focal)</code></td><td>DataFrame</td><td>→ <code>ms.json</code></td><td>Focal brand market share yr25 vs yr24 per grain</td></tr> | |
| <tr><td>13</td><td><code>pipeline/exporters/market.py</code></td><td><code>build_vol_salience_json(df, focal)</code></td><td>DataFrame</td><td>→ <code>vol_salience.json</code></td><td>Pack volume % of focal brand total</td></tr> | |
| <tr><td>14</td><td><code>pipeline/exporters/market.py</code></td><td><code>build_val_share_json(df, focal)</code></td><td>DataFrame</td><td>→ <code>val_share.json</code></td><td>Pack value % of focal brand total</td></tr> | |
| <tr><td>15</td><td><code>pipeline/exporters/market.py</code></td><td><code>build_comp_ms_json(df, focal, comps)</code></td><td>DataFrame</td><td>→ <code>comp_ms.json</code></td><td>All brands' market share yr25 vs yr24</td></tr> | |
| <tr><td>16</td><td><code>pipeline/exporters/market.py</code></td><td><code>build_vtm_json(df, focal, comps)</code></td><td>DataFrame</td><td>→ <code>vtm.json</code></td><td>Category + per-brand volumes with MS and vol change</td></tr> | |
| <tr><td>17</td><td><code>pipeline/exporters/ppa.py</code></td><td><code>build_ppa_json(df, focal, comps, channel, ...)</code></td><td>DataFrame + xlsx</td><td>→ <code>ppa_mt.json</code> / <code>ppa_tt.json</code></td><td>Per-brand PPA matrix (SKU, MRP, price/ml, RPI, gross contribution)</td></tr> | |
| <tr><td>18</td><td><code>pipeline/exporters/analytics.py</code></td><td><code>build_interaction_json(df, focal, comps)</code></td><td>DataFrame</td><td>→ <code>interaction.json</code></td><td>Cross-brand Pearson r of monthly volumes per CH×Region</td></tr> | |
| <tr><td>19</td><td><code>pipeline/exporters/analytics.py</code></td><td><code>build_growth_decomp_json(df, focal)</code></td><td>DataFrame</td><td>→ <code>growth_decomp.json</code></td><td>%-point contribution per grain to brand volume growth yr24→yr25</td></tr> | |
| <tr><td>20</td><td><code>pipeline/exporters/analytics.py</code></td><td><code>build_pgi_json(df, focal, pack_order)</code></td><td>DataFrame</td><td>→ <code>pgi.json</code></td><td>Price Gradient Index per channel yr24 vs yr25</td></tr> | |
| <tr><td>21</td><td><code>pipeline/exporters/recommendations.py</code></td><td><code>build_recs_json(df, focal, comps, final_df, pack_order, xlsx)</code></td><td>DataFrame</td><td>→ <code>recs_full.json</code></td><td>±5% pricing recommendation cards with score, feasibility, impact (vol, val, GC)</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- ██████████████████████████████████████████████████████ --> | |
| <!-- PART B — TAB BUTTON CLICK FLOW --> | |
| <!-- ██████████████████████████████████████████████████████ --> | |
| <div class="card"> | |
| <div class="card-title">PART B — Tab Button Click: Complete Flow</div> | |
| <p class="card-desc">What happens in the frontend when a tab is clicked. Data is already loaded in DataContext at this point — no new backend calls are made on tab switch.</p> | |
| <div class="mermaid"> | |
| flowchart TD | |
| T([" 👤 User clicks\n a Tab button "]) --> TN | |
| subgraph FE_NAV [" FRONTEND — TabNav.jsx "] | |
| TN["TabNav()\nonClick: onTabChange(t.id)\nprop callback -> App.jsx"] | |
| TN2["App.jsx\nsetActiveTab(t.id)\nstate: activeTab = 'results' | 'trends' | 'ppa'\n | 'recs' | 'sim' | 'interaction'\n | 'growth' | 'gi' | 'methodology'"] | |
| TN --> TN2 | |
| end | |
| subgraph FE_APP [" FRONTEND — App.jsx "] | |
| APP["tabComponents[activeTab]\nMounted tab component renders\n(previously loaded data from DataContext)\nNo new API calls triggered"] | |
| TN2 --> APP | |
| end | |
| subgraph CTX [" FRONTEND — DataContext (already loaded) "] | |
| CTX1["useData() hook\nAll 16 keys already in memory:\nmodels, recs, ms, vol_salience,\nval_share, freq_anchors, ppa_mt,\nppa_tt, comp_ms, vtm, interaction,\ngrowth_decomp, pgi, stats,\ntrend, recs_full"] | |
| APP --> CTX1 | |
| end | |
| subgraph TABS [" TAB COMPONENTS "] | |
| T1["ElasticityResults.jsx\nTab: Results\nuseData(): models, stats, freq_anchors\nLocal state: channel filter, region filter,\npack filter, sort column"] | |
| T2["Trends.jsx\nTab: Trends\nuseData(): trend\nLocal state: brand filter, channel,\nmetric (volume/value/price/dist)"] | |
| T3["PPA.jsx\nTab: PPA\nuseData(): ppa_mt, ppa_tt, freq_anchors, trend\nLocal state: channel (MT/TT), pack filter"] | |
| T4["Recommendations.jsx\nTab: Recommendations\nuseData(): recs, freq_anchors\nLocal state: type filter (inc/dec/both),\nchannel filter"] | |
| T5["Simulation.jsx\nTab: Simulation\nuseData(): models\nLocal state: selected grain,\nprice delta input, computed impact"] | |
| T6["BrandInteraction.jsx\nTab: Interaction\nuseData(): vtm, interaction, freq_anchors\nLocal state: channel, region filters"] | |
| T7["GrowthDecomposition.jsx\nTab: Growth\nuseData(): growth_decomp, stats\nLocal state: sort, filter state"] | |
| T8["PriceGradient.jsx\nTab: Price Gradient\nuseData(): pgi\nLocal state: channel filter"] | |
| T9["Methodology.jsx\nTab: Methodology\nNo DataContext dependency\nStatic explanatory content"] | |
| CTX1 --> T1 & T2 & T3 & T4 & T5 & T6 & T7 & T8 & T9 | |
| end | |
| style FE_NAV fill:#e3f2fd,stroke:#1565c0,color:#0d1b4b | |
| style FE_APP fill:#e3f2fd,stroke:#1565c0,color:#0d1b4b | |
| style CTX fill:#e8f5e9,stroke:#2e7d32,color:#1b2e1b | |
| style TABS fill:#f3e5f5,stroke:#7b1fa2,color:#2e0040 | |
| </div> | |
| <div class="legend"> | |
| <div class="leg"><div class="leg-dot" style="background:#bbdefb;border:1.5px solid #1565c0"></div>React Frontend</div> | |
| <div class="leg"><div class="leg-dot" style="background:#c8e6c9;border:1.5px solid #2e7d32"></div>DataContext (in-memory)</div> | |
| <div class="leg"><div class="leg-dot" style="background:#e1bee7;border:1.5px solid #7b1fa2"></div>Tab Components</div> | |
| </div> | |
| </div> | |
| <!-- ── HOW DATA REACHES EACH TAB ── --> | |
| <div class="card"> | |
| <div class="card-title">Part B — How Each Tab Gets Its Data</div> | |
| <p class="card-desc">Data flows from pipeline → JSON files → FastAPI → DataContext → tab component. Once loaded, tab switches are instant (no API calls).</p> | |
| <table> | |
| <thead> | |
| <tr> | |
| <th>Tab (id)</th> | |
| <th>Component</th> | |
| <th>Data keys used</th> | |
| <th>Python function that created each JSON</th> | |
| <th>What is displayed</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td>results</td> | |
| <td><code>ElasticityResults.jsx</code></td> | |
| <td><code>models</code>, <code>stats</code>, <code>freq_anchors</code></td> | |
| <td> | |
| <code>build_model_export()</code> → models.json<br> | |
| <code>build_stats_json()</code> → stats.json<br> | |
| <code>compute_freq_anchors()</code> → freq_anchors.json | |
| </td> | |
| <td>Elasticity table per CH×Region×Pack; own-price E, Adj-R², dist E, comp coefs; anchor tags</td> | |
| </tr> | |
| <tr> | |
| <td>trends</td> | |
| <td><code>Trends.jsx</code></td> | |
| <td><code>trend</code></td> | |
| <td><code>build_trend_json()</code> → trend.json</td> | |
| <td>Monthly time-series chart; brand/channel/metric filter; volume, value, price, distribution lines</td> | |
| </tr> | |
| <tr> | |
| <td>ppa</td> | |
| <td><code>PPA.jsx</code></td> | |
| <td><code>ppa_mt</code>, <code>ppa_tt</code>, <code>freq_anchors</code>, <code>trend</code></td> | |
| <td> | |
| <code>build_ppa_json(ch='MT')</code> → ppa_mt.json<br> | |
| <code>build_ppa_json(ch='TT')</code> → ppa_tt.json | |
| </td> | |
| <td>Price-pack architecture matrix; MRP, price/ml, RPI, gross contribution per brand/pack; MT or TT toggle</td> | |
| </tr> | |
| <tr> | |
| <td>recs</td> | |
| <td><code>Recommendations.jsx</code></td> | |
| <td><code>recs</code>, <code>freq_anchors</code></td> | |
| <td><code>build_recs_json()</code> → recs_full.json <em>(served as "recs")</em></td> | |
| <td>Pricing recommendation cards per CH×Pack; ±5% scenarios; score, feasibility, vol/val/GC impact</td> | |
| </tr> | |
| <tr> | |
| <td>sim</td> | |
| <td><code>Simulation.jsx</code></td> | |
| <td><code>models</code></td> | |
| <td><code>build_model_export()</code> → models.json</td> | |
| <td>Interactive price simulator; user enters % price change; computes vol/val impact using own-price elasticity</td> | |
| </tr> | |
| <tr> | |
| <td>interaction</td> | |
| <td><code>BrandInteraction.jsx</code></td> | |
| <td><code>vtm</code>, <code>interaction</code>, <code>freq_anchors</code></td> | |
| <td> | |
| <code>build_vtm_json()</code> → vtm.json<br> | |
| <code>build_interaction_json()</code> → interaction.json | |
| </td> | |
| <td>Cross-brand correlation heatmap; volume-to-market by brand; channel/region filters</td> | |
| </tr> | |
| <tr> | |
| <td>growth</td> | |
| <td><code>GrowthDecomposition.jsx</code></td> | |
| <td><code>growth_decomp</code>, <code>stats</code></td> | |
| <td> | |
| <code>build_growth_decomp_json()</code> → growth_decomp.json<br> | |
| <code>build_stats_json()</code> → stats.json | |
| </td> | |
| <td>%-point contribution bars per grain to brand volume growth yr24→yr25; sorted by contribution</td> | |
| </tr> | |
| <tr> | |
| <td>gi</td> | |
| <td><code>PriceGradient.jsx</code></td> | |
| <td><code>pgi</code></td> | |
| <td><code>build_pgi_json()</code> → pgi.json</td> | |
| <td>Price Gradient Index table per channel; MRP, price/ml, relative gradient yr24 vs yr25</td> | |
| </tr> | |
| <tr> | |
| <td>methodology</td> | |
| <td><code>Methodology.jsx</code></td> | |
| <td><em>none</em></td> | |
| <td><em>—</em></td> | |
| <td>Static methodology explanation; no data dependency</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- ── DATA LOADING SEQUENCE ── --> | |
| <div class="card"> | |
| <div class="card-title">Part B — DataContext Loading Sequence (on page load or reload)</div> | |
| <p class="card-desc">This runs once on page load and again after every successful upload. All 16 keys are fetched sequentially; progress bar reflects completion.</p> | |
| <div class="mermaid"> | |
| sequenceDiagram | |
| participant DC as DataContext.jsx | |
| participant API as FastAPI :8000 | |
| participant FS as public/data/ (Vite static) | |
| Note over DC: loadAll() called on mount or reload() | |
| loop for each of 16 keys | |
| DC->>API: GET /api/data/{key} | |
| alt Server running & output file exists | |
| API-->>DC: 200 JSON (from output/{key}.json) | |
| else Server down or file missing | |
| DC->>FS: GET /data/{key}.json | |
| alt Static file exists | |
| FS-->>DC: 200 JSON (from public/data/{key}.json) | |
| else | |
| FS-->>DC: 404 | |
| DC-->>DC: key = null (tab shows empty state) | |
| end | |
| end | |
| Note over DC: setProgress((i+1)/16 × 100) | |
| end | |
| Note over DC: setData(all 16 keys)<br/>setLoading(false)<br/>All tabs can now render | |
| </div> | |
| </div> | |
| <!-- ── API ENDPOINT REFERENCE ── --> | |
| <div class="card"> | |
| <div class="card-title">API Endpoint Reference</div> | |
| <table> | |
| <thead><tr><th>Method</th><th>Endpoint</th><th>Called by</th><th>Function in upload_server.py</th><th>Purpose</th></tr></thead> | |
| <tbody> | |
| <tr> | |
| <td><span class="badge be">POST</span></td> | |
| <td><code>/api/upload</code></td> | |
| <td><code>handleFile()</code> — UploadModal.jsx</td> | |
| <td><code>upload_excel()</code></td> | |
| <td>Saves xlsx, starts background pipeline thread</td> | |
| </tr> | |
| <tr> | |
| <td><span class="badge be">POST</span></td> | |
| <td><code>/api/upload/reset</code></td> | |
| <td>UploadModal on open</td> | |
| <td><code>reset_job()</code></td> | |
| <td>Clears previous job state so fresh run can start</td> | |
| </tr> | |
| <tr> | |
| <td><span class="badge fe">GET</span></td> | |
| <td><code>/api/upload/status</code></td> | |
| <td><code>startPolling()</code> — every 1.5 s</td> | |
| <td><code>get_status()</code></td> | |
| <td>Returns { status, message, elapsed_s, filename }</td> | |
| </tr> | |
| <tr> | |
| <td><span class="badge fe">GET</span></td> | |
| <td><code>/api/upload/logs</code></td> | |
| <td><code>startPolling()</code> — every 1.5 s</td> | |
| <td><code>get_logs()</code></td> | |
| <td>Returns full pipeline stdout log accumulated so far</td> | |
| </tr> | |
| <tr> | |
| <td><span class="badge fe">GET</span></td> | |
| <td><code>/api/data/{key}</code></td> | |
| <td><code>fetchKey(key)</code> — DataContext.jsx</td> | |
| <td><code>get_data(key)</code></td> | |
| <td>Reads <code>output/{key}.json</code> written by pipeline; returns as JSON</td> | |
| </tr> | |
| <tr> | |
| <td><span class="badge fe">GET</span></td> | |
| <td><code>/api/data</code></td> | |
| <td>Debug / manual use</td> | |
| <td><code>list_data_keys()</code></td> | |
| <td>Lists all 16 keys with availability and file size</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- ── COMPLETE KEY→JSON→FUNCTION MAP ── --> | |
| <div class="card"> | |
| <div class="card-title">Complete Map: API Key → JSON File → Python Function → Tab</div> | |
| <table> | |
| <thead> | |
| <tr><th>API key</th><th>JSON file</th><th>Exporter file</th><th>Python function</th><th>Used in tab</th></tr> | |
| </thead> | |
| <tbody> | |
| <tr><td><code>models</code></td><td><code>models.json</code></td><td>exporters/stats.py</td><td><code>build_model_export()</code></td><td>results, sim</td></tr> | |
| <tr><td><code>stats</code></td><td><code>stats.json</code></td><td>exporters/stats.py</td><td><code>build_stats_json()</code></td><td>results, growth, header KPIs</td></tr> | |
| <tr><td><code>freq_anchors</code></td><td><code>freq_anchors.json</code></td><td>pipeline/proxies.py</td><td><code>compute_freq_anchors()</code></td><td>results, ppa, recs, interaction</td></tr> | |
| <tr><td><code>trend</code></td><td><code>trend.json</code></td><td>exporters/market.py</td><td><code>build_trend_json()</code></td><td>trends, ppa</td></tr> | |
| <tr><td><code>ms</code></td><td><code>ms.json</code></td><td>exporters/market.py</td><td><code>build_ms_json()</code></td><td>(available for market share views)</td></tr> | |
| <tr><td><code>vol_salience</code></td><td><code>vol_salience.json</code></td><td>exporters/market.py</td><td><code>build_vol_salience_json()</code></td><td>(available for pack salience charts)</td></tr> | |
| <tr><td><code>val_share</code></td><td><code>val_share.json</code></td><td>exporters/market.py</td><td><code>build_val_share_json()</code></td><td>(available for value share charts)</td></tr> | |
| <tr><td><code>comp_ms</code></td><td><code>comp_ms.json</code></td><td>exporters/market.py</td><td><code>build_comp_ms_json()</code></td><td>(available for competitor views)</td></tr> | |
| <tr><td><code>vtm</code></td><td><code>vtm.json</code></td><td>exporters/market.py</td><td><code>build_vtm_json()</code></td><td>interaction</td></tr> | |
| <tr><td><code>ppa_mt</code></td><td><code>ppa_mt.json</code></td><td>exporters/ppa.py</td><td><code>build_ppa_json(channel='MT')</code></td><td>ppa</td></tr> | |
| <tr><td><code>ppa_tt</code></td><td><code>ppa_tt.json</code></td><td>exporters/ppa.py</td><td><code>build_ppa_json(channel='TT')</code></td><td>ppa</td></tr> | |
| <tr><td><code>interaction</code></td><td><code>interaction.json</code></td><td>exporters/analytics.py</td><td><code>build_interaction_json()</code></td><td>interaction</td></tr> | |
| <tr><td><code>growth_decomp</code></td><td><code>growth_decomp.json</code></td><td>exporters/analytics.py</td><td><code>build_growth_decomp_json()</code></td><td>growth</td></tr> | |
| <tr><td><code>pgi</code></td><td><code>pgi.json</code></td><td>exporters/analytics.py</td><td><code>build_pgi_json()</code></td><td>gi</td></tr> | |
| <tr><td><code>recs</code> / <code>recs_full</code></td><td><code>recs_full.json</code></td><td>exporters/recommendations.py</td><td><code>build_recs_json()</code></td><td>recs</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <script> | |
| mermaid.initialize({ | |
| startOnLoad: true, | |
| theme: 'default', | |
| themeVariables: { | |
| primaryColor: '#e8f5e9', | |
| primaryTextColor: '#1b2e1b', | |
| primaryBorderColor: '#2e7d32', | |
| lineColor: '#2e7d32', | |
| secondaryColor: '#fff3e0', | |
| tertiaryColor: '#e3f2fd', | |
| fontSize: '12px', | |
| }, | |
| flowchart: { curve: 'basis', padding: 18, nodeSpacing: 40, rankSpacing: 50 }, | |
| sequence: { actorMargin: 80, messageMargin: 40, mirrorActors: false }, | |
| }) | |
| </script> | |
| </body> | |
| </html> | |