GdzieWjade/README.md

128 lines
8.8 KiB
Markdown

# GdzieWjade
A PWA that tells wheelchair users and parents with strollers whether a place or route in Kraków fits
**their** limits, and shows how we know: every fact carries a source, a date and a reliability level.
Missing data is never shown as "accessible".
Prototype for the "Kraków bez barier" hackathon challenge (the app name is one constant: `APP_NAME` in `src/config.ts`). Demo area: Wawel, Old Town and Kazimierz out east to TAURON Arena (≈19 km²).
## Run
```bash
pnpm install
pnpm dev # or: pnpm build && pnpm preview
pnpm test # unit tests (parsing, verdicts, routing, reach, moderation)
pnpm smoke # end-to-end flows against `pnpm preview` on :4173
pnpm a11y # axe-core WCAG 2.2 AA scan of every main view
pnpm data:build # re-fetch OSM + ZTP data into public/data/kk-snapshot.json
```
Demo sign-in has no passwords: pick **Anna** (wheelchair), **Piotr** (stroller) or **Maria** (city
official, unlocks the City panel). Everything is stored in the browser, per account.
## What it does
| For residents and visitors | For city staff (role "Pracownik urzędu") |
| --- | --- |
| Guided profile: persona, equipment presets, limits with plain-language hints and tooltips, live preview of how many places fit | Data-quality KPIs computed from open sources, no database kept by the city |
| Fit report per place (entrance, steps, ramp/lift, width, surface, toilet, benches, baby changing) | Gaps by place type; operator-feed gap for transit stops |
| Step-free routing that avoids steps, high kerbs, steep and narrow sections, optionally cobbles; text route and barrier list | "Fix first" ranking of crossings with high or unknown kerb height, weighted by places served |
| Barrier-free reach: what you can get to in 5/10/15 min | Report moderation queue: approve → "verified by the city office" |
| Printable access card per place | Anonymous demand signals: where people look but data is missing |
| Report a correction (kept apart from OSM data, marked unverified) | CSV / GeoJSON export of the computed data |
| Saved places, text size, high contrast, reduced motion, PL/EN | |
## Routes, guidance and evidence
- **Route options:** the shortest path comes first (difficulties counted, not avoided), then variants that trade extra
distance for fewer difficulties. Each shows distance, time, overhead vs shortest, and counts of barriers / cautions /
unknown points; the best one is flagged "Recommended" and others are drawn faintly on the map.
- **Guide me:** live GPS guidance with turn prompts and barrier warnings at the top (optional voice, screen kept awake,
off-route re-routing). "Simulate GPS" walks the route for demos. Or hand the route to Google Maps / Apple Maps: waypoints
along our route keep their path close to ours, but those apps do not know the user's limits.
- **Map layers:** street map or satellite (Esri World Imagery; check its terms before production use).
- **Report evidence:** photo (EXIF stripped, downscaled), slope measured with the phone's tilt sensors, kerb height measured on
the photo against a reference object (card / A4 / ruler), or typed in. **LiDAR is not reachable from a web app** (no
LiDAR or WebXR on iOS Safari); `Measurement.method = 'lidar'` is reserved for a native iOS companion.
- **Automatic first look:** on-device CLIP zero-shot (free, private, no API key) labels the photo; rules combine it with the
claim and measurement into validity, consistency, severity and a triage lane (fast-track / review / likely reject).
It only orders the city queue. If the model cannot load the app says "No Credits left". Hosted free vision APIs
need a key (Pollinations anonymous is text-only), so none are wired in.
## Public transport
The route form has a **walking / transit** switch. In transit mode the app plans up to one transfer from the ZTP Kraków
timetable (static GTFS, `public/data/kk-transit.json`, built by `pnpm transit:build`):
- every walking leg (to the first stop, between stops when changing, from the last stop) is routed with the user's limits, with
barriers counted per leg; stops that cannot be reached for the profile are dropped;
- each connection shows departure and arrival times, delays, the next departures of the line and stop accessibility from OSM;
- a stop's detail page lists its next departures; other places list the stops within 400 m.
**Live delays** come from ZTP's GTFS-Realtime feeds (`TripUpdates_A/T.pb`, `ServiceAlerts_A/T.pb`, decoded in `src/lib/gtfsrt.ts`, no
dependency). ZTP sends no CORS headers, so the app requests `rt/*`, which `pnpm dev` and `pnpm preview` proxy to
`gtfs.ztp.krakow.pl`. A static production host needs the same reverse-proxy rule (nginx: `location /rt/ { proxy_pass https://gtfs.ztp.krakow.pl/; }`).
Without it the app says "timetable only" and shows no delays; it never pretends a trip is on time when no prediction exists.
The ZTP timetable has **no low-floor flag** (`wheelchair_accessible` is 0 for all 57k trips in the area) and the stops' `wheelchair_boarding`
is empty, so the app cannot promise an accessible vehicle and says so.
## Data and honesty rules
- **OpenStreetMap** (ODbL) via Overpass: places, steps, ramps, kerbs, surfaces, benches, toilets.
- **ZTP Kraków GTFS** (`stops.txt`, `wheelchair_boarding`): 82 stops in the area. The operator's field says
"no information" for **all** of them; OSM contributors describe 44. The app shows both and the City panel
points at the gap. Licence of the ZTP feed must be confirmed at otwartedane.um.krakow.pl before reuse.
- Reliability levels: `verified` (survey date ≤ 24 months, or approved by the city office) · `mapped`
(OSM edit ≤ 24 months) · `stale` (older) · `inferred` (from nearby objects) · `user` (unverified report) ·
`none`. The date shown is the last OSM edit of the object unless a survey date exists, so we never call
edit-date data "confirmed".
- Absence of data → "No data". User report vs OSM disagreement → "Conflicting data" with both sides shown.
Refresh failure → last snapshot date plus a warning (demo switch in *Data → Demo controls*).
- Four fictional places and three sample reports are labelled **DEMO** and can be hidden in Settings.
## Architecture
```
scripts/build-data.mjs adapters: Overpass (OSM) + GTFS (ZTP) → public/data/kk-snapshot.json
src/lib/osm.ts tag interpretation (surface, incline, width, kerb)
src/lib/assess.ts fact model {status, source, confidence, date} + verdict per profile
src/lib/route.ts A* over the pedestrian graph, profile-aware penalties
src/lib/reach.ts time-limited Dijkstra for barrier-free reach
src/lib/civic.ts city-panel aggregates, hotspot ranking, export
src/components/* USWDS 3 UI; MapView = MapLibre GL (raster OSM tiles)
```
Acquisition and presentation are separate: a new source is a new adapter producing the same fact shape; a
new city is a bounding box in `build-data.mjs` plus its adapters. Configuration lives in code constants today;
moving it to a per-city JSON is the first scaling step.
## Business model and go-live (proposal)
- Free for residents. Revenue from B2B: venue accessibility widget/report for hotels, event organisers and
property managers; accessibility API for booking and travel apps; roll-out package for other cities.
- Owner of the product: the operating entity (not UMK). Costs: static hosting, scheduled Overpass/GTFS pulls,
report moderation.
- Month-one launch: host static build + nightly data job; real SSO for staff; server-side reports with
rate limiting and two-person confirmation before pushing to OSM; replace OSM tile server with a paid or
self-hosted tile provider; confirm every dataset licence.
## Privacy and security
No accounts on a server, no analytics, no health questions — only barrier preferences. Profile, saved places
and reports are in `localStorage`. Geolocation (optional) never leaves the browser. HTTPS only.
The demo sign-in is not security: it only selects a local profile and role.
## Accessibility status
Target WCAG 2.2 AA. Automated: axe-core finds 0 violations on login, places, place detail, route, profile,
City panel, data pages (`pnpm a11y`). Built in: full keyboard operation, focus management on view changes,
information never by colour alone, tooltips dismissible with Esc, text size and contrast settings, text
equivalents for map content. **Known limits:** choosing a place by clicking a map pin is not keyboard
accessible (use the list); no manual screen-reader pass on every platform yet; high-contrast mode is basic.
## Known limitations
Demo area only. Reports and moderation are local to one browser. Kerb height is known for only part of the
crossings and entrance width is almost never mapped. Walking times are estimates. Routing ignores traffic
lights and temporary obstacles. OSM tile servers are for demos only.