Helmet Selfie Discount
A rider taps Verify helmet before unlock, the app captures a live selfie, the system stores it, and the rider gets a discount off the unlock fee. The verification is also fed into the per-trip score as the "helmet verified" signal.
Critical naming note
The helmet selfie table is rider_helmet_selfies. It was renamed from helmet_verifications to avoid a collision with the computer-vision pipeline's own helmet_verifications table (a different concept). If you are reading older internal docs that reference helmet_verifications in the rider-score context, treat that name as out of date - the canonical table is rider_helmet_selfies.
How it works for the rider
- At unlock, the app checks for an active helmet verification on the rider's account.
- If none exists or it has expired, the unlock screen shows a Verify helmet button with the discount preview ("Wear your helmet to save $0.50 on this unlock").
- Tapping it opens the camera. The rider captures themselves in their helmet.
- The selfie uploads to the private storage bucket and a
rider_helmet_selfiesrow is created withpassed_at=now()(v3.0 trusts the live capture; v3.1 will add a CV pass). - The discount is applied immediately at the unlock-fee level. The "helmet verified" signal also feeds the per-trip score, raising it.
What gets stored
| Field | Value |
|---|---|
id | UUID primary key |
customer_uuid | FK to customers |
ride_uuid | Optional FK to the ride at which the selfie was captured |
storage_path | Object path inside the helmet-verifications private storage bucket |
passed_at | Timestamp the verification was accepted (null = pending/failed) |
ttl_expires_at | When this verification stops being valid for new unlocks (default 24h) |
consumed_at | When the discount was applied at unlock (tracks single-use behavior) |
method | self_capture (v3.0) or cv_verified (v3.1+) |
Note again: the table name is rider_helmet_selfies, not helmet_verifications. The storage bucket is helmet-verifications, which is fine - the bucket and the table are deliberately named differently to keep the naming separation intact.
TTL and reuse
- Default TTL is 24 hours. Editable per-subaccount.
- A verified selfie stays usable for multiple unlocks until
ttl_expires_at, unlessconsumed_atis set (single-use mode, off by default). - After
ttl_expires_at + 30 days, the image file is purged from the storage bucket by thehelmet-verification-cleanupcron unless an open dispute references it.
Pricing integration
The discount is applied at the unlock-fee level through the standard pricing pipeline. The bridge function apply-uplift.ts is the single place where rider-score-related pricing modifiers (helmet discount AND price uplift) get computed, so the discount stays consistent with At Risk uplifts and never forks ride-pricing logic.
| Subaccount setting | Effect |
|---|---|
helmet_discount_unlock_fee_cents | Cents off the unlock fee per verified ride |
helmet_ttl_hours | How long a single verification stays valid |
helmet_single_use | If true, a verification is consumed on first unlock |
Beginner riders
A successful helmet selfie during the Beginner tier (rides 1-3) unlocks Silver-tier perks for that rider for the remainder of their Beginner period. The intent: new riders get rewarded for safe behavior immediately, before they have a real rolling score.
What the score formula does with the signal
The "helmet verified" term is worth 10 points out of 100 on the default weights. A rider who is helmet-verified at unlock effectively gets a 10-point head start on the per-trip score.
This is on top of the unlock-fee discount. The two effects are deliberately independent - one is pricing, one is scoring - so you can tune them separately.
Storage and privacy
- Selfies live in a private Supabase storage bucket. Riders cannot share or expose their own selfies; the operator sees them only in the appeal queue when relevant.
- We do not run face recognition. We only check for the presence of a helmet (v3.1+ with CV) or trust the live-capture timestamp (v3.0).
- Images are deleted 30 days after
ttl_expires_atby the cleanup cron unless the verification is linked to an open dispute or appeal. - Levy never shares helmet selfies with insurers, cities, or law enforcement except by warrant.
v3.0 vs v3.1
| Aspect | v3.0 (today) | v3.1 (planned) |
|---|---|---|
| Verification | Trust live capture metadata (timestamp + camera-real-time flag) | Run a CV pass on the image to confirm a helmet is in frame |
| Risk | Slightly easier to spoof | Harder to spoof but adds compute cost |
| Where logic lives | submitHelmetSelfie() | Same entry point, new CV step before passed_at is set |
v3.0 is rolled out today. v3.1 lands once the computer-vision pipeline (project 4) is GA.
API surface
| Endpoint | Purpose |
|---|---|
POST /api/mobile/rider/helmet-selfie | Upload selfie, returns discount token |
GET /api/cron/helmet-verification-cleanup | TTL+30d image purge (background) |
What the operator can do
- View any active or recent verification on a rider's profile (no raw image - only metadata and a signed short-lived URL if you click through).
- Force-expire a verification (e.g. if you suspect spoofing). Writes a
score_audit_logrow. - Adjust the per-subaccount discount and TTL at Settings > Helmet selfie.
What the rider cannot do
- Reuse another rider's selfie - selfies are keyed to
customer_uuid. - Upload a still image from the camera roll - the capture flow forces the live camera.
- Skip the helmet flow if a step-4 throttle cap or step-6 lockout is already active. Helmet verification does not override an active intervention.
Next
See Reward Tiers for how the discount complements tier perks. See Reaction-Time Safe Ride Check for the other pre-unlock verification.