2026-10-03 13:33:50 +02:00
|
|
|
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<string, string>;
|
|
|
|
|
/** 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<string, number>;
|
|
|
|
|
};
|
|
|
|
|
places: Place[];
|
|
|
|
|
toilets: Place[];
|
|
|
|
|
elevators: Place[];
|
|
|
|
|
/** [lat, lon, ISO date | null] */
|
|
|
|
|
benches: [number, number, string | null][];
|
|
|
|
|
nodes: [number, number][];
|
|
|
|
|
ways: Way[];
|
|
|
|
|
kerbs: Record<string, string>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------- 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<string, string | number>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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';
|
2026-10-03 14:29:28 +02:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2026-10-03 13:33:50 +02:00
|
|
|
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;
|
2026-10-03 14:29:28 +02:00
|
|
|
/** 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;
|
2026-10-03 13:33:50 +02:00
|
|
|
}
|
2026-10-03 14:29:28 +02:00
|
|
|
|
|
|
|
|
// ---------------- 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 }
|
2026-10-03 15:02:13 +02:00
|
|
|
| { state: 'failed'; reason: 'no-credits' | 'error' | 'skipped' };
|