Every file triggered from the moment the Excel is uploaded to the moment the dashboard renders. In exact execution order.
Header()setUploadOpen(true)
uploadOpen=true → UploadModal mountsUploadModal()POST /api/upload/resetonDrop(e) or browsehandleFile(file)
/api/upload/reset (clears previous job)handleFile() runs:.xlsx/.xlsm/.xls)FormData({ file })POST /api/uploadstartPolling() — polls every 1.5 s
upload_excel()POST /api/upload_find_pipeline_python()Thread(_run_pipeline).start()
_uploads/filename.xlsx_find_pipeline_python() — finds Python with pandas_run_pipeline(xlsx_path)_run_pipeline(xlsx_path)_reset_steps()subprocess.Popen([python, "-m", "pipeline.run", ...])_annotate(line) per line
_annotate() prints step banners at key linesoutput/*.json → react-dashboard/public/data/_job["status"] = "done"
python -m pipeline.runmain()
--input, --output)DEFAULT_PACK_ORDERGUARDRAIL_MIN_OBS = 6MAX_COMP_COMBO_SIZE = 3ELASTICITY_ABS_CAP = 6.0SEASONAL_R2_THRESHOLD = 0.05ANCHOR_DOMINANCE_RATIO = 1.20REC_DELTA_PCT = 5.0
load_source_data(path)read_brand_config(path)
load_source_data():1_Source_Data sheet header rowPrice = Value / Volumeread_brand_config():2_Brand_Config sheetbuild_wide_pivot(df, focal, comps, ch, rg, pack_order)run_elasticity_models(all_data, comps, pack_order)ols(y, X) — internal
build_wide_pivot():run_elasticity_models():ols(y, X):run_diagnostics(all_data, comps)safe_corr(a, b) — internalsafe_trend(s) — internal
output/{focal}_Step1_Diagnostics.xlsx
assign_proxies(best_df, pack_order)compute_freq_anchors(df, focal)
assign_proxies():Final_OwnE, IsProxy, ProxyMethod columnscompute_freq_anchors():{CH|Region: {anchor, vol_sal}}
build_grain_metrics(df, focal, comps)build_model_export(final_df, metrics)build_stats_json(df, focal, final_df, anchors)print_summary(final_df, anchors)
build_grain_metrics(): vol sal, val share, MS yr25/yr24, price/ml per grainbuild_model_export(): flat list of grain elasticities + metrics → models.jsonbuild_stats_json(): brand KPIs (vol growth, avg ε, anchors, Cr value) → stats.jsonprint_summary(): prints grain/proxy/Adj-R² summary to terminal
build_trend_json(df, focal, comps)build_ms_json(df, focal)build_vol_salience_json(df, focal)build_val_share_json(df, focal)build_comp_ms_json(df, focal, comps)build_vtm_json(df, focal, comps)
build_ppa_json(df, focal, comps, channel, pack_order, excel_path, excel_sheet)_parse_ppa_sheet(path, sheet) — internal
channel="MT", sheet 3_PPA_MT → ppa_mt.jsonchannel="TT", sheet 4_PPA_TT → ppa_tt.jsonbuild_interaction_json(df, focal, comps)build_growth_decomp_json(df, focal)build_pgi_json(df, focal, pack_order)
build_recs_json(df, focal, comps, final_df, pack_order, excel_path)
final_df + GC% from PPA sheet
Modular app/output/shutil.copy2(src, REACT_DATA/fname)_job["status"] = "done"
output/*.json → react-dashboard/public/data/recs_full.json → recs.json (alias)"done" so frontend poll loop stopsstartPolling()GET /api/upload/statusGET /api/upload/logshandleReload()onSuccess(filename)
status="done"handleReload() → calls onSuccess(filename)handleSuccess(filename)setActiveFile(filename)localStorage.setItem(LS_KEY, filename)reload()
reload() from DataContext → triggers full data refresh
loadAll()fetchKey(key) × 16GET /api/data/{key} (primary)GET /data/{key}.json (fallback)setData(results)setLoading(false)
setProgress() updated per key (progress bar)setData(results) → all tabs re-render with fresh data
Dashboard()tabComponents[activeTab]
loading=false| # | Full File Path | Layer | Triggered by | Purpose |
|---|---|---|---|---|
| 1 | react-dashboard/src/components/Header.jsx | FE | User click | Opens upload modal |
| 2 | react-dashboard/src/components/UploadModal.jsx | FE | Header.jsx | File validation, POST /api/upload, polling |
| 3 | Modular app/upload_server.py | BE | POST /api/upload | Saves xlsx, spawns pipeline thread |
| 4 | Modular app/pipeline/run.py | PY | subprocess.Popen | Pipeline entry point — orchestrates all steps |
| 5 | Modular app/pipeline/config.py | CFG | import by run.py | All thresholds and constants (pack order, caps, ratios) |
| 6 | Modular app/pipeline/loader.py | PY | run.py line 65 | Read 1_Source_Data + 2_Brand_Config sheets from xlsx |
| 7 | Modular app/pipeline/modelling.py | PY | run.py line 111, 134 | Wide pivot per grain + OLS elasticity model search |
| 8 | Modular app/pipeline/diagnostics.py | PY | run.py line 125 | Correlation, CV, RPI checks → writes Diagnostics.xlsx |
| 9 | Modular app/pipeline/proxies.py | PY | run.py line 140, 145 | Proxy elasticity for wrong-sign grains + frequency anchors |
| 10 | Modular app/pipeline/exporters/stats.py | PY | run.py line 153 | → models.json, stats.json, freq_anchors.json |
| 11 | Modular app/pipeline/exporters/market.py | PY | run.py line 178 | → trend.json, ms.json, vol_salience.json, val_share.json, comp_ms.json, vtm.json |
| 12 | Modular app/pipeline/exporters/ppa.py | PY | run.py line 198, 206 | → ppa_mt.json, ppa_tt.json (reads 3_PPA_MT, 4_PPA_TT sheets) |
| 13 | Modular app/pipeline/exporters/analytics.py | PY | run.py line 217 | → interaction.json, growth_decomp.json, pgi.json |
| 14 | Modular app/pipeline/exporters/recommendations.py | PY | run.py line 220 | → recs_full.json (pricing rec cards ±5% scenarios) |
| 15 | Modular app/upload_server.py | BE | After pipeline exits 0 | Copies output/*.json → react-dashboard/public/data/, sets job done |
| 16 | react-dashboard/src/DataContext.jsx | FE | Header.handleSuccess() → reload() | Fetches all 16 keys via GET /api/data/{key}, updates React state |
| 17 | react-dashboard/src/App.jsx | FE | DataContext loading=false | Clears loading screen, renders active tab with fresh data |
tabs/ElasticityResults.jsx
tabs/Trends.jsx
tabs/PPA.jsx
tabs/Recommendations.jsx
tabs/Simulation.jsx
tabs/BrandInteraction.jsx
tabs/GrowthDecomposition.jsx
tabs/PriceGradient.jsx
tabs/Methodology.jsx
— these mount after tab click, not on upload.