Initial commit — Mek-Tech Live Labs Showcase v1.0
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
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 };
|
||||
Reference in New Issue
Block a user