AI Agent Coding Plan: Council District & Councilmember Resolution
Feature Overview
Automatically resolve a property to its City Council district and current councilmember, displaying contact information and flagging committee-relevant routing when applicable.
Agent Objectives
Primary Goal
Given a property address or OPA number, the agent must:
- Extract or normalize the property coordinates (latitude, longitude)
- Perform point-in-polygon lookup against Philadelphia’s Council District shapefile
- Identify the current councilmember for that district
- Return councilmember name, district number, office email, and phone
- Flag if the L&I Committee chair is the relevant member (for licensing/violations issues)
Success Criteria
- Coordinate lookup: <100ms (in-memory shapefile or cached GeoJSON)
- Councilmember data: always current (refreshed weekly or on-demand from city API)
- Committee flagging: correctly identifies L&I Chair for violations/licensing contexts
- User-facing output: councilmember contact + district + committee flag in <50 tokens
Data Sources & Dependencies
Primary Data
| Source |
Purpose |
Format |
Update Cadence |
Notes |
| OPA Property API |
Property coordinates (lat/lon) |
JSON |
Real-time |
Use existing OPA lookup; extract centroid |
| City Council District Shapefile |
Point-in-polygon lookup |
GeoJSON or shapefile binary |
Annual or on-demand |
Available from City’s GIS; convert to GeoJSON for in-memory use |
| City Council Member Directory |
Name, district, contact info |
Published CSV/JSON or web scrape |
Weekly or triggered |
phila.gov or official council roster |
| L&I Committee Membership |
Current chair identification |
HTML table or API |
Monthly or event-driven |
Parse from council website or FOIA/public record |
Secondary Validation
- Cross-reference OPA district assignment (if available) as fallback/validation
- Cache layer: Redis or in-memory for shapefile + councilmember directory (TTL: 7 days)
Agent Workflow
Step 1: Input & Normalization
Agent Task:
- Accept property address (string) or OPA number (numeric)
- Call OPA Property API to fetch property record
- Extract
LATITUDE and LONGITUDE fields
- Validate coordinates are within Philadelphia bounds
- Output: (lat, lon, opa_number, address_string)
Error Handling:
- If OPA lookup fails: return user-friendly error + suggest alternative input
- If coordinates missing: attempt reverse-geocode from address string
- If outside Philly: inform user, suggest nearby properties
Step 2: District Resolution via Point-in-Polygon
Agent Task:
- Load Philadelphia Council District shapefile (GeoJSON, in-memory or cached)
- Perform point-in-polygon test against property coordinates
- Match to district number (1–17)
- Output: district_number
Implementation Notes:
- Use lightweight geo library:
point-in-polygon npm or similar
- Preload shapefile on agent startup; cache as JSON for speed
- If shapefile unavailable: fall back to OPA’s internal district field (if present)
Fallback:
- Query City’s GIS API directly if local lookup fails (slower, network-dependent)
Step 3: Councilmember Lookup
Agent Task:
- Query cached councilmember directory by district_number
- Return: name, office email, phone, committee assignments
- Output: {name, email, phone, committees: []}
Data Refresh Logic:
- On startup: fetch from phila.gov or official source
- Cache TTL: 7 days
- Manual refresh endpoint:
agent.refresh_councilmembers()
- Fallback: embedded JSON snapshot (updated quarterly in code)
Step 4: Committee Flagging (Conditional)
Agent Task:
- Check if current issue context includes licensing violations or rental license expiration
- Retrieve L&I Committee chair name + contact
- If district’s councilmember == L&I Chair: flag explicitly
- If district’s councilmember != L&I Chair: note chair’s committee role for escalation
- Output: {committee_relevant: bool, flag_text: string}
Flag Logic:
IF issue_type IN ['license_violation', 'rental_license_expired', 'unfit_structure', 'dangerous_building']:
IF district_councilmember == li_committee_chair:
flag = "Your councilmember, [NAME], chairs the L&I Committee — direct jurisdiction."
ELSE:
flag = "For licensing issues, consider L&I Committee Chair [CHAIR_NAME] — direct contact: [EMAIL/PHONE]."
ELSE:
flag = null
Output Format (User-Facing)
Standard Response
Council District: 2
Councilmember: Kenyatta Johnson
Email: kenyatta.johnson@phila.gov
Phone: (215) 686-XXXX
With Committee Flag (Licensing/Violations)
Council District: 2
Councilmember: Mark Squilla
Email: mark.squilla@phila.gov
Phone: (215) 686-XXXX
⚠️ Committee Note: Your councilmember chairs the L&I Committee,
which has direct oversight of rental licensing and building violations.
Escalation Path (Non-Chair District)
Council District: 3
Councilmember: Jamie Gauthier
Email: jamie.gauthier@phila.gov
Phone: (215) 686-XXXX
For licensing or violation issues, the L&I Committee Chair is:
Mark Squilla | mark.squilla@phila.gov | (215) 686-XXXX
Implementation Checklist
Phase 1: Core Lookup (Minimum Viable)
Phase 2: Committee Flagging & Context Awareness
Phase 3: Robustness & Refresh
Performance & Costs
| Operation |
Latency |
Cost |
Notes |
| OPA lookup |
~200ms |
$0 (cached) |
Reuse from prior property fetch |
| Point-in-polygon |
<10ms |
$0 (in-memory) |
Preloaded shapefile |
| Councilmember lookup |
<5ms |
$0 (cached JSON) |
7-day TTL |
| Committee check |
<5ms |
$0 (cached) |
Embedded roster |
| Total |
~200ms |
$0 |
Dominated by OPA call |
Scope Notes
In Scope (This Feature)
✅ Property → Council district resolution (point-in-polygon)
✅ Councilmember name, contact, committee assignments
✅ L&I Committee chair identification for licensing/violations
✅ Committee-relevant routing flags
✅ Caching for performance & cost control
Out of Scope
❌ Individual councilmember voting records or positions
❌ Constituent casework tracking or ticket submission
❌ Real-time council hearing schedules or bill tracking
❌ Other city boards (ZBA, HRC, etc.) — scope is council only
Dependencies & Libraries
{
"point-in-polygon": "^2.2.0",
"geojson": "^0.5.0",
"lru-cache": "^10.x",
"axios": "^1.6.0"
}
Success Metrics (Post-Launch)
- Accuracy: 100% district assignment (validated against sample of 100 properties)
- Councilmember data freshness: <7 days stale
- Committee flag precision: 0 false positives in violations/licensing queries
- User satisfaction: “Contact info was helpful” (binary feedback)
- Latency: P95 <300ms (including OPA call)
Notes for Implementation
- Shapefile Conversion: Download from City’s GIS portal, convert using GDAL or
ogr2ogr to GeoJSON once, commit to repo. No runtime shape parsing needed.
- Councilmember Directory: Scrape phila.gov on-demand (weekly cron), or manually maintain a JSON snapshot. Include district, name, email, phone, committees.
- L&I Chair: Currently Mark Squilla (District 5, Chair). Maintain a small JSON config mapping “li_committee_chair” → current person + contact. Update whenever council changes chair.
- Issue Context: When agent receives a property + issue type (e.g., “rental_license_expired”), pass issue_type to the councilmember lookup routine so it can decide whether to flag committee relevance.
- Fallback Chain: OPA coords → reverse-geocode → OPA district field → error. Do not fail silently.
Example Agent Invocation
# Input
agent.resolve_council_member(
property_address="315 N 12th St, Philadelphia, PA",
issue_type="rental_license_expired"
)
# Output
{
"district": 2,
"councilmember": {
"name": "Kenyatta Johnson",
"email": "kenyatta.johnson@phila.gov",
"phone": "(215) 686-XXXX"
},
"committee_flag": {
"relevant": false,
"text": "For licensing issues, contact L&I Committee Chair Mark Squilla: mark.squilla@phila.gov"
}
}
Timeline Recommendation
Priority: HIGH (ship before PhilaDox/ZBA work)
Estimated effort: 3–5 days (Phase 1 + 2)
Dependencies: None (OPA already integrated)
Blocking: None
AI Agent Coding Plan: Council District & Councilmember Resolution
Feature Overview
Automatically resolve a property to its City Council district and current councilmember, displaying contact information and flagging committee-relevant routing when applicable.
Agent Objectives
Primary Goal
Given a property address or OPA number, the agent must:
Success Criteria
Data Sources & Dependencies
Primary Data
Secondary Validation
Agent Workflow
Step 1: Input & Normalization
Agent Task:
LATITUDEandLONGITUDEfieldsError Handling:
Step 2: District Resolution via Point-in-Polygon
Agent Task:
Implementation Notes:
point-in-polygonnpm or similarFallback:
Step 3: Councilmember Lookup
Agent Task:
Data Refresh Logic:
agent.refresh_councilmembers()Step 4: Committee Flagging (Conditional)
Agent Task:
Flag Logic:
Output Format (User-Facing)
Standard Response
With Committee Flag (Licensing/Violations)
Escalation Path (Non-Chair District)
Implementation Checklist
Phase 1: Core Lookup (Minimum Viable)
Phase 2: Committee Flagging & Context Awareness
Phase 3: Robustness & Refresh
Performance & Costs
Scope Notes
In Scope (This Feature)
✅ Property → Council district resolution (point-in-polygon)
✅ Councilmember name, contact, committee assignments
✅ L&I Committee chair identification for licensing/violations
✅ Committee-relevant routing flags
✅ Caching for performance & cost control
Out of Scope
❌ Individual councilmember voting records or positions
❌ Constituent casework tracking or ticket submission
❌ Real-time council hearing schedules or bill tracking
❌ Other city boards (ZBA, HRC, etc.) — scope is council only
Dependencies & Libraries
{ "point-in-polygon": "^2.2.0", "geojson": "^0.5.0", "lru-cache": "^10.x", "axios": "^1.6.0" }Success Metrics (Post-Launch)
Notes for Implementation
ogr2ogrto GeoJSON once, commit to repo. No runtime shape parsing needed.Example Agent Invocation
Timeline Recommendation
Priority: HIGH (ship before PhilaDox/ZBA work)
Estimated effort: 3–5 days (Phase 1 + 2)
Dependencies: None (OPA already integrated)
Blocking: None