Write your representatives, EU version
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```