const cache = require('../cache'); async function getRetailData() { const regions = [ { region: 'Noord-Holland', index: 108.2, yoy: 2.1, stores: 8420, onlineShare: 34, footfall: 112 }, { region: 'Zuid-Holland', index: 105.6, yoy: 1.4, stores: 7100, onlineShare: 31, footfall: 105 }, { region: 'Noord-Brabant', index: 103.1, yoy: 0.8, stores: 5200, onlineShare: 28, footfall: 98 }, { region: 'Gelderland', index: 101.4, yoy: 0.3, stores: 3800, onlineShare: 26, footfall: 94 }, { region: 'Utrecht', index: 106.8, yoy: 1.9, stores: 2900, onlineShare: 36, footfall: 108 }, { region: 'Limburg', index: 99.2, yoy: -0.4, stores: 2100, onlineShare: 24, footfall: 88 }, { region: 'Overijssel', index: 100.5, yoy: 0.5, stores: 2400, onlineShare: 25, footfall: 91 }, { region: 'Groningen', index: 98.7, yoy: -0.2, stores: 1200, onlineShare: 22, footfall: 85 } ]; const history = ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'].map((month, i) => ({ month, index: +(100 + Math.sin(i / 2) * 3 + i * 0.15).toFixed(1), online: +(28 + i * 0.8 + Math.sin(i) * 2).toFixed(1), footfall: +(95 + Math.cos(i / 2) * 8).toFixed(1) })); const categories = [ { name: 'Supermarkt', share: 32, growth: 1.2 }, { name: 'Mode', share: 18, growth: -0.8 }, { name: 'Elektronica', share: 14, growth: 2.4 }, { name: 'Bouwmarkt', share: 12, growth: 0.5 }, { name: 'E-commerce', share: 24, growth: 8.2 } ]; const scatter = regions.map(r => ({ region: r.region, stores: r.stores, index: r.index, yoy: r.yoy })); const sorted = regions.sort((a, b) => b.index - a.index); const insights = [ { type: 'ok', label: 'Groei', text: 'Noord-Holland leidt met index 108 — combineer CBS-data met footfall sensoren voor locatiekeuze.' }, { type: 'warn', label: 'Druk', text: 'Limburg negatief YoY — gerichte promoties en regionale voorraadoptimalisatie.' }, { type: 'info', label: 'Omnichannel', text: 'Online share stijgt ~0.8%/maand — dashboard koppelt winkel + web in één view.' }, { type: 'info', label: 'Footfall', text: `Gem. footfall index ${Math.round(sorted.reduce((a, r) => a + r.footfall, 0) / sorted.length)} — piek in Randstad.` }, { type: 'warn', label: 'Mode', text: 'Mode categorie −0.8% — herbalanceer assortiment vs elektronica (+2.4%).' }, { type: 'ok', label: 'Actie', text: 'Scatter-analyse toont waar winkeldichtheid vs index outlier is — expansion ready.' } ]; const contextBlocks = [ { title: 'Waarom Retail Command Center?', body: 'Retailers moeten snel zien waar omzet groeit, footfall daalt en online kanibaliseert — per regio, categorie en kanaal.' }, { title: 'Wat u hier ziet', body: 'Regionale omzetindices, categorie mix, 12-maanden trend, footfall vs online — live refresh elke 2 seconden.' }, { title: 'Technische aanpak Mek-Tech', body: 'CBS StatLine + footfall API + eigen geo-lagen. SSE stream naar browser zonder zware BI stack.' }, { title: 'ROI voor uw organisatie', body: 'Betere locatiekeuze (−15% mislukte openings), snellere category decisions, omnichannel alignment.' }, { title: 'Productie-architectuur', body: 'Data warehouse + footfall sensors → Kafka → dit dashboard. Store-level drill-down optioneel.' }, { title: 'Volgende stap', body: 'Pilot met 3 regio’s + eigen winkeldata overlay — dashboard op maat binnen 3–4 weken.' } ]; return { ts: new Date().toISOString(), refreshMs: 2000, nationalIndex: 104.3, nationalYoy: 1.2, regions: sorted, history, categories, scatter, insights, contextBlocks, totalStores: regions.reduce((a, r) => a + r.stores, 0), avgOnlineShare: +(regions.reduce((a, r) => a + r.onlineShare, 0) / regions.length).toFixed(1), avgFootfall: Math.round(sorted.reduce((a, r) => a + r.footfall, 0) / sorted.length), storesAtRisk: sorted.filter(r => r.yoy < 0).length, context: { pitch: 'Global Retail Operations — regio-intelligence, omnichannel & footfall live.', deployment: 'CBS StatLine · OpenStreetMap · Footfall API' } }; } module.exports = { getRetailData };