Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"/> | |
| <title>Genesis AI β Full Code Flow</title> | |
| <style> | |
| *{box-sizing:border-box;margin:0;padding:0} | |
| body{font-family:'Segoe UI',Arial,sans-serif;background:#f4f6f9;color:#1a1a2e;padding:30px} | |
| h1{font-size:22px;font-weight:700;color:#1a6b2f;border-bottom:3px solid #1a6b2f;padding-bottom:10px;margin-bottom:4px} | |
| .doc-sub{font-size:11px;color:#888;margin-bottom:28px} | |
| /* ββ step card ββ */ | |
| .step{background:#fff;border-radius:12px;box-shadow:0 2px 10px rgba(0,0,0,.07);margin-bottom:22px;overflow:hidden} | |
| .step-hdr{display:flex;align-items:center;gap:12px;padding:14px 20px;border-bottom:1px solid #e8ede9} | |
| .step-num{background:#1a6b2f;color:#fff;font-size:13px;font-weight:700;width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0} | |
| .step-title{font-size:14px;font-weight:700;color:#1a1a2e} | |
| .step-file{font-size:10px;font-family:'Consolas',monospace;color:#1a6b2f;background:#e8f5e9;padding:2px 8px;border-radius:4px;margin-left:auto;white-space:nowrap} | |
| .tag{display:inline-block;font-size:10px;font-weight:700;padding:2px 8px;border-radius:4px;margin-right:4px} | |
| .fe {background:#e3f2fd;color:#1565c0} | |
| .api{background:#fff3e0;color:#e65100} | |
| .py {background:#e8f5e9;color:#1a6b2f} | |
| .out{background:#f3e5f5;color:#6a1b9a} | |
| .step-body{padding:16px 20px;display:grid;grid-template-columns:1fr 1fr;gap:16px} | |
| .step-body.full{grid-template-columns:1fr} | |
| .panel-title{font-size:10px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#888;margin-bottom:8px} | |
| /* ββ code block ββ */ | |
| pre{background:#1a1a2e;color:#d4e6c3;border-radius:8px;padding:14px 16px;font-family:'Consolas',monospace;font-size:10.5px;line-height:1.6;overflow-x:auto;white-space:pre} | |
| pre .kw {color:#c792ea} | |
| pre .fn {color:#82aaff} | |
| pre .st {color:#c3e88d} | |
| pre .cm {color:#546e7a;font-style:italic} | |
| pre .hl {background:rgba(255,235,59,.12);display:block} | |
| /* ββ desc block ββ */ | |
| .desc{font-size:12px;line-height:1.7;color:#333} | |
| .desc ul{margin-left:18px;margin-top:6px} | |
| .desc li{margin-bottom:4px} | |
| .desc .arrow{color:#1a6b2f;font-weight:700} | |
| .desc code{font-family:'Consolas',monospace;font-size:11px;background:#f0f4f0;padding:2px 5px;border-radius:3px} | |
| /* ββ arrow connector ββ */ | |
| .connector{text-align:center;font-size:22px;color:#1a6b2f;margin:-6px 0;line-height:1} | |
| /* ββ output row ββ */ | |
| .out-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px;padding:14px 20px;border-top:1px solid #e8ede9;background:#fafcfa} | |
| .out-card{background:#fff;border:1px solid #c8e6c9;border-radius:7px;padding:9px 12px} | |
| .out-card .fname{font-family:'Consolas',monospace;font-size:10.5px;font-weight:700;color:#1a6b2f} | |
| .out-card .fused{font-size:10px;color:#888;margin-top:2px} | |
| /* ββ API pill ββ */ | |
| .api-pill{display:inline-flex;align-items:center;gap:6px;background:#fff8f0;border:1px solid #ffcc80;border-radius:6px;padding:4px 10px;margin-bottom:10px} | |
| .api-method{font-size:10px;font-weight:700;color:#e65100;font-family:'Consolas',monospace} | |
| .api-url{font-size:11px;font-family:'Consolas',monospace;color:#333} | |
| .divider{border:none;border-top:2px dashed #c8e6c9;margin:24px 0} | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Genesis AI β Full Code Flow</h1> | |
| <div class="doc-sub">Every button click, API call, and Python function β in execution order</div> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- STEP 1 β UPLOAD BUTTON CLICK --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <div class="step"> | |
| <div class="step-hdr"> | |
| <div class="step-num">1</div> | |
| <div> | |
| <div class="step-title">User clicks β Upload button</div> | |
| <div style="margin-top:3px"><span class="tag fe">Frontend</span></div> | |
| </div> | |
| <div class="step-file">Header.jsx β UploadModal.jsx</div> | |
| </div> | |
| <div class="step-body"> | |
| <div> | |
| <div class="panel-title">Code that runs (Header.jsx)</div> | |
| <pre><span class="kw">const</span> [uploadOpen, setUploadOpen] = <span class="fn">useState</span>(<span class="kw">false</span>) | |
| <span class="cm">// Button in header</span> | |
| <button <span class="fn">onClick</span>={() => <span class="fn">setUploadOpen</span>(<span class="kw">true</span>)}> | |
| β Upload | |
| </button> | |
| <span class="cm">// Opens modal</span> | |
| <<span class="fn">UploadModal</span> | |
| open={uploadOpen} | |
| onClose={() => <span class="fn">setUploadOpen</span>(<span class="kw">false</span>)} | |
| onSuccess={handleSuccess} | |
| /></pre> | |
| </div> | |
| <div> | |
| <div class="panel-title">What happens</div> | |
| <div class="desc"> | |
| <ul> | |
| <li><code>setUploadOpen(true)</code> triggers React state update</li> | |
| <li><code>UploadModal</code> component mounts and becomes visible</li> | |
| <li>On mount, UploadModal calls <code>POST /api/upload/reset</code> to clear any previous job state on the server</li> | |
| <li>User sees drag-and-drop zone for <code>.xlsx</code> file</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="connector">β</div> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- STEP 2 β RESET OLD JOB --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <div class="step"> | |
| <div class="step-hdr"> | |
| <div class="step-num">2</div> | |
| <div> | |
| <div class="step-title">Browser resets server job state</div> | |
| <div style="margin-top:3px"><span class="tag api">API</span><span class="tag py">Python</span></div> | |
| </div> | |
| <div class="step-file">UploadModal.jsx β upload_server.py</div> | |
| </div> | |
| <div class="step-body"> | |
| <div> | |
| <div class="panel-title">Frontend call (UploadModal.jsx)</div> | |
| <pre><span class="cm">// Called when modal opens</span> | |
| <span class="kw">useEffect</span>(() => { | |
| <span class="kw">if</span> (open) { | |
| <span class="fn">fetch</span>(<span class="st">'/api/upload/reset'</span>, { method: <span class="st">'POST'</span> }) | |
| } | |
| }, [open])</pre> | |
| </div> | |
| <div> | |
| <div class="panel-title">Python handler (upload_server.py)</div> | |
| <pre><span class="kw">@app</span>.<span class="fn">post</span>(<span class="st">"/api/upload/reset"</span>) | |
| <span class="kw">def</span> <span class="fn">reset_job</span>(): | |
| <span class="kw">with</span> _job_lock: | |
| _job[<span class="st">"status"</span>] = <span class="st">"idle"</span> | |
| _job[<span class="st">"message"</span>] = <span class="st">""</span> | |
| _job[<span class="st">"logs"</span>] = <span class="st">""</span> | |
| _job[<span class="st">"filename"</span>] = <span class="kw">None</span> | |
| <span class="kw">return</span> {<span class="st">"ok"</span>: <span class="kw">True</span>}</pre> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="connector">β</div> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- STEP 3 β FILE UPLOAD --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <div class="step"> | |
| <div class="step-hdr"> | |
| <div class="step-num">3</div> | |
| <div> | |
| <div class="step-title">User drops / picks .xlsx file β POST /api/upload</div> | |
| <div style="margin-top:3px"><span class="tag api">API</span><span class="tag py">Python</span></div> | |
| </div> | |
| <div class="step-file">UploadModal.jsx β upload_server.py</div> | |
| </div> | |
| <div class="step-body"> | |
| <div> | |
| <div class="panel-title">Frontend call (UploadModal.jsx)</div> | |
| <pre><span class="kw">async function</span> <span class="fn">handleFile</span>(file) { | |
| <span class="kw">const</span> form = <span class="kw">new</span> <span class="fn">FormData</span>() | |
| form.<span class="fn">append</span>(<span class="st">'file'</span>, file) | |
| <span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="st">'/api/upload'</span>, { | |
| method: <span class="st">'POST'</span>, | |
| body: form <span class="cm">// multipart/form-data</span> | |
| }) | |
| <span class="kw">const</span> json = <span class="kw">await</span> res.<span class="fn">json</span>() | |
| <span class="cm">// β { ok: true, filename: "..." }</span> | |
| <span class="fn">setPhase</span>(<span class="st">'running'</span>) | |
| <span class="fn">startPolling</span>() <span class="cm">// begins GET /status every 1.5s</span> | |
| }</pre> | |
| </div> | |
| <div> | |
| <div class="panel-title">Python handler (upload_server.py)</div> | |
| <pre><span class="kw">@app</span>.<span class="fn">post</span>(<span class="st">"/api/upload"</span>) | |
| <span class="kw">async def</span> <span class="fn">upload_excel</span>(file: UploadFile): | |
| <span class="cm"># Save file to disk</span> | |
| dest = UPLOAD_DIR / file.filename | |
| dest.<span class="fn">write_bytes</span>(<span class="kw">await</span> file.<span class="fn">read</span>()) | |
| <span class="cm"># Launch pipeline in background thread</span> | |
| thread = threading.<span class="fn">Thread</span>( | |
| target=_run_pipeline, | |
| args=(dest,), daemon=<span class="kw">True</span> | |
| ) | |
| thread.<span class="fn">start</span>() | |
| <span class="cm"># Return immediately β don't wait for pipeline</span> | |
| <span class="kw">return</span> {<span class="st">"ok"</span>: <span class="kw">True</span>, <span class="st">"filename"</span>: file.filename}</pre> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="connector">β</div> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- STEP 4 β SUBPROCESS --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <div class="step"> | |
| <div class="step-hdr"> | |
| <div class="step-num">4</div> | |
| <div> | |
| <div class="step-title">Background thread launches Python pipeline subprocess</div> | |
| <div style="margin-top:3px"><span class="tag py">Python</span></div> | |
| </div> | |
| <div class="step-file">upload_server.py β pipeline/run.py</div> | |
| </div> | |
| <div class="step-body"> | |
| <div> | |
| <div class="panel-title">Code (upload_server.py β _run_pipeline)</div> | |
| <pre><span class="kw">def</span> <span class="fn">_run_pipeline</span>(xlsx_path): | |
| pipeline_python = <span class="fn">_find_pipeline_python</span>() | |
| <span class="cm"># finds Python 3.13 that has pandas</span> | |
| cmd = [ | |
| pipeline_python, <span class="st">"-m"</span>, <span class="st">"pipeline.run"</span>, | |
| <span class="st">"--input"</span>, str(xlsx_path), | |
| <span class="st">"--output"</span>, str(OUTPUT_DIR), | |
| ] | |
| env = os.environ.<span class="fn">copy</span>() | |
| env[<span class="st">"PYTHONIOENCODING"</span>] = <span class="st">"utf-8"</span> | |
| <span class="cm"># Stream output line-by-line to terminal</span> | |
| proc = subprocess.<span class="fn">Popen</span>( | |
| cmd, cwd=str(BASE_DIR), | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.STDOUT, | |
| text=<span class="kw">True</span>, env=env, | |
| ) | |
| <span class="kw">for</span> line <span class="kw">in</span> proc.stdout: | |
| <span class="fn">_annotate</span>(line, xlsx_path.name) <span class="cm"># print step headers</span> | |
| <span class="fn">_tprint</span>(line) <span class="cm"># live terminal output</span></pre> | |
| </div> | |
| <div> | |
| <div class="panel-title">While pipeline runs β browser polls</div> | |
| <pre><span class="cm">// UploadModal.jsx polls every 1500ms</span> | |
| <span class="kw">async function</span> <span class="fn">poll</span>() { | |
| <span class="kw">const</span> s = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="st">'/api/upload/status'</span>) | |
| <span class="kw">const</span> l = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="st">'/api/upload/logs'</span>) | |
| <span class="cm">// shows live logs in modal</span> | |
| <span class="kw">if</span> (s.status === <span class="st">'done'</span>) stopPolling() | |
| <span class="kw">if</span> (s.status === <span class="st">'error'</span>) stopPolling() | |
| }</pre> | |
| <div class="desc" style="margin-top:10px"> | |
| <ul> | |
| <li><span class="arrow">β</span> <code>GET /api/upload/status</code> returns <code>{ status, message, elapsed_s }</code></li> | |
| <li><span class="arrow">β</span> <code>GET /api/upload/logs</code> returns <code>{ logs }</code> β live pipeline stdout</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="connector">β</div> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- STEP 5 β LOADER --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <div class="step"> | |
| <div class="step-hdr"> | |
| <div class="step-num">5</div> | |
| <div> | |
| <div class="step-title">Load Excel data β pipeline/loader.py</div> | |
| <div style="margin-top:3px"><span class="tag py">Python</span></div> | |
| </div> | |
| <div class="step-file">pipeline/loader.py</div> | |
| </div> | |
| <div class="step-body"> | |
| <div> | |
| <div class="panel-title">load_source_data( )</div> | |
| <pre><span class="kw">def</span> <span class="fn">load_source_data</span>(path): | |
| <span class="cm"># Opens sheet 1_Source_Data</span> | |
| raw = pd.<span class="fn">read_excel</span>(path, | |
| sheet_name=<span class="st">"1_Source_Data"</span>, | |
| header=<span class="kw">None</span>) | |
| <span class="cm"># Finds header row (where col[1] == "Brand")</span> | |
| hdr_row = <span class="fn">next</span>(i <span class="kw">for</span> i, r <span class="kw">in</span> raw.<span class="fn">iterrows</span>() | |
| <span class="kw">if</span> str(r.iloc[1]).<span class="fn">strip</span>().<span class="fn">lower</span>() == <span class="st">"brand"</span>) | |
| df = pd.<span class="fn">read_excel</span>(path, | |
| sheet_name=<span class="st">"1_Source_Data"</span>, | |
| skiprows=hdr_row, header=<span class="st">0</span>) | |
| <span class="cm"># Computes Price = Value / Volume</span> | |
| df[<span class="st">"Price"</span>] = df[<span class="st">"Value"</span>] / df[<span class="st">"Volume"</span>] | |
| df[<span class="st">"Date"</span>] = pd.<span class="fn">to_datetime</span>(df[<span class="st">"Date"</span>]) | |
| df[<span class="st">"Month"</span>] = df[<span class="st">"Date"</span>].dt.<span class="fn">to_period</span>(<span class="st">"M"</span>) | |
| <span class="kw">return</span> df <span class="cm"># 3,207 rows</span></pre> | |
| </div> | |
| <div> | |
| <div class="panel-title">read_brand_config( )</div> | |
| <pre><span class="kw">def</span> <span class="fn">read_brand_config</span>(path): | |
| <span class="cm"># Opens sheet 2_Brand_Config</span> | |
| raw = pd.<span class="fn">read_excel</span>(path, | |
| sheet_name=<span class="st">"2_Brand_Config"</span>, | |
| header=<span class="kw">None</span>) | |
| <span class="cm"># Reads focal brand name</span> | |
| focal1 = <span class="fn">_get</span>(<span class="st">"Focal Brand Name"</span>) | |
| <span class="cm"># Reads competitor brand names</span> | |
| <span class="kw">for</span> _, row <span class="kw">in</span> raw.<span class="fn">iterrows</span>(): | |
| slot = str(row.iloc[1]).<span class="fn">strip</span>() | |
| <span class="kw">if</span> slot.<span class="fn">startswith</span>(<span class="st">"Comp"</span>): | |
| config[<span class="st">"competitors"</span>].<span class="fn">append</span>(row.iloc[2]) | |
| <span class="kw">return</span> config | |
| <span class="cm"># β { focal_brands: ['DABUR SARSON AMLA'],</span> | |
| <span class="cm"># competitors: ['NIHAR...','PARACHUTE'] }</span></pre> | |
| <div class="desc" style="margin-top:10px"> | |
| <ul> | |
| <li>Reads <b>sheet 1</b>: 3,207 rows of monthly brand/channel/pack sales data</li> | |
| <li>Reads <b>sheet 2</b>: focal brand name + competitor list</li> | |
| <li>Output: clean <code>df</code> DataFrame passed to all subsequent steps</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="connector">β</div> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- STEP 6 β WIDE PIVOT --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <div class="step"> | |
| <div class="step-hdr"> | |
| <div class="step-num">6</div> | |
| <div> | |
| <div class="step-title">Build wide pivot tables β pipeline/modelling.py</div> | |
| <div style="margin-top:3px"><span class="tag py">Python</span></div> | |
| </div> | |
| <div class="step-file">pipeline/modelling.py β build_wide_pivot()</div> | |
| </div> | |
| <div class="step-body"> | |
| <div> | |
| <div class="panel-title">build_wide_pivot( ) β called per Channel Γ Region</div> | |
| <pre><span class="kw">def</span> <span class="fn">build_wide_pivot</span>(df, focal, competitors, | |
| channel, region, pack_order): | |
| sub = df[(df[<span class="st">"Channel"</span>] == channel) | |
| & (df[<span class="st">"Region"</span>] == region)] | |
| <span class="cm"># Focal brand columns</span> | |
| focal_df = sub[sub[<span class="st">"Brand"</span>] == focal].<span class="fn">rename</span>(columns={ | |
| <span class="st">"Price"</span>: <span class="st">"Price_F"</span>, <span class="st">"Volume"</span>: <span class="st">"Vol_F"</span>, | |
| <span class="st">"Distribution"</span>: <span class="st">"Dist_F"</span>, <span class="st">"Value"</span>: <span class="st">"Val_F"</span>, | |
| }) | |
| <span class="cm"># Merge competitor columns</span> | |
| <span class="kw">for</span> comp <span class="kw">in</span> competitors: | |
| short = comp[:8].<span class="fn">replace</span>(<span class="st">" "</span>, <span class="st">"_"</span>) | |
| comp_df = sub[sub[<span class="st">"Brand"</span>] == comp].<span class="fn">rename</span>(columns={ | |
| <span class="st">"Price"</span>: <span class="st">f"Price_{short}"</span>, | |
| <span class="st">"Volume"</span>: <span class="st">f"Vol_{short}"</span>, | |
| }) | |
| m = m.<span class="fn">merge</span>(comp_df, on=[<span class="st">"Month"</span>,<span class="st">"Pack Size Group"</span>]) | |
| <span class="cm"># Category volume = sum of all brands</span> | |
| cat = sub.<span class="fn">groupby</span>([<span class="st">"Month"</span>,<span class="st">"Pack Size Group"</span>])[<span class="st">"Volume"</span>] | |
| .<span class="fn">sum</span>().<span class="fn">rename</span>(<span class="st">"Cat_Vol"</span>) | |
| <span class="cm"># Adjacent pack volumes (for cannibalization)</span> | |
| m[<span class="st">"Vol_Up"</span>] = <span class="cm"># volume of next larger pack</span> | |
| m[<span class="st">"Vol_Down"</span>] = <span class="cm"># volume of next smaller pack</span> | |
| <span class="kw">return</span> m <span class="cm"># 674 rows, 33 grains total</span></pre> | |
| </div> | |
| <div> | |
| <div class="panel-title">Columns in the output DataFrame</div> | |
| <div class="desc"> | |
| <ul> | |
| <li><b>Focal brand:</b> <code>Vol_F, Price_F, Dist_F, Val_F</code></li> | |
| <li><b>Per competitor:</b> <code>Price_NIHAR__, Vol_NIHAR__, Dist_NIHAR__</code> etc.</li> | |
| <li><b>Category:</b> <code>Cat_Vol</code> β sum of ALL brands at same CH/RG/Pack/Month</li> | |
| <li><b>Cannibalization:</b> <code>Vol_Up, Vol_Down</code> β adjacent pack volumes</li> | |
| <li><b>Result:</b> 33 grains Γ 24 months = 674 rows total</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="connector">β</div> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- STEP 7 β OLS MODELS --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <div class="step"> | |
| <div class="step-hdr"> | |
| <div class="step-num">7</div> | |
| <div> | |
| <div class="step-title">Run OLS elasticity models β pipeline/modelling.py</div> | |
| <div style="margin-top:3px"><span class="tag py">Python</span></div> | |
| </div> | |
| <div class="step-file">pipeline/modelling.py β run_elasticity_models()</div> | |
| </div> | |
| <div class="step-body"> | |
| <div> | |
| <div class="panel-title">run_elasticity_models( ) β tests 343 spec combinations</div> | |
| <pre><span class="kw">def</span> <span class="fn">run_elasticity_models</span>(all_data, competitors, pack_order): | |
| <span class="kw">for</span> (fb, ch, rg, ps), g <span class="kw">in</span> grains: | |
| <span class="cm"># Build log-transformed variables</span> | |
| log_vol = np.<span class="fn">log</span>(g[<span class="st">"Vol_F"</span>].<span class="fn">clip</span>(1e-9)) | |
| log_p = np.<span class="fn">log</span>(g[<span class="st">"Price_F"</span>].<span class="fn">clip</span>(1e-9)) | |
| log_dist = np.<span class="fn">log</span>(g[<span class="st">"Dist_F"</span>].<span class="fn">clip</span>(1e-9)) | |
| log_cat = np.<span class="fn">log</span>(g[<span class="st">"Cat_Vol"</span>].<span class="fn">clip</span>(1e-9)) | |
| <span class="cm"># Test every combination of specs</span> | |
| <span class="kw">for</span> use_seasonal <span class="kw">in</span> (<span class="kw">False</span>, <span class="kw">True</span>): | |
| <span class="kw">for</span> combo <span class="kw">in</span> comp_combos: <span class="cm"># none, 1 comp, 2 comps, 3 comps</span> | |
| X = [log_p, log_dist, log_cat, *combo] | |
| res = <span class="fn">ols</span>(log_vol, X) | |
| <span class="cm"># Sign guardrail: own_e < 0 AND dist_e > 0</span> | |
| sign_ok = res.own_e < <span class="st">0</span> and res.dist_e > <span class="st">0</span> | |
| <span class="cm"># Select best Adj-RΒ² with correct sign</span> | |
| best = ok_specs.<span class="fn">loc</span>[ok_specs[<span class="st">"AdjR2"</span>].<span class="fn">idxmax</span>()]</pre> | |
| </div> | |
| <div> | |
| <div class="panel-title">ols( ) β core OLS estimator</div> | |
| <pre><span class="kw">def</span> <span class="fn">ols</span>(y, X): | |
| Xc = np.<span class="fn">column_stack</span>([np.<span class="fn">ones</span>(n), X]) | |
| <span class="cm"># Solve via numpy least squares</span> | |
| betas, _, _, _ = np.linalg.<span class="fn">lstsq</span>(Xc, y) | |
| <span class="cm"># Compute RΒ², Adj-RΒ², t-stats, p-values</span> | |
| y_hat = Xc @ betas | |
| sse = ((y - y_hat)**2).<span class="fn">sum</span>() | |
| sst = ((y - y.<span class="fn">mean</span>())**2).<span class="fn">sum</span>() | |
| r2 = <span class="st">1</span> - sse / sst | |
| adj_r2 = <span class="st">1</span> - (<span class="st">1</span>-r2) * (n-<span class="st">1</span>) / (n-k-<span class="st">1</span>) | |
| <span class="kw">return</span> {<span class="st">"betas"</span>: betas, <span class="st">"adj_r2"</span>: adj_r2, | |
| <span class="st">"t"</span>: t, <span class="st">"p"</span>: p} | |
| <span class="cm"># own-price elasticity = betas[1] (log-log β direct elasticity)</span></pre> | |
| <div class="desc" style="margin-top:10px"> | |
| <ul> | |
| <li>343 spec combos tested per grain</li> | |
| <li>19 / 26 grains get direct model with correct sign</li> | |
| <li>7 grains flagged as <code>Forced=True</code> β go to proxies</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="connector">β</div> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- STEP 8 β PROXIES --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <div class="step"> | |
| <div class="step-hdr"> | |
| <div class="step-num">8</div> | |
| <div> | |
| <div class="step-title">Assign proxy elasticities β pipeline/proxies.py</div> | |
| <div style="margin-top:3px"><span class="tag py">Python</span></div> | |
| </div> | |
| <div class="step-file">pipeline/proxies.py β assign_proxies()</div> | |
| </div> | |
| <div class="step-body"> | |
| <div> | |
| <div class="panel-title">assign_proxies( ) β fills 7 forced grains</div> | |
| <pre><span class="kw">def</span> <span class="fn">assign_proxies</span>(best_df, pack_order): | |
| <span class="kw">for</span> _, row <span class="kw">in</span> best_df.<span class="fn">iterrows</span>(): | |
| <span class="kw">if not</span> row[<span class="st">"Forced"</span>]: | |
| <span class="cm"># Direct model β use as-is</span> | |
| row[<span class="st">"Final_OwnE"</span>] = row[<span class="st">"OwnE"</span>] | |
| row[<span class="st">"IsProxy"</span>] = <span class="kw">False</span> | |
| <span class="kw">continue</span> | |
| <span class="cm"># Priority 1: interpolate adjacent packs</span> | |
| <span class="kw">if</span> lower <span class="kw">and</span> upper: | |
| row[<span class="st">"Final_OwnE"</span>] = (lo_e + hi_e) / <span class="st">2</span> | |
| row[<span class="st">"ProxyMethod"</span>] = <span class="st">f"Interp {lower} & {upper}"</span> | |
| <span class="cm"># Priority 2: borrow from nearest valid pack</span> | |
| <span class="kw">else</span>: | |
| row[<span class="st">"Final_OwnE"</span>] = closest[<span class="st">"OwnE"</span>] | |
| row[<span class="st">"ProxyMethod"</span>] = <span class="st">f"Borrowed from {closest['Pack']}"</span> | |
| <span class="cm"># Clamp to [-6.0, 0] (config: ELASTICITY_ABS_CAP = 6.0)</span> | |
| final_df[<span class="st">"Final_OwnE"</span>] = final_df[<span class="st">"Final_OwnE"</span>].<span class="fn">clip</span>(lower=<span class="st">-6.0</span>) | |
| <span class="kw">return</span> final_df <span class="cm"># 26 rows, all sign-OK</span></pre> | |
| </div> | |
| <div> | |
| <div class="panel-title">compute_freq_anchors( )</div> | |
| <pre><span class="kw">def</span> <span class="fn">compute_freq_anchors</span>(df, focal): | |
| <span class="kw">for</span> ch <span class="kw">in</span> channels: | |
| <span class="kw">for</span> rg <span class="kw">in</span> regions: | |
| <span class="cm"># Vol salience per pack</span> | |
| vs = { ps: vol / brand_tot * <span class="st">100</span> | |
| <span class="kw">for</span> ps <span class="kw">in</span> packs } | |
| <span class="cm"># Anchor = dominant pack </span> | |
| <span class="cm"># (must be β₯ 1.20x next largest)</span> | |
| <span class="kw">if</span> top >= <span class="st">1.20</span> * second: | |
| anchor = <span class="fn">max</span>(vs, key=vs.get) | |
| anchors[<span class="st">f"{ch}|{rg}"</span>] = { | |
| <span class="st">"anchor"</span>: anchor, | |
| <span class="st">"vol_sal"</span>: vs[anchor] | |
| } | |
| <span class="kw">return</span> anchors | |
| <span class="cm"># e.g. TT|All India β 33-80 (72.5% vol)</span></pre> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="connector">β</div> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- STEP 9 β EXPORTERS --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <div class="step"> | |
| <div class="step-hdr"> | |
| <div class="step-num">9</div> | |
| <div> | |
| <div class="step-title">Write JSON outputs β pipeline/exporters/</div> | |
| <div style="margin-top:3px"><span class="tag out">Output</span></div> | |
| </div> | |
| <div class="step-file">5 exporter modules β 16 JSON files</div> | |
| </div> | |
| <div class="step-body full"> | |
| <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px"> | |
| <div> | |
| <div class="panel-title">exporters/stats.py</div> | |
| <pre><span class="fn">build_model_export</span>(final_df, grain_metrics) | |
| <span class="cm"># β models.json (elasticity per grain)</span> | |
| <span class="fn">build_stats_json</span>(df, focal, final_df, anchors) | |
| <span class="cm"># β stats.json (KPIs: vol, val, growth%)</span> | |
| <span class="cm"># β freq_anchors.json (dominant packs)</span></pre> | |
| <div class="panel-title" style="margin-top:12px">exporters/market.py</div> | |
| <pre><span class="fn">build_trend_json</span>(df, focal, competitors) | |
| <span class="cm"># β trend.json (monthly time-series)</span> | |
| <span class="fn">build_ms_json</span>(df, focal) | |
| <span class="cm"># β ms.json (market share yr25 vs yr24)</span> | |
| <span class="fn">build_comp_ms_json</span>(df, focal, competitors) | |
| <span class="cm"># β comp_ms.json (all-brand share)</span> | |
| <span class="fn">build_vtm_json</span>(df, focal, competitors) | |
| <span class="cm"># β vtm.json (volume-to-market)</span> | |
| <span class="fn">build_vol_salience_json</span>(df, focal) | |
| <span class="cm"># β vol_salience.json</span> | |
| <span class="fn">build_val_share_json</span>(df, focal) | |
| <span class="cm"># β val_share.json</span></pre> | |
| </div> | |
| <div> | |
| <div class="panel-title">exporters/ppa.py</div> | |
| <pre><span class="fn">build_ppa_json</span>(df, focal, competitors, | |
| channel=mt_ch, excel_sheet=<span class="st">"3_PPA_MT"</span>) | |
| <span class="cm"># reads PPA sheet from Excel</span> | |
| <span class="cm"># β ppa_mt.json</span> | |
| <span class="fn">build_ppa_json</span>(df, focal, competitors, | |
| channel=tt_ch, excel_sheet=<span class="st">"4_PPA_TT"</span>) | |
| <span class="cm"># β ppa_tt.json</span></pre> | |
| <div class="panel-title" style="margin-top:12px">exporters/analytics.py</div> | |
| <pre><span class="fn">build_interaction_json</span>(df, focal, competitors) | |
| <span class="cm"># Pearson correlation per brandΓpack pair</span> | |
| <span class="cm"># β interaction.json</span> | |
| <span class="fn">build_growth_decomp_json</span>(df, focal) | |
| <span class="cm"># pp-contribution to brand vol growth</span> | |
| <span class="cm"># β growth_decomp.json</span> | |
| <span class="fn">build_pgi_json</span>(df, focal, pack_order) | |
| <span class="cm"># price gradient index per channel</span> | |
| <span class="cm"># β pgi.json</span></pre> | |
| <div class="panel-title" style="margin-top:12px">exporters/recommendations.py</div> | |
| <pre><span class="fn">build_recs_json</span>(df, focal, competitors, | |
| final_df, pack_order, excel_path) | |
| <span class="cm"># pricing rec cards per CH Γ Pack</span> | |
| <span class="cm"># β recs_full.json</span></pre> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="out-grid"> | |
| <div class="out-card"><div class="fname">models.json</div><div class="fused">β Elasticity Results tab</div></div> | |
| <div class="out-card"><div class="fname">stats.json</div><div class="fused">β Header KPIs</div></div> | |
| <div class="out-card"><div class="fname">freq_anchors.json</div><div class="fused">β Methodology tab</div></div> | |
| <div class="out-card"><div class="fname">trend.json</div><div class="fused">β Trends tab</div></div> | |
| <div class="out-card"><div class="fname">ms.json</div><div class="fused">β shared</div></div> | |
| <div class="out-card"><div class="fname">comp_ms.json</div><div class="fused">β shared</div></div> | |
| <div class="out-card"><div class="fname">vtm.json</div><div class="fused">β Brand Interaction tab</div></div> | |
| <div class="out-card"><div class="fname">vol_salience.json</div><div class="fused">β shared</div></div> | |
| <div class="out-card"><div class="fname">val_share.json</div><div class="fused">β shared</div></div> | |
| <div class="out-card"><div class="fname">ppa_mt.json</div><div class="fused">β PPA tab MT</div></div> | |
| <div class="out-card"><div class="fname">ppa_tt.json</div><div class="fused">β PPA tab TT</div></div> | |
| <div class="out-card"><div class="fname">interaction.json</div><div class="fused">β Brand Interaction tab</div></div> | |
| <div class="out-card"><div class="fname">growth_decomp.json</div><div class="fused">β Growth Decomp tab</div></div> | |
| <div class="out-card"><div class="fname">pgi.json</div><div class="fused">β Price Gradient tab</div></div> | |
| <div class="out-card"><div class="fname">recs_full.json</div><div class="fused">β Recommendations tab</div></div> | |
| <div class="out-card"><div class="fname">recs.json</div><div class="fused">β alias of recs_full</div></div> | |
| </div> | |
| </div> | |
| <div class="connector">β</div> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- STEP 10 β SERVE DATA --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <div class="step"> | |
| <div class="step-hdr"> | |
| <div class="step-num">10</div> | |
| <div> | |
| <div class="step-title">User clicks Reload Dashboard β React fetches all data</div> | |
| <div style="margin-top:3px"><span class="tag api">API</span><span class="tag py">Python</span><span class="tag fe">Frontend</span></div> | |
| </div> | |
| <div class="step-file">DataContext.jsx β upload_server.py β React tabs</div> | |
| </div> | |
| <div class="step-body"> | |
| <div> | |
| <div class="panel-title">Python endpoint (upload_server.py)</div> | |
| <pre><span class="kw">@app</span>.<span class="fn">get</span>(<span class="st">"/api/data/{key}"</span>) | |
| <span class="kw">def</span> <span class="fn">get_data</span>(key: str): | |
| fname = DATA_KEY_MAP[key] | |
| <span class="cm"># e.g. "models" β "models.json"</span> | |
| json_file = OUTPUT_DIR / fname | |
| <span class="cm"># e.g. output/models.json</span> | |
| <span class="fn">_tprint</span>(<span class="st">f"[DATA] GET /api/data/{key}"</span> | |
| <span class="st">f" β output/{fname}"</span> | |
| <span class="st">f" β React"</span>) | |
| <span class="kw">with</span> <span class="fn">open</span>(json_file) <span class="kw">as</span> fh: | |
| data = json.<span class="fn">load</span>(fh) | |
| <span class="kw">return</span> <span class="fn">JSONResponse</span>(content=data)</pre> | |
| </div> | |
| <div> | |
| <div class="panel-title">Frontend fetch (DataContext.jsx)</div> | |
| <pre><span class="kw">async function</span> <span class="fn">fetchKey</span>(key) { | |
| <span class="cm">// 1st: try FastAPI backend</span> | |
| <span class="kw">try</span> { | |
| <span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="st">`/api/data/${key}`</span>) | |
| <span class="kw">if</span> (res.ok) <span class="kw">return</span> res.<span class="fn">json</span>() | |
| } <span class="kw">catch</span> (_) {} | |
| <span class="cm">// fallback: Vite static file</span> | |
| <span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="st">`/data/${key}.json`</span>) | |
| <span class="kw">return</span> res.<span class="fn">json</span>() | |
| } | |
| <span class="cm">// Called for all 16 keys</span> | |
| <span class="kw">for</span> (<span class="kw">const</span> key <span class="kw">of</span> KEYS) { | |
| results[key] = <span class="kw">await</span> <span class="fn">fetchKey</span>(key) | |
| } | |
| <span class="cm">// β React context updated</span> | |
| <span class="cm">// β All 9 tabs re-render</span></pre> | |
| <div class="desc" style="margin-top:10px"> | |
| <ul> | |
| <li>Terminal shows: <code>[DATA] GET /api/data/models β output/models.json (15.2 KB) β React</code></li> | |
| <li>Γ 16 calls total</li> | |
| <li>All tabs receive fresh pipeline-computed data</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <hr class="divider"/> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- API SUMMARY --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <div class="step"> | |
| <div class="step-hdr"> | |
| <div class="step-num" style="background:#e65100">β‘</div> | |
| <div><div class="step-title">All API calls β in order of execution</div></div> | |
| </div> | |
| <div class="step-body full"> | |
| <table style="width:100%;border-collapse:collapse;font-size:12px"> | |
| <thead> | |
| <tr style="background:#1a6b2f;color:#fff"> | |
| <th style="padding:8px 12px;text-align:left">Order</th> | |
| <th style="padding:8px 12px;text-align:left">Method</th> | |
| <th style="padding:8px 12px;text-align:left">Endpoint</th> | |
| <th style="padding:8px 12px;text-align:left">Caller</th> | |
| <th style="padding:8px 12px;text-align:left">Python handler</th> | |
| <th style="padding:8px 12px;text-align:left">Response</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr style="background:#f8faf8"><td style="padding:7px 12px;font-weight:700;color:#1a6b2f">1</td><td style="padding:7px 12px">POST</td><td style="padding:7px 12px"><code>/api/upload/reset</code></td><td style="padding:7px 12px">UploadModal (on open)</td><td style="padding:7px 12px"><code>reset_job()</code></td><td style="padding:7px 12px"><code>{ ok: true }</code></td></tr> | |
| <tr><td style="padding:7px 12px;font-weight:700;color:#1a6b2f">2</td><td style="padding:7px 12px">POST</td><td style="padding:7px 12px"><code>/api/upload</code></td><td style="padding:7px 12px">UploadModal (file pick)</td><td style="padding:7px 12px"><code>upload_excel()</code></td><td style="padding:7px 12px"><code>{ ok: true, filename }</code></td></tr> | |
| <tr style="background:#f8faf8"><td style="padding:7px 12px;font-weight:700;color:#1a6b2f">3β¦N</td><td style="padding:7px 12px">GET</td><td style="padding:7px 12px"><code>/api/upload/status</code></td><td style="padding:7px 12px">UploadModal (every 1.5s)</td><td style="padding:7px 12px"><code>get_status()</code></td><td style="padding:7px 12px"><code>{ status, elapsed_s }</code></td></tr> | |
| <tr><td style="padding:7px 12px;font-weight:700;color:#1a6b2f">3β¦N</td><td style="padding:7px 12px">GET</td><td style="padding:7px 12px"><code>/api/upload/logs</code></td><td style="padding:7px 12px">UploadModal (every 1.5s)</td><td style="padding:7px 12px"><code>get_logs()</code></td><td style="padding:7px 12px"><code>{ logs }</code></td></tr> | |
| <tr style="background:#f8faf8"><td style="padding:7px 12px;font-weight:700;color:#1a6b2f">N+1</td><td style="padding:7px 12px">GET</td><td style="padding:7px 12px"><code>/api/data/models</code></td><td style="padding:7px 12px">DataContext (reload)</td><td style="padding:7px 12px"><code>get_data("models")</code></td><td style="padding:7px 12px">models.json contents</td></tr> | |
| <tr><td style="padding:7px 12px;font-weight:700;color:#1a6b2f">N+2</td><td style="padding:7px 12px">GET</td><td style="padding:7px 12px"><code>/api/data/stats</code></td><td style="padding:7px 12px">DataContext (reload)</td><td style="padding:7px 12px"><code>get_data("stats")</code></td><td style="padding:7px 12px">stats.json contents</td></tr> | |
| <tr style="background:#f8faf8"><td style="padding:7px 12px;font-weight:700;color:#1a6b2f">β¦</td><td style="padding:7px 12px">GET</td><td style="padding:7px 12px"><code>/api/data/{key}</code> Γ14 more</td><td style="padding:7px 12px">DataContext (reload)</td><td style="padding:7px 12px"><code>get_data(key)</code></td><td style="padding:7px 12px">JSON for each key</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div style="text-align:center;font-size:11px;color:#aaa;margin-top:24px;padding-top:14px;border-top:1px solid #e0e0e0"> | |
| Genesis AI Β· Price Elasticity Intelligence Β· Dabur Sarson Amla | Generated by Claude Code | |
| </div> | |
| </body> | |
| </html> | |