276 lines
9.1 KiB
TypeScript
276 lines
9.1 KiB
TypeScript
import type { CategoryId } from "./osm-tags.js";
|
||
|
||
export const PROFILE_IDS = [
|
||
"universal",
|
||
"family",
|
||
"senior",
|
||
"professional",
|
||
"student",
|
||
] as const;
|
||
|
||
export type ProfileId = (typeof PROFILE_IDS)[number];
|
||
|
||
export interface Profile {
|
||
id: ProfileId;
|
||
label: string;
|
||
description: string;
|
||
emoji: string;
|
||
/** Suggested category weight presets (shown in sliders, range 0–2). */
|
||
categoryWeights: Record<CategoryId, number>;
|
||
/**
|
||
* Per-subcategory importance weights (0–1) used when computing grid_scores
|
||
* for this profile. Any subcategory not listed falls back to DEFAULT_SUBCATEGORY_WEIGHT.
|
||
*/
|
||
subcategoryWeights: Record<string, number>;
|
||
}
|
||
|
||
/** Fallback weight for any subcategory not explicitly listed in a profile. */
|
||
export const DEFAULT_SUBCATEGORY_WEIGHT = 0.5;
|
||
|
||
/**
|
||
* Universal subcategory weights — corrected baseline used by the "universal" profile
|
||
* and as the fallback for profile-specific overrides.
|
||
*/
|
||
export const UNIVERSAL_SUBCATEGORY_WEIGHTS: Record<string, number> = {
|
||
// ── service_trade ─────────────────────────────────────────────────────────
|
||
supermarket: 1.0, // primary grocery source — essential daily
|
||
convenience: 0.65, // corner shop fallback, often open late
|
||
pharmacy: 1.0, // health necessity
|
||
restaurant: 0.55, // daily eating / social
|
||
cafe: 0.4, // social / work spot
|
||
bank: 0.35, // branch banking — increasingly digital, visited rarely
|
||
atm: 0.2, // cash access — separate from bank branch
|
||
market: 0.4, // greengrocer, butcher, marketplace — specialty food
|
||
laundry: 0.3, // occasional need
|
||
post_office: 0.4, // administrative, rarely visited
|
||
|
||
// ── transport ─────────────────────────────────────────────────────────────
|
||
train_station: 1.0, // high-speed / intercity rail
|
||
metro: 1.0, // rapid urban transit
|
||
tram_stop: 0.75,// reliable fixed-route urban
|
||
bus_stop: 0.55,// basic PT — variable frequency/quality
|
||
ferry: 0.5, // situational but important where present
|
||
bike_share: 0.5, // active mobility option
|
||
car_share: 0.5, // car access without ownership
|
||
|
||
// ── work_school ───────────────────────────────────────────────────────────
|
||
school: 0.7, // compulsory education — essential for families, not universal
|
||
driving_school: 0.2, // niche, occasional — rarely a walkability factor
|
||
kindergarten: 0.75, // essential for young families
|
||
university: 0.55, // higher education — relevant to students/young adults
|
||
coworking: 0.55, // remote / flexible work
|
||
office: 0.1, // landuse centroid — not a meaningful walkable destination
|
||
|
||
// ── culture_community ─────────────────────────────────────────────────────
|
||
hospital: 1.0, // critical healthcare
|
||
clinic: 0.8, // routine healthcare
|
||
library: 0.7, // education + community hub
|
||
community_center: 0.6, // social infrastructure
|
||
social_services: 0.6, // vital for those who need it
|
||
theatre: 0.5, // cultural enrichment
|
||
place_of_worship: 0.25,// highly variable relevance
|
||
government: 0.4, // administrative
|
||
museum: 0.4, // cultural enrichment
|
||
|
||
// ── recreation ────────────────────────────────────────────────────────────
|
||
park: 1.0, // daily green space
|
||
playground: 0.85,// family essential
|
||
sports_facility: 0.65,// active recreation
|
||
gym: 0.65,// fitness
|
||
green_space: 0.6, // passive nature (forest, meadow, grass)
|
||
swimming_pool: 0.55,// seasonal / activity-specific
|
||
};
|
||
|
||
export const PROFILES: Record<ProfileId, Profile> = {
|
||
universal: {
|
||
id: "universal",
|
||
label: "Universal",
|
||
description: "Balanced across all resident types",
|
||
emoji: "⚖️",
|
||
categoryWeights: {
|
||
service_trade: 1.0,
|
||
transport: 1.0,
|
||
work_school: 1.0,
|
||
culture_community: 1.0,
|
||
recreation: 1.0,
|
||
},
|
||
subcategoryWeights: UNIVERSAL_SUBCATEGORY_WEIGHTS,
|
||
},
|
||
|
||
family: {
|
||
id: "family",
|
||
label: "Young Family",
|
||
description: "Families with young children — schools, playgrounds, healthcare",
|
||
emoji: "👨👩👧",
|
||
categoryWeights: {
|
||
service_trade: 1.2,
|
||
transport: 1.0,
|
||
work_school: 1.5,
|
||
culture_community: 0.9,
|
||
recreation: 1.4,
|
||
},
|
||
subcategoryWeights: {
|
||
...UNIVERSAL_SUBCATEGORY_WEIGHTS,
|
||
// school & childcare — essential for this group
|
||
school: 1.0,
|
||
kindergarten: 1.0,
|
||
// daily shopping
|
||
supermarket: 1.0,
|
||
convenience: 0.7,
|
||
pharmacy: 1.0,
|
||
// healthcare for children
|
||
clinic: 1.0,
|
||
hospital: 0.9,
|
||
// outdoor play
|
||
playground: 1.0,
|
||
park: 1.0,
|
||
green_space: 0.75,
|
||
sports_facility: 0.7,
|
||
// less relevant to families with young children
|
||
cafe: 0.3,
|
||
restaurant: 0.45,
|
||
gym: 0.5,
|
||
university: 0.2,
|
||
coworking: 0.25,
|
||
office: 0.05,
|
||
theatre: 0.3,
|
||
museum: 0.35,
|
||
bar: 0.0,
|
||
},
|
||
},
|
||
|
||
senior: {
|
||
id: "senior",
|
||
label: "Senior",
|
||
description: "Older residents — healthcare, local services, green space",
|
||
emoji: "🧓",
|
||
categoryWeights: {
|
||
service_trade: 1.4,
|
||
transport: 1.1,
|
||
work_school: 0.3,
|
||
culture_community: 1.5,
|
||
recreation: 1.0,
|
||
},
|
||
subcategoryWeights: {
|
||
...UNIVERSAL_SUBCATEGORY_WEIGHTS,
|
||
// healthcare — top priority
|
||
hospital: 1.0,
|
||
clinic: 1.0,
|
||
pharmacy: 1.0,
|
||
social_services: 0.9,
|
||
// daily essentials
|
||
supermarket: 1.0,
|
||
convenience: 0.8,
|
||
// social & community
|
||
community_center: 0.9,
|
||
library: 0.8,
|
||
cafe: 0.55, // social outings matter
|
||
restaurant: 0.45,
|
||
// accessible green space
|
||
park: 1.0,
|
||
green_space: 0.75,
|
||
// transit (many don't drive)
|
||
bus_stop: 0.75,
|
||
tram_stop: 0.8,
|
||
metro: 0.8,
|
||
train_station: 0.75,
|
||
// not relevant
|
||
school: 0.05,
|
||
kindergarten: 0.05,
|
||
university: 0.15,
|
||
coworking: 0.05,
|
||
office: 0.0,
|
||
playground: 0.3,
|
||
gym: 0.5,
|
||
swimming_pool: 0.5,
|
||
},
|
||
},
|
||
|
||
professional: {
|
||
id: "professional",
|
||
label: "Young Professional",
|
||
description: "Working adults without children — transit, fitness, dining",
|
||
emoji: "💼",
|
||
categoryWeights: {
|
||
service_trade: 1.0,
|
||
transport: 1.5,
|
||
work_school: 0.7,
|
||
culture_community: 0.9,
|
||
recreation: 1.1,
|
||
},
|
||
subcategoryWeights: {
|
||
...UNIVERSAL_SUBCATEGORY_WEIGHTS,
|
||
// transit — high priority for commuters
|
||
metro: 1.0,
|
||
train_station: 1.0,
|
||
tram_stop: 0.85,
|
||
bus_stop: 0.65,
|
||
bike_share: 0.7,
|
||
// fitness & dining
|
||
gym: 0.9,
|
||
sports_facility: 0.7,
|
||
swimming_pool: 0.65,
|
||
restaurant: 0.75,
|
||
cafe: 0.7,
|
||
// daily essentials
|
||
supermarket: 1.0,
|
||
pharmacy: 0.85,
|
||
// work
|
||
coworking: 0.85,
|
||
// less relevant
|
||
school: 0.1,
|
||
kindergarten: 0.05,
|
||
playground: 0.15,
|
||
university: 0.35,
|
||
office: 0.05,
|
||
social_services: 0.4,
|
||
},
|
||
},
|
||
|
||
student: {
|
||
id: "student",
|
||
label: "Student",
|
||
description: "Students — university, library, cafés, transit, budget services",
|
||
emoji: "🎓",
|
||
categoryWeights: {
|
||
service_trade: 0.9,
|
||
transport: 1.4,
|
||
work_school: 1.5,
|
||
culture_community: 1.2,
|
||
recreation: 0.8,
|
||
},
|
||
subcategoryWeights: {
|
||
...UNIVERSAL_SUBCATEGORY_WEIGHTS,
|
||
// education
|
||
university: 1.0,
|
||
library: 1.0,
|
||
coworking: 0.9,
|
||
// social
|
||
cafe: 0.9,
|
||
restaurant: 0.65,
|
||
// transit
|
||
metro: 1.0,
|
||
train_station: 0.9,
|
||
tram_stop: 0.8,
|
||
bus_stop: 0.7,
|
||
bike_share: 0.85,
|
||
// budget shopping
|
||
supermarket: 0.9,
|
||
convenience: 0.75,
|
||
pharmacy: 0.75,
|
||
// culture
|
||
museum: 0.55,
|
||
theatre: 0.5,
|
||
community_center: 0.6,
|
||
// recreation
|
||
gym: 0.75,
|
||
sports_facility: 0.65,
|
||
park: 0.75,
|
||
// not relevant
|
||
school: 0.05,
|
||
kindergarten: 0.05,
|
||
office: 0.05,
|
||
playground: 0.2,
|
||
},
|
||
},
|
||
};
|