Skip to content

Latest commit

Β 

History

65 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

PhilAIsion

An AI-powered civic agent that lets Philadelphia residents access city services by just talking β€” through a phone call, a chat, or a $50 Raspberry Pi kiosk. Built for Drexel CodeFest 2026.


The Problem

Right now in Philadelphia, getting help from the city means navigating dozens of websites, PDFs, and forms β€” often in perfect English. And access isn't equal:

  • 84% of households have high-speed internet
  • 67% of older adults and Spanish-speaking residents do
  • 71% of low-income households do

That's tens of thousands of people effectively locked out of services that already exist.

What PhilAIsion Does

Walk up, call, or open a browser. Say something like:

"My trash hasn't been picked up in two weeks."

PhilAIsion's AI agent will:

  1. Understand the situation in any of 10 languages
  2. Search Philadelphia's 700+ city services and 122 government forms
  3. Take action β€” file the 311 report against the live PublicStuff API, draft the legal complaint, email the legal aid org, or check benefits eligibility
  4. Speak back with natural-sounding voice (ElevenLabs) so literacy isn't a barrier

It works without broadband, without a smartphone, and in any language. Instead of telling residents what to do β€” it does it for them.


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                          USER ENTRY POINTS                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   Phone (Vapi)    Web App (React)    Raspberry Pi Kiosk          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              FRONTEND  Β·  React 18 + Vite + i18next               β”‚
β”‚  ─────────────────────────────────────────────────────────────   β”‚
β”‚  β€’ Web Speech API (STT β€” 10 languages)                           β”‚
β”‚  β€’ ElevenLabs TTS via /voice proxy (with browser fallback)       β”‚
β”‚  β€’ Supabase Auth (Google OAuth + anonymous)                      β”‚
β”‚  β€’ SSE streaming chat with tool-call cards                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           BACKEND  Β·  Node + Express + TypeScript + Prisma        β”‚
β”‚  ─────────────────────────────────────────────────────────────   β”‚
β”‚                                                                   β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚           AI AGENT  (OpenAI GPT-4o + tool use)           β”‚   β”‚
β”‚   β”‚  ────────────────────────────────────────────────────    β”‚   β”‚
β”‚   β”‚   14 callable tools β€” file_city_report, draft_document,  β”‚   β”‚
β”‚   β”‚   find_legal_help, immigration_screening, check_benefits β”‚   β”‚
β”‚   β”‚   web_lookup, escalate_emergency, …                      β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                                   β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚  RAG  Β·  text-embedding-3-small + cosine similarity      β”‚   β”‚
β”‚   β”‚  ────────────────────────────────────────────────────    β”‚   β”‚
β”‚   β”‚   122 phila.gov forms + 712 services indexed in-memory   β”‚   β”‚
β”‚   β”‚   Top-4 semantic match for "I need to…" queries          β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                                   β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚              EXTERNAL INTEGRATIONS                        β”‚   β”‚
β”‚   β”‚  ────────────────────────────────────────────────────    β”‚   β”‚
β”‚   β”‚   β€’ PublicStuff API (live 311 filing)                    β”‚   β”‚
β”‚   β”‚   β€’ CARTO public_cases_fc (duplicate-report detection)   β”‚   β”‚
β”‚   β”‚   β€’ Gmail API (legal-aid intake email)                   β”‚   β”‚
β”‚   β”‚   β€’ OpenAI Whisper (optional server-side STT)            β”‚   β”‚
β”‚   β”‚   β€’ ElevenLabs (TTS proxy)                               β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         DATA  Β·  Supabase Postgres + Prisma  +  Static JSON       β”‚
β”‚  ─────────────────────────────────────────────────────────────   β”‚
β”‚   User Β· Case Β· CaseAction      orgs.json Β· laws.json            β”‚
β”‚   philaDocumentsCatalog (700+)   philaServicesCatalog (712+)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Tech Stack

Frontend (/frontend)

Category Library Version
Framework React 18.3.1
Build tool Vite 5.3.1
Routing react-router-dom 6.30.3
Auth @supabase/supabase-js 2.103.0
HTTP axios 1.7.2
Animation framer-motion 12.38.0
Icons lucide-react 1.8.0
Markdown react-markdown 10.1.0
i18n i18next + react-i18next 26.0.4 / 17.0.2
Voice (STT) Web Speech API (native) β€”

Backend (/backend)

Category Library Version
Runtime Node.js + Express 4.19.0
Language TypeScript 6.0.2 (ES2020, strict)
ORM Prisma 7.7.0 (PostgreSQL adapter)
LLM OpenAI SDK 6.34.0
Anthropic @anthropic-ai/sdk 0.39.0
Embeddings OpenAI text-embedding-3-small β€”
Voice (TTS) ElevenLabs eleven_turbo_v2_5 β€”
Voice (STT) OpenAI whisper-1 (optional) β€”
Web scraping cheerio 1.2.0
HTTP axios 1.7.0

Infrastructure

Layer Service
Database Supabase Postgres (Prisma-managed schema)
Auth Supabase Auth β€” Google OAuth + anonymous sessions
Email Gmail API (OAuth2 refresh token)
Hosting Vercel (frontend) + Render/Fly.io (backend)
Kiosk Raspberry Pi 3 + Raspberry Pi OS Bookworm + Chromium kiosk mode

The AI Agent β€” 14 Tools

The agent uses OpenAI GPT-4o with structured tool calls. Every call is logged to CaseAction so users (and the city) can audit what was done on their behalf.

Tool What it does
file_city_report Submits 311 reports to PublicStuff (POTHOLE, ILLEGAL-DUMPING, GRAFFITI, NOISE, SANITATION, HOUSING-VIOLATION, etc.)
send_org_email Sends an intake email to a legal aid / social services org via Gmail API
draft_document Generates a legal complaint, demand letter, or formal notice
find_resources Matches user need (food, shelter, health, legal) to a curated set of ~50 Philadelphia nonprofits
explain_rights Cites relevant PA / Philadelphia tenant, housing, immigration, civic law from laws.json
check_benefits Eligibility screen against 20+ benefits (SNAP, Medicaid, LIHEAP, WIC, …) by household factors
find_legal_help Routes to legal-aid org by domain + auto-sends intake email
immigration_screening Maps user situation to pathway (asylum / DACA / TPS / U-visa) + provider lookup
find_jobs_training Workforce-development program matching
civic_participation Voter registration, expungement, elected official lookup
search_city_services Semantic search over 712+ phila.gov services
web_lookup Fetches and extracts info from a specific URL (Cheerio)
web_search Generic web search fallback
escalate_emergency Hot-routes no-heat / homelessness / DV / medical / utility-shutoff to the right hotline

RAG β€” How Form & Service Matching Works

When a user asks "How do I appeal my property taxes?", we don't pattern-match keywords β€” we use semantic vector search.

  1. Indexing (one-time, ~$0.01) β€” On boot, the backend embeds all 122 forms in philaDocumentsCatalog and 712 services in philaServicesCatalog using OpenAI text-embedding-3-small. Vectors are cached in-memory.
  2. Query β€” User text is embedded on the fly.
  3. Cosine similarity β€” Top-4 matches above the 0.35 threshold are returned. Below threshold β†’ "no good match, let me ask differently."
  4. Inline cards β€” Frontend renders matches as clickable cards under the assistant's reply.

Endpoint: GET /chat/find-forms?q=<query>&limit=4

This is the same mechanism used by search_city_services from inside the agent loop, so the LLM can pull relevant forms mid-conversation without hard-coding categories.


Voice Pipeline

Speech-to-Text

  • Web Speech API in the browser β€” free, ~10 languages supported by Chrome
  • Optional fallback: POST /voice/transcribe with Whisper for languages Chrome doesn't cover

Text-to-Speech

  • Backend proxies POST /voice/tts β†’ ElevenLabs eleven_turbo_v2_5
    • Default voice: Sarah (EXAVITQu4vr4xnSDxMaL)
    • Settings: stability 0.45, similarity_boost 0.75, style 0.15
    • Max 1200 chars per request β€” long replies are split into sentences and queued
  • Audio cached as Blob URLs per session (no re-billing for repeats)
  • Browser speechSynthesis fallback if /voice/health fails β€” chunked at sentence boundaries to avoid Chrome's known bug where utterances longer than ~200 chars silently restart

Streaming

  • Chat replies stream as Server-Sent Events: token, tool_start, tool_done, done, error
  • TTS hook buffers tokens into sentences and queues each sentence for ElevenLabs as soon as a ., !, or ? arrives β€” so the user starts hearing the first sentence while the LLM is still writing the third

Database Schema

model User {
  id                 String   @id @default(cuid())
  createdAt          DateTime @default(now())
  updatedAt          DateTime @updatedAt
  name               String?
  email              String?  @unique
  phone              String?
  preferredLanguage  String?  @default("en")
  neighborhood       String?
  provider           String?  // 'google' | 'anonymous'
  profile            Json?    // {address, demographics, household, ...}
  cases              Case[]
}

model Case {
  id          String        @id @default(cuid())
  userId      String?       // nullable β€” supports anonymous kiosk users
  user        User?         @relation(fields: [userId], references: [id])
  transcript  String        // full conversation as text
  summary     Json          // structured summary {issue, location, urgency}
  domains     String[]      // ['housing', 'legal', '311']
  severity    String        // 'low' | 'medium' | 'high'
  status      String        // 'open' | 'in_progress' | 'resolved' | 'rejected'
  language    String?
  actions     CaseAction[]
  createdAt   DateTime      @default(now())
}

model CaseAction {
  id       String   @id @default(cuid())
  caseId   String
  case     Case     @relation(fields: [caseId], references: [id])
  payload  Json     // {tool, input, result} β€” every tool call is auditable
  createdAt DateTime @default(now())
}

API Surface (selected)

Method Path Purpose
POST /chat One-shot agent call, returns full reply + tool actions
POST /chat/stream SSE-streamed agent reply (tokens + tool events)
POST /chat/reset Clear server-side session state
GET /chat/find-forms?q=&limit= Semantic form match (RAG)
GET /chat/duplicate-check?service=&address=&radius=&days= CARTO check before filing 311
POST /voice/tts ElevenLabs proxy β†’ audio/mpeg
GET /voice/health Probe ElevenLabs availability
POST /voice/transcribe Whisper fallback STT
GET /documents List 122 phila.gov forms
GET /documents/:id One form's metadata + PDF URL
POST /documents/:id/submit Route by submitPath (open311 / email / mail / online / in-person)
GET /services Browse 712+ city services
GET /forms/:id Form field schema for AutoFilloutForm

Full contract: see BACKEND_API_CONTRACT.md.


Languages (10)

English Β· Spanish Β· Simplified Chinese Β· Traditional Chinese Β· Russian Β· Vietnamese Β· Arabic (RTL) Β· Haitian Creole Β· Brazilian Portuguese Β· French

All UI strings live in /frontend/src/i18n/locales/. The agent reasons in English internally but replies in the user's selected language.


The Kiosk

A $50 Raspberry Pi 3 running:

  • Raspberry Pi OS Bookworm
  • Chromium in --kiosk mode (no chrome / tabs / nav bar)
  • Auto-loads https://<host>/?kiosk=1 β€” flips the React app into kiosk mode (hides tab bar, shows large-touch UI)
  • 90-second idle timer auto-resets to home for the next visitor
  • unclutter hides the cursor after 2s
  • Wi-Fi power-save disabled, screen blanking disabled

Install script: kiosk/install-kiosk.sh (idempotent β€” safe to re-run).


Local Development

Prerequisites

  • Node 20+
  • A Supabase project (Postgres + Auth)
  • API keys: OpenAI, ElevenLabs, Gmail OAuth refresh token (optional)

Backend

cd backend
npm install
cp .env.example .env   # fill in keys
npx prisma migrate deploy
npm run dev            # β†’ http://localhost:3000

Frontend

cd frontend
npm install
cp .env.example .env   # set VITE_API_URL + VITE_SUPABASE_*
npm run dev            # β†’ http://localhost:5173

Environment variables

backend/.env

PORT=3000
OPENAI_API_KEY=sk-…
ELEVENLABS_API_KEY=sk_…
ELEVENLABS_VOICE_ID=EXAVITQu4vr4xnSDxMaL
SUPABASE_URL=https://<project>.supabase.co
SUPABASE_ANON_KEY=…
SUPABASE_SERVICE_ROLE_KEY=…
DATABASE_URL=postgresql://…
DIRECT_URL=postgresql://…
GMAIL_CLIENT_ID=…
GMAIL_CLIENT_SECRET=…

frontend/.env

VITE_API_URL=http://localhost:3000
VITE_SUPABASE_URL=https://<project>.supabase.co
VITE_SUPABASE_ANON_KEY=…

Project Structure

Ben-Franklin/
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ modules/
β”‚   β”‚   β”‚   β”œβ”€β”€ chat/         # agent orchestration + SSE streaming
β”‚   β”‚   β”‚   β”œβ”€β”€ voice/        # /voice/tts + /voice/transcribe
β”‚   β”‚   β”‚   β”œβ”€β”€ documents/    # phila.gov forms catalog + PDF resolution
β”‚   β”‚   β”‚   β”œβ”€β”€ services/     # phila.gov services catalog
β”‚   β”‚   β”‚   β”œβ”€β”€ forms/        # form field schemas + prefill
β”‚   β”‚   β”‚   β”œβ”€β”€ auth/         # Supabase OAuth bridge
β”‚   β”‚   β”‚   β”œβ”€β”€ email/        # Gmail intake mailer
β”‚   β”‚   β”‚   └── brt-appeal/   # property tax appeal flow
β”‚   β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”‚   β”œβ”€β”€ agent.ts      # the 14-tool LLM loop
β”‚   β”‚   β”‚   └── embeddings.ts # in-memory RAG
β”‚   β”‚   └── data/
β”‚   β”‚       β”œβ”€β”€ philaDocumentsCatalog.ts   # 700+ docs
β”‚   β”‚       β”œβ”€β”€ philaServicesCatalog.ts    # 712+ services
β”‚   β”‚       β”œβ”€β”€ orgs.json                  # ~50 nonprofits
β”‚   β”‚       └── laws.json                  # PA / Philly law citations
β”‚   └── prisma/schema.prisma
β”‚
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ pages/            # Landing, Home, BenChat, Kiosk, Documents, …
β”‚   β”‚   β”œβ”€β”€ components/       # VoiceInput, SpeakButton, ResponseCard, …
β”‚   β”‚   β”œβ”€β”€ hooks/            # useVoice, useSpeech, useSentenceTTS, useProfile
β”‚   β”‚   β”œβ”€β”€ services/         # api.js, auth.js
β”‚   β”‚   └── i18n/locales/     # 10 language packs
β”‚   └── vite.config.js
β”‚
β”œβ”€β”€ kiosk/
β”‚   β”œβ”€β”€ install-kiosk.sh      # idempotent Pi setup
β”‚   └── README.md
β”‚
└── BACKEND_API_CONTRACT.md   # full endpoint spec

Why this matters

We didn't build another chatbot. We built AI as an interface to government β€” accessible, actionable, and built for the people who need it most. Voice-first means it works for someone whose hands are full, whose English is shaky, or whose only "device" is a kiosk in a library.


Credits

Built at Drexel CodeFest 2026 by team Ben-Franklin.

Stack credits: Anthropic Claude Β· OpenAI GPT-4o Β· ElevenLabs Β· Supabase Β· phila.gov open data Β· PublicStuff 311 Β· CARTO Β· React Β· Prisma

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages