Secure Bike Parking

Secure Bike Parking: Scoring Where to Lock Up

2 min read Updated July 23, 2026

Where you lock your bike in Portland decides whether it’s there when you get back. I turned that street knowledge into a map.

The Problem

Some racks are theft magnets, some are safe. That knowledge lives in riders’ heads, not anywhere you can look it up.

What I Built

A map-driven guide that scores each parking spot by theft risk, visibility, and street-level imagery, and lets riders add spots of their own. Two surfaces: a public map and an admin moderation portal. It started as a two-person, five-hour hackathon build and grew into a roughly 500-commit platform.

Tech Stack

Languages
  • TypeScript/TSX
  • SQL
  • JavaScript
Runtime & frameworks
  • React 19
  • Vite
  • React Router
  • Cloudflare Pages Functions
  • Cloudflare Workers
Data & storage
  • Cloudflare D1
  • Cloudflare KV
  • Cloudflare R2
  • Cloudflare Queues
  • browser localStorage
Infra & deploy
  • npm workspaces
  • Wrangler
  • Cloudflare Pages preview branches
  • Worker cron and queue consumer
AI / ML
  • Anthropic Claude Haiku 4.5 for NL parsing
  • explanation generation
  • submitted-photo vision judging
Testing
  • Vitest
  • Playwright
  • Node test runner
  • TypeScript project references
  • OpenAPI codegen drift check
Notable libraries
  • Leaflet
  • leaflet.markercluster
  • jose
  • openapi-typescript
  • exifr
  • heic2any
  • fflate

How It Works

The recommendation pipeline fuses several mismatched sources - Nominatim geocoding, OSM and Overpass spots, PortlandMaps crime data, Google Street View and Mapillary imagery - and scores each one. Claude vision judges the spots; a lighter model parses trip requests in plain language. User submissions run through a human-in-the-loop ingest pipeline. Shared OpenAPI contracts are enforced by a Playwright end-to-end gate on every UI change, so two separate front ends can never drift from the API.

Secure Bike Parking: five fused signals, one contract, two apps Five mismatched sources feed the scoring engine: Nominatim geocoding, OSM and Overpass bike-parking spots, PortlandMaps crime data, Google Street View and Mapillary imagery, and rider-submitted photos. apps/api fuses them into one composite score, using Claude Haiku 4.5 to parse trip requests in plain language and Claude vision to judge submitted photos. Below apps/api sits packages/contracts, an OpenAPI 3.1 spec that is the single source of truth for generated types, and a Playwright end-to-end gate that blocks shipping an admin UI change without a passing test. Both apps/web, the public map, and apps/admin, the moderation portal, are generated from that same contract so the two front ends cannot drift apart. apps/admin also runs human-in-the-loop ingest approval, report moderation with a score-impact preview, and an append-only audit log - a rider's submitted photo only affects the score after an operator approves it there. Secure Bike Parking five fused signals, one contract, two apps Nominatim geocoding OSM / Overpass bike-parking spots PortlandMaps crime data Street View + Mapillary imagery Rider photos user-submitted apps/api - composite score Claude Haiku 4.5 parses the trip, plain language Claude vision judges rider-submitted photos weighted score: theft risk + visibility + imagery packages/contracts (OpenAPI 3.1) - single source of truth for generated types Playwright e2e gate - blocks shipping admin UI without a passing test apps/web - public map scored spots on the map trip recommendation card reads the same generated types as admin apps/admin - moderation portal human-in-the-loop ingest approval report moderation, score-impact preview append-only audit log
Five mismatched sources fuse into one score behind apps/api. packages/contracts is the only thing apps/web and apps/admin are required to agree on, and a Playwright gate makes that a build failure, not a promise.

What Made It Hard

Fusing messy, mismatched geodata into one score a rider can trust. Building a moderation pipeline a human can actually review at speed. Keeping a contract-tested API honest across two independent front ends.

The Throughline

A civic tool for my own city: take scattered, hard-won local knowledge and make it usable in ten seconds by anyone. The contract in the middle is what lets it stay honest as it grows.

Status

Live at securebikeparking.org, grown from the hackathon demo into a working platform.