import React, { useState, useMemo } from 'react' import { useData } from '../DataContext.jsx' import { fmtCr, fmtL } from '../utils.js' function computeImpacts(grains, dabP, cp) { return grains.map(m => { const ownPct = m.OwnE * (dabP / 100) const compPct = Object.entries(m.CompCoefs || {}).reduce((s, [c, v]) => s + v * ((cp[c] || 0) / 100), 0) const totPct = ownPct + compPct const baseVol = m.BaseVol25 || 0 const pml = m.PricePerMl || 0 const gc = (m.GC || 0) / 100 const baseVal = baseVol * pml * 1000 const newVol = baseVol * (1 + totPct) const newPml = pml * (1 + dabP / 100) const newVal = newVol * newPml * 1000 const volL = newVol - baseVol const valRs = newVal - baseVal const gcRs = newVal * gc - baseVal * gc return { m, totPct, volL, valRs, gcRs, vs: m.ValShare || 0 } }) } // 'DABUR SARSON AMLA' -> 'Dabur Sarson Amla' function toLabel(brand) { return brand.toLowerCase().replace(/\b\w/g, c => c.toUpperCase()) } export default function Simulation() { const { models, stats } = useData() const brandLabel = stats?.brand || '' // Derive competitors dynamically from CompCoefs in models data const competitors = useMemo(() => { if (!models) return [] const compSet = new Set() models.forEach(m => Object.keys(m.CompCoefs || {}).forEach(c => compSet.add(c))) return [...compSet].sort() }, [models]) const [fch, setFch] = useState('ALL') const [frg, setFrg] = useState('ALL') const [fpack, setFpack] = useState('ALL') const [dP, setDP] = useState(5) // compResp initialised dynamically once competitors are known const [compResp, setCompResp] = useState({}) // Keep compResp keys in sync with competitors list const syncedCompResp = useMemo(() => { const obj = {} competitors.forEach(c => { obj[c] = compResp[c] ?? 0 }) return obj }, [competitors, compResp]) const grains = useMemo(() => { if (!models) return [] return models.filter(m => (fch === 'ALL' || m.Channel === fch) && (frg === 'ALL' || m.Region === frg) && (fpack === 'ALL' || m.Pack === fpack) ) }, [models, fch, frg, fpack]) const scenarios = useMemo(() => { if (!grains.length) return [] const allZero = Object.fromEntries(competitors.map(c => [c, 0])) const allDP = Object.fromEntries(competitors.map(c => [c, dP])) const allHalf = Object.fromEntries(competitors.map(c => [c, dP / 2])) return [ { num: 1, cls: 'sn1', name: `${brandLabel} only`, desc: 'Competitors hold prices', dabP: dP, cp: allZero }, { num: 2, cls: 'sn2', name: 'All comps match', desc: 'All competitors match %', dabP: dP, cp: allDP }, { num: 3, cls: 'sn3', name: 'Custom response', desc: 'Individual responses', dabP: dP, cp: syncedCompResp }, { num: 4, cls: 'sn4', name: '50% comp response', desc: 'Comps at half focal %', dabP: dP, cp: allHalf }, ].map(sc => { const impacts = computeImpacts(grains, sc.dabP, sc.cp) const vsT = grains.reduce((s, m) => s + (m.ValShare || 0), 0) const wtdPct = vsT > 0 ? impacts.reduce((s, x) => s + x.totPct * (x.vs / vsT), 0) : 0 const totVolL = impacts.reduce((s, x) => s + x.volL, 0) const totValRs = impacts.reduce((s, x) => s + x.valRs, 0) const totGcRs = impacts.reduce((s, x) => s + x.gcRs, 0) return { ...sc, impacts, wtdPct, totVolL, totValRs, totGcRs } }) }, [grains, dP, syncedCompResp, competitors, brandLabel]) if (!models) return