Files
Siro/backend/pricing-engine/src/analysis/zone.ts
T

83 lines
2.6 KiB
TypeScript

import { RideSample, ZoneAnalysis } from './types';
import { assignZone, classifyZoneType } from './clustering';
/**
* Analyze pricing by geographical zone (2.5km grid).
* Groups samples into zones and computes per-zone statistics.
*/
export function analyzeByZone(samples: RideSample[]): ZoneAnalysis[] {
const zoneMap = new Map<string, RideSample[]>();
for (const s of samples) {
// Use start location for zone assignment
const zone = assignZone(s.startLat, s.startLng);
if (!zoneMap.has(zone)) zoneMap.set(zone, []);
zoneMap.get(zone)!.push(s);
}
const results: ZoneAnalysis[] = [];
for (const [zoneKey, zoneSamples] of zoneMap) {
if (zoneSamples.length < 3) continue;
const ppkValues = zoneSamples.map(s => s.ppk);
const avgPpk = Math.round(
(ppkValues.reduce((a, b) => a + b, 0) / ppkValues.length) * 1000
) / 1000;
// Count by tier — thresholds depend on currency scale
const tierCounts: Record<string, number> = {};
const sample = zoneSamples[0];
const isHighDenom = sample.countryCode === 'SY' || sample.countryCode === 'IQ';
const econThreshold = isHighDenom ? 15 : 0.35;
const stdThreshold = isHighDenom ? 40 : 0.55;
for (const s of zoneSamples) {
const tier =
s.ppk < econThreshold ? 'economy' :
s.ppk < stdThreshold ? 'standard' : 'premium';
tierCounts[tier] = (tierCounts[tier] || 0) + 1;
}
const [latStr, lngStr] = zoneKey.split(',');
results.push({
zoneKey,
centerLat: parseFloat(latStr),
centerLng: parseFloat(lngStr),
samples: zoneSamples,
avgPpk,
tierDistribution: tierCounts,
});
}
return results.sort((a, b) => a.avgPpk - b.avgPpk);
}
/**
* Analyze pricing by zone type (centre, mid, suburb, outskirts).
* Passes countryCode to classifyZoneType so the correct city center is used.
*/
export function analyzeByZoneType(
samples: RideSample[]
): Array<{ zoneType: string; avgPpk: number; sampleCount: number; avgPrice: number }> {
const typeMap = new Map<string, number[]>();
for (const s of samples) {
// Pass countryCode so we use the correct city center (not always Amman)
const zoneType = classifyZoneType(s.startLat, s.startLng, s.countryCode);
if (!typeMap.has(zoneType)) typeMap.set(zoneType, []);
typeMap.get(zoneType)!.push(s.ppk);
}
return Array.from(typeMap.entries())
.map(([zoneType, ppks]) => ({
zoneType,
avgPpk: Math.round(
(ppks.reduce((a, b) => a + b, 0) / ppks.length) * 1000
) / 1000,
sampleCount: ppks.length,
avgPrice: 0, // calculated below if needed
}))
.sort((a, b) => a.avgPpk - b.avgPpk);
}