No description
Find a file
2026-10-03 19:14:41 +02:00
public Fix bus stops and rebrand (TBD). 2026-10-03 19:14:41 +02:00
scripts Fix bus stops and rebrand (TBD). 2026-10-03 19:14:41 +02:00
src Fix bus stops and rebrand (TBD). 2026-10-03 19:14:41 +02:00
.DS_Store Works :) 2026-10-03 14:29:28 +02:00
.gitignore Scaffold and MVP 2026-10-03 13:33:50 +02:00
17b7999e-e2c0-42fd-8402-b008fb4b392c.pdf Scaffold and MVP 2026-10-03 13:33:50 +02:00
index.html Fix bus stops and rebrand (TBD). 2026-10-03 19:14:41 +02:00
package.json Fix bus stops and rebrand (TBD). 2026-10-03 19:14:41 +02:00
pnpm-lock.yaml Works :) 2026-10-03 14:29:28 +02:00
pnpm-workspace.yaml Works :) 2026-10-03 14:29:28 +02:00
README.md Fix bus stops and rebrand (TBD). 2026-10-03 19:14:41 +02:00
tsconfig.json Scaffold and MVP 2026-10-03 13:33:50 +02:00
vite.config.ts Fix bus stops and rebrand (TBD). 2026-10-03 19:14:41 +02:00

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

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.