Files
Project_Velocity/app/src/lib/platformMappers.ts
sayan 84e439712c feat/#24 WebOS Completion (#25)
#24 WebOS Completion

Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local>
Reviewed-on: #25
2026-04-18 18:59:04 +05:30

140 lines
4.0 KiB
TypeScript

import type { InventoryPropertySummary } from '@/lib/velocityPlatformClient';
import type { Unit } from '@/types';
function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === 'object' && !Array.isArray(value)
? value as Record<string, unknown>
: {};
}
function asNumber(value: unknown): number | null {
if (typeof value === 'number' && Number.isFinite(value)) {
return value;
}
if (typeof value === 'string') {
const cleaned = value.replace(/[^0-9.]/g, '');
const parsed = Number(cleaned);
if (Number.isFinite(parsed) && parsed > 0) {
return parsed;
}
}
return null;
}
function pickFirstNumber(values: unknown[]): number | null {
for (const value of values) {
const parsed = asNumber(value);
if (parsed !== null) {
return parsed;
}
}
return null;
}
function mapInventoryStatus(status: string): Unit['status'] {
switch ((status ?? '').toLowerCase()) {
case 'active':
return 'available';
case 'under_review':
return 'reserved';
case 'archived':
return 'hold';
default:
return 'hold';
}
}
function inferArea(unitMix: unknown[]): number {
for (const item of unitMix) {
const record = asRecord(item);
const area = pickFirstNumber([
record.avg_area_sqm,
record.avg_area,
record.area_sqm,
record.area,
record.size_sqm,
record.size,
]);
if (area !== null) {
return Math.round(area);
}
}
return 0;
}
function inferPrice(priceBands: unknown[]): number {
for (const item of priceBands) {
const record = asRecord(item);
const price = pickFirstNumber([
record.from,
record.min,
record.price,
record.starting_price,
record.amount,
record.value,
]);
if (price !== null) {
return Math.round(price);
}
}
return 0;
}
function inferType(propertyType: string, unitMix: unknown[]): Unit['type'] {
const normalizedPropertyType = (propertyType ?? '').toLowerCase();
if (normalizedPropertyType.includes('penthouse')) return 'penthouse';
if (normalizedPropertyType.includes('studio')) return 'studio';
if (normalizedPropertyType.includes('1')) return '1br';
if (normalizedPropertyType.includes('2')) return '2br';
if (normalizedPropertyType.includes('3')) return '3br';
for (const item of unitMix) {
const record = asRecord(item);
const raw = String(
record.type ?? record.unit_type ?? record.label ?? record.configuration ?? ''
).toLowerCase();
if (raw.includes('penthouse')) return 'penthouse';
if (raw.includes('studio')) return 'studio';
if (raw.includes('1')) return '1br';
if (raw.includes('2')) return '2br';
if (raw.includes('3')) return '3br';
}
return '2br';
}
function inferFloor(unitMix: unknown[]): number {
for (const item of unitMix) {
const record = asRecord(item);
const floor = pickFirstNumber([record.floor, record.level, record.start_floor]);
if (floor !== null) {
return Math.round(floor);
}
}
return 0;
}
export function mapInventoryPropertySummaryToUnit(
property: InventoryPropertySummary,
index: number,
): Unit {
const location = asRecord(property.location);
const unitMix = Array.isArray(property.unit_mix) ? property.unit_mix : [];
const priceBands = Array.isArray(property.price_bands) ? property.price_bands : [];
const district = typeof location.district === 'string' ? location.district : '';
const city = typeof location.city === 'string' ? location.city : '';
const view = [district, city].filter(Boolean).join(', ') || property.developer_name || 'Location pending';
return {
id: property.property_id,
unitNumber: property.project_name || `Property ${index + 1}`,
type: inferType(property.property_type, unitMix),
floor: inferFloor(unitMix),
area: inferArea(unitMix),
price: inferPrice(priceBands),
status: mapInventoryStatus(property.status),
view,
lastUpdated: new Date(property.ingested_at ?? property.created_at ?? Date.now()),
};
}