Write your representatives, EU version
at main 83 lines 3.1 kB view raw view rendered
1# Constituency Matching Algorithm 2 3## Overview 4 5WriteThem.eu uses a two-stage process to match users to their correct Bundestag constituency: 6 71. **Address Geocoding**: Convert user's address to latitude/longitude coordinates 82. **Point-in-Polygon Lookup**: Find which constituency polygon contains those coordinates 9 10## Stage 1: Address Geocoding 11 12We use OpenStreetMap's Nominatim API to convert addresses to coordinates. 13 14### Process: 151. User provides: Street, Postal Code, City 162. System checks cache (GeocodeCache table) for previous results 173. If not cached, query Nominatim API with rate limiting (1 req/sec) 184. Cache result (success or failure) to minimize API calls 195. Return (latitude, longitude) or None 20 21### Fallback: 22If geocoding fails or user only provides postal code, fall back to PLZ prefix heuristic (maps first 2 digits to state). 23 24## Stage 2: Point-in-Polygon Lookup 25 26We use official Bundestag constituency boundaries (GeoJSON format) with shapely for geometric queries. 27 28### Process: 291. Load GeoJSON with 299 Bundestag constituencies on startup 302. Create shapely Point from coordinates 313. Check which constituency Polygon contains the point 324. Look up Constituency object in database by external_id 335. Return Constituency or None 34 35### Performance: 36- GeoJSON loaded once at startup (~2MB in memory) 37- Class-level caching prevents repeated loads 38- Lookup typically takes 10-50ms 39- No external API calls required 40 41## Data Sources 42 43- **Constituency Boundaries**: [dknx01/wahlkreissuche](https://github.com/dknx01/wahlkreissuche) (Open Data) 44- **Geocoding**: [OpenStreetMap Nominatim](https://nominatim.openstreetmap.org/) (Open Data) 45- **Representative Data**: [Abgeordnetenwatch API](https://www.abgeordnetenwatch.de/api) 46 47## Accuracy 48 49This approach provides constituency-accurate matching (exact Wahlkreis), significantly more precise than PLZ-based heuristics which only provide state-level accuracy. 50 51### Known Limitations: 52- Requires valid German address 53- Dependent on OSM geocoding quality 54- Rate limited to 1 request/second (public API) 55 56## Implementation Details 57 58### Services 59 60- **AddressGeocoder** (`letters/services.py`): Handles geocoding with caching 61- **WahlkreisLocator** (`letters/services.py`): Performs point-in-polygon matching 62- **ConstituencyLocator** (`letters/services.py`): Integrates both services with PLZ fallback 63 64### Database Models 65 66- **GeocodeCache** (`letters/models.py`): Caches geocoding results to minimize API calls 67- **Constituency** (`letters/models.py`): Stores constituency information with external_id mapping to GeoJSON 68 69### Management Commands 70 71- **fetch_wahlkreis_data**: Downloads official Bundestag constituency boundaries 72- **query_wahlkreis**: Query constituency by address or postal code 73- **query_topics**: Find matching topics for letter text 74- **query_representatives**: Find representatives by address and/or topics 75 76### Testing 77 78Run the test suite: 79```bash 80python manage.py test letters.tests.test_address_matching 81python manage.py test letters.tests.test_topic_mapping 82python manage.py test letters.tests.test_constituency_suggestions 83```