export type Lang = 'pl' | 'en'; export type ProfileKind = 'wheelchair' | 'stroller'; /** Preferences only — never a diagnosis. Stored on the device. */ export interface Profile { kind: ProfileKind; /** Highest kerb the user can cross, cm. */ maxKerbCm: number; /** Narrowest passage, metres. */ minWidthM: number; /** Steepest slope, percent. */ maxInclinePct: number; avoidCobbles: boolean; needToilet: boolean; needRest: boolean; needChanging: boolean; } export type PlaceCategory = 'food' | 'culture' | 'shop' | 'stay' | 'health' | 'services' | 'toilets' | 'elevator' | 'transit'; export interface Place { id: string; lat: number; lon: number; name: string | null; cat: PlaceCategory; kind: string | null; /** ISO date of the last edit of the OSM element (not necessarily of the access tags). */ ts: string | null; tags: Record; /** Fictional place that exists only to demonstrate data states. */ demo?: boolean; /** City-feed extras (GTFS stops). */ ext?: { gtfsWheelchair: number | null; feedDate: string | null; mode: 'tram' | 'bus'; osmMatch: string | null }; } export interface Way { id: number; hw: string; ts: string | null; nodes: number[]; name?: string; surface?: string; smooth?: string; incline?: string; width?: string; stepCount?: number; ramp?: string; rampWheelchair?: string; rampStroller?: string; handrail?: string; footway?: 'crossing' | 'sidewalk'; tunnel?: boolean; bridge?: boolean; wheelchair?: string; checked?: string; } export interface Snapshot { meta: { fetchedAt: string; bbox: [number, number, number, number]; source: string; license: string; endpoint: string; cityData: { name: string; url: string; feedDates: { mode: string; version: string | null }[]; license: string }; counts: Record; }; places: Place[]; toilets: Place[]; elevators: Place[]; /** [lat, lon, ISO date | null] */ benches: [number, number, string | null][]; nodes: [number, number][]; ways: Way[]; kerbs: Record; } // ---------------- assessment ---------------- export type Status = 'ok' | 'warn' | 'fail' | 'unknown' | 'conflict'; /** * How far a piece of information can be trusted. Ordered strongest first. * - verified: dated survey (check_date / survey:date) within 24 months * - mapped: present in OSM, edited within 24 months, no survey date * - stale: present in OSM but not touched for over 24 months * - inferred: derived from nearby geometry, not stated for this place * - user: unverified report from a user of this app * - none: nothing is known */ export type Confidence = 'verified' | 'mapped' | 'stale' | 'inferred' | 'user' | 'none'; export type SourceKind = 'osm-tag' | 'osm-geometry' | 'user-report' | 'city-review' | 'city-gtfs' | 'demo' | 'none'; export type FactKey = 'entrance' | 'steps' | 'ramp' | 'width' | 'surface' | 'toilet' | 'rest' | 'changing'; /** Translatable message: key into the i18n dictionary plus parameters. */ export interface Msg { k: string; p?: Record; } export interface Fact { key: FactKey; status: Status; headline: Msg; detail?: Msg; source: SourceKind; confidence: Confidence; /** ISO date this information dates from, when known. */ date: string | null; /** Required for this profile, i.e. it can decide the verdict. */ required: boolean; /** Second opinion that disagrees (user report vs OSM). */ alt?: { headline: Msg; source: SourceKind; confidence: Confidence; date: string | null }; } export type Verdict = 'match' | 'partial' | 'fail' | 'incomplete' | 'conflict'; export interface Assessment { placeId: string; verdict: Verdict; facts: Fact[]; } export type ReportAttr = 'entrance' | 'steps' | 'width' | 'toilet' | 'ramp'; export type MeasureMethod = 'photo-ref' | 'inclinometer' | 'manual' | 'lidar'; /** A number measured on site. `lidar` is reserved for a future native iOS companion app. */ export interface Measurement { kind: 'kerb' | 'incline'; value: number; unit: 'cm' | 'pct'; method: MeasureMethod; } export type ReportValue = 'yes' | 'limited' | 'no' | number; export interface UserReport { id: string; placeId: string; attr: ReportAttr; /** yes/limited/no, or entrance width in cm. */ value: ReportValue; note?: string; createdAt: string; /** Who wrote it (demo account id). */ authorId?: string; /** City moderation outcome; absent = pending. */ status?: 'pending' | 'accepted' | 'rejected'; reviewedAt?: string; reviewedBy?: string; /** Seeded example, not a real user report. */ demo?: boolean; /** Photo stored in IndexedDB under this id (EXIF stripped, downscaled). */ photoId?: string; /** Measured on site, if any. */ measure?: Measurement; /** Automated first look; a human always decides. */ vetting?: VetState; } // ---------------- AI triage ---------------- export type EvidenceFlag = 'blurry' | 'face' | 'text' | 'unrelated' | 'no-photo'; export interface Vetting { provider: string; at: string; validity: 'valid' | 'unclear' | 'invalid'; /** Does the photo agree with what the reporter claims? */ consistency: 'supports' | 'contradicts' | 'neutral'; severity: 'critical' | 'major' | 'minor' | 'none'; accessibilityClass: 'inaccessible' | 'difficult' | 'accessible' | 'unknown'; triage: 'fast-track' | 'review' | 'likely-reject'; confidence: number; flags: EvidenceFlag[]; /** Top photo labels with probabilities (0..1). */ top: { key: string; score: number }[]; reasons: Msg[]; } export type VetState = | { state: 'running' } | { state: 'done'; result: Vetting } | { state: 'failed'; reason: 'no-credits' | 'error' };