commits
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Prevents worktree contents from being tracked in repository.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Reorganize planning documents from vision/ to docs/plans/ for better
structure and discoverability.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive documentation for the address-based constituency
matching system, including:
- Technical overview of geocoding and point-in-polygon lookup
- Data sources and accuracy characteristics
- Setup instructions in README
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Preload WahlkreisLocator GeoJSON data during Django startup to ensure
first requests don't incur the ~2 second GeoJSON loading penalty.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add .gitignore patterns for Shapefile components and GeoJSON
- Update fetch_wahlkreis_data to download from official Bundeswahlleiterin
- Auto-detect and convert Shapefiles to GeoJSON using pyshp
- Keep only GeoJSON output, temporary files cleaned up automatically
Users can regenerate geodata by running:
uv run python manage.py fetch_wahlkreis_data --force
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add class-level cache for parsed GeoJSON constituencies to avoid
reloading and reparsing the 44MB file on every WahlkreisLocator
instantiation.
Performance improvement:
- First request: 1.36s (initial load + cache)
- Subsequent requests: 0.02s (68x faster)
- Overall: 43% improvement on first request, near-instant on subsequent
The cache is shared across all instances and invalidated only if
a different GeoJSON path is used.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements Task 8 from the accurate constituency matching plan. This command
provides a comprehensive test framework for the full address-based constituency
matching pipeline:
Address → Geocoding → Coordinates → Constituency → Representatives
Key features:
- Test single addresses with --street, --postal-code, --city arguments
- Test all 20 predefined German addresses with --test-all
- Filter specific test addresses with --indices parameter
- Comprehensive test coverage across all 16 German states
- Real landmark addresses for easy verification
- Clear, color-coded output showing each pipeline step
- Performance metrics: success rate, cache hits, timing stats
- Graceful error handling and informative warnings
Example usage:
# Test single address
uv run python manage.py test_matching \
--street "Platz der Republik 1" \
--postal-code "11011" \
--city "Berlin"
# Test all predefined addresses
uv run python manage.py test_matching --test-all
# Test specific addresses by index
uv run python manage.py test_matching --test-all --indices 1,5,10
Test addresses include major landmarks like the Bundestag, Brandenburg Gate,
Hamburg Rathaus, Munich Marienplatz, Cologne Cathedral, and others across
all German states.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added IdentityVerificationForm to collect street address, postal code,
and city from users. The form includes validation to ensure all three
fields are provided together or all left empty.
Changes:
- Added IdentityVerificationForm to forms.py with German labels
- Updated profile view to handle address form submission
- Modified profile.html template to display address form
- Address is stored in IdentityVerification model
- Form pre-fills existing address data
- Added comprehensive tests for form validation and view logic
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Integrates AddressGeocoder and WahlkreisLocator services into
ConstituencyLocator to enable accurate address-based constituency
matching with PLZ-prefix fallback.
Changes:
- Updated ConstituencyLocator to accept full address parameters
(street, postal_code, city, country)
- Implemented address-based lookup using AddressGeocoder +
WahlkreisLocator
- Maintained backward compatibility with PLZ-only lookups via
locate_legacy() method
- Returns List[Representative] instead of LocatedConstituencies
for the new API
- Added PLZ fallback when address geocoding fails
- Added comprehensive integration tests
The full pipeline now works as:
1. Address → AddressGeocoder → (lat, lon)
2. (lat, lon) → WahlkreisLocator → Wahlkreis info
3. Wahlkreis info → Database → Representatives
Completes Task 5 of accurate constituency matching implementation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement a service that uses Shapely to perform geometric point-in-polygon
matching to find which Bundestag constituency contains given coordinates.
Implementation:
- WahlkreisLocator class that loads 299 constituencies from GeoJSON
- Point-in-polygon matching using shapely.geometry.contains()
- Loads 44MB GeoJSON in ~1.4 seconds, caches parsed geometries
- Average lookup time: 0.44ms per coordinate pair
Tests:
- test_locate_bundestag_building: Verifies Berlin constituency matching
- test_locate_hamburg_rathaus: Verifies Hamburg constituency matching
- test_locate_multiple_known_locations: Tests 5 German cities
- test_coordinates_outside_germany: Tests Paris, London, NYC return None
- test_geojson_loads_successfully: Verifies loading performance
Performance metrics from interactive testing:
- GeoJSON load time: 1.444s (under 2s requirement)
- Average lookup: 0.44ms per query
- All 5 tests pass with correct constituency identification
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Downloaded official Shapefile from bundeswahlleiterin.de
- Converted to GeoJSON format with all 299 constituencies
- Added pyshp dependency for Shapefile processing
- Created GeoJSONDataTests to verify data integrity
- Updated CONSTITUENCY_BOUNDARIES_PATH to use wahlkreise.geojson
The official data includes:
- 299 Bundestag constituencies (Wahlkreise)
- Properties: WKR_NR, WKR_NAME, LAND_NR, LAND_NAME
- Polygon geometries for accurate point-in-polygon matching
File size: 44.08 MB
Source: https://www.bundeswahlleiterin.de/bundestagswahlen/2025/wahlkreiseinteilung/downloads.html
Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement geocoding service using OpenStreetMap Nominatim API for
German addresses with comprehensive caching and error handling.
Key features:
- Geocodes German addresses to lat/lon coordinates
- Uses GeocodeCache model to minimize API calls
- Implements rate limiting (1 request/second) for API compliance
- Caches both successful and failed attempts
- Handles API errors gracefully with detailed error messages
- Includes comprehensive test coverage with mocked API responses
Tests added:
- test_successful_geocoding_with_mocked_api
- test_cache_hit_no_api_call
- test_failed_geocoding_cached
- test_api_error_handling
All 25 tests pass (4 new AddressGeocoderTests + 21 existing).
Part of Task 2: OSM Nominatim API Client implementation.
Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This model caches geocoding API results to minimize external API calls
and improve performance. It stores:
- Address components (street, postal code, city, country)
- Coordinates (latitude, longitude)
- Success/failure status to avoid repeated failed lookups
- SHA256 hash of normalized address for fast lookup
Part of Task 1 from accurate constituency matching plan.
Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- Simplify expert matching to use TopicArea assignments directly
- Rank experts by committee role (chair > deputy > member)
- Increase expert representative limit from 5 to 15
- Filter direct representatives to only show direct mandates (not list)
- Fix topic matching false positives using word boundary regex
- Reorder suggestions UI: keywords first, remove similar letters section
- Add searchable constituency dropdowns using Tom Select library
- Remove "Saved constituencies" redundant display section from profile
- Remove tags field from letter creation form
- Add extra_head block to base template for page-specific assets
- Create keyword-based mapping between committees and federal TopicAreas
- Enhance TopicArea keywords with German terms from committee metadata
- Representatives now inherit topics from their committee memberships
- Add last_synced_at tracking across synced models for data freshness monitoring
- Add tqdm progress indicators for long-running sync operations
- Extract and store representative biography, focus topics, and contact links
- Remove unused ElectoralDistrict model, simplify Constituency structure
- translate the topic taxonomy to german (names, descriptions, keywords) and add legal references/urls
- publish a kompetenzen overview page with matrix, everyday examples, and per-level topic listings
- tighten suggestion tests around german keywords and ensure competency results render as expected
- add verification type/status support for identity records and allow self-declared constituency selection
- scope letter suggestions to relevant parliaments based on topic competence, prioritising matching constituencies
- update suggestion UI copy and add regressions covering state-focused topics and constituency filtering
- switch representative/topic fields to ManyToMany and wire migrations to preserve existing links
- drop the legacy committee.topic_area FK and migrate data into the new relation
- update the committee topic mapping command to use the new schema and improve its output
Add comprehensive documentation for the address-based constituency
matching system, including:
- Technical overview of geocoding and point-in-polygon lookup
- Data sources and accuracy characteristics
- Setup instructions in README
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add .gitignore patterns for Shapefile components and GeoJSON
- Update fetch_wahlkreis_data to download from official Bundeswahlleiterin
- Auto-detect and convert Shapefiles to GeoJSON using pyshp
- Keep only GeoJSON output, temporary files cleaned up automatically
Users can regenerate geodata by running:
uv run python manage.py fetch_wahlkreis_data --force
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add class-level cache for parsed GeoJSON constituencies to avoid
reloading and reparsing the 44MB file on every WahlkreisLocator
instantiation.
Performance improvement:
- First request: 1.36s (initial load + cache)
- Subsequent requests: 0.02s (68x faster)
- Overall: 43% improvement on first request, near-instant on subsequent
The cache is shared across all instances and invalidated only if
a different GeoJSON path is used.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements Task 8 from the accurate constituency matching plan. This command
provides a comprehensive test framework for the full address-based constituency
matching pipeline:
Address → Geocoding → Coordinates → Constituency → Representatives
Key features:
- Test single addresses with --street, --postal-code, --city arguments
- Test all 20 predefined German addresses with --test-all
- Filter specific test addresses with --indices parameter
- Comprehensive test coverage across all 16 German states
- Real landmark addresses for easy verification
- Clear, color-coded output showing each pipeline step
- Performance metrics: success rate, cache hits, timing stats
- Graceful error handling and informative warnings
Example usage:
# Test single address
uv run python manage.py test_matching \
--street "Platz der Republik 1" \
--postal-code "11011" \
--city "Berlin"
# Test all predefined addresses
uv run python manage.py test_matching --test-all
# Test specific addresses by index
uv run python manage.py test_matching --test-all --indices 1,5,10
Test addresses include major landmarks like the Bundestag, Brandenburg Gate,
Hamburg Rathaus, Munich Marienplatz, Cologne Cathedral, and others across
all German states.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added IdentityVerificationForm to collect street address, postal code,
and city from users. The form includes validation to ensure all three
fields are provided together or all left empty.
Changes:
- Added IdentityVerificationForm to forms.py with German labels
- Updated profile view to handle address form submission
- Modified profile.html template to display address form
- Address is stored in IdentityVerification model
- Form pre-fills existing address data
- Added comprehensive tests for form validation and view logic
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Integrates AddressGeocoder and WahlkreisLocator services into
ConstituencyLocator to enable accurate address-based constituency
matching with PLZ-prefix fallback.
Changes:
- Updated ConstituencyLocator to accept full address parameters
(street, postal_code, city, country)
- Implemented address-based lookup using AddressGeocoder +
WahlkreisLocator
- Maintained backward compatibility with PLZ-only lookups via
locate_legacy() method
- Returns List[Representative] instead of LocatedConstituencies
for the new API
- Added PLZ fallback when address geocoding fails
- Added comprehensive integration tests
The full pipeline now works as:
1. Address → AddressGeocoder → (lat, lon)
2. (lat, lon) → WahlkreisLocator → Wahlkreis info
3. Wahlkreis info → Database → Representatives
Completes Task 5 of accurate constituency matching implementation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement a service that uses Shapely to perform geometric point-in-polygon
matching to find which Bundestag constituency contains given coordinates.
Implementation:
- WahlkreisLocator class that loads 299 constituencies from GeoJSON
- Point-in-polygon matching using shapely.geometry.contains()
- Loads 44MB GeoJSON in ~1.4 seconds, caches parsed geometries
- Average lookup time: 0.44ms per coordinate pair
Tests:
- test_locate_bundestag_building: Verifies Berlin constituency matching
- test_locate_hamburg_rathaus: Verifies Hamburg constituency matching
- test_locate_multiple_known_locations: Tests 5 German cities
- test_coordinates_outside_germany: Tests Paris, London, NYC return None
- test_geojson_loads_successfully: Verifies loading performance
Performance metrics from interactive testing:
- GeoJSON load time: 1.444s (under 2s requirement)
- Average lookup: 0.44ms per query
- All 5 tests pass with correct constituency identification
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Downloaded official Shapefile from bundeswahlleiterin.de
- Converted to GeoJSON format with all 299 constituencies
- Added pyshp dependency for Shapefile processing
- Created GeoJSONDataTests to verify data integrity
- Updated CONSTITUENCY_BOUNDARIES_PATH to use wahlkreise.geojson
The official data includes:
- 299 Bundestag constituencies (Wahlkreise)
- Properties: WKR_NR, WKR_NAME, LAND_NR, LAND_NAME
- Polygon geometries for accurate point-in-polygon matching
File size: 44.08 MB
Source: https://www.bundeswahlleiterin.de/bundestagswahlen/2025/wahlkreiseinteilung/downloads.html
Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement geocoding service using OpenStreetMap Nominatim API for
German addresses with comprehensive caching and error handling.
Key features:
- Geocodes German addresses to lat/lon coordinates
- Uses GeocodeCache model to minimize API calls
- Implements rate limiting (1 request/second) for API compliance
- Caches both successful and failed attempts
- Handles API errors gracefully with detailed error messages
- Includes comprehensive test coverage with mocked API responses
Tests added:
- test_successful_geocoding_with_mocked_api
- test_cache_hit_no_api_call
- test_failed_geocoding_cached
- test_api_error_handling
All 25 tests pass (4 new AddressGeocoderTests + 21 existing).
Part of Task 2: OSM Nominatim API Client implementation.
Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This model caches geocoding API results to minimize external API calls
and improve performance. It stores:
- Address components (street, postal code, city, country)
- Coordinates (latitude, longitude)
- Success/failure status to avoid repeated failed lookups
- SHA256 hash of normalized address for fast lookup
Part of Task 1 from accurate constituency matching plan.
Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- Simplify expert matching to use TopicArea assignments directly
- Rank experts by committee role (chair > deputy > member)
- Increase expert representative limit from 5 to 15
- Filter direct representatives to only show direct mandates (not list)
- Fix topic matching false positives using word boundary regex
- Reorder suggestions UI: keywords first, remove similar letters section
- Create keyword-based mapping between committees and federal TopicAreas
- Enhance TopicArea keywords with German terms from committee metadata
- Representatives now inherit topics from their committee memberships
- Add last_synced_at tracking across synced models for data freshness monitoring
- Add tqdm progress indicators for long-running sync operations
- Extract and store representative biography, focus topics, and contact links
- Remove unused ElectoralDistrict model, simplify Constituency structure
- add verification type/status support for identity records and allow self-declared constituency selection
- scope letter suggestions to relevant parliaments based on topic competence, prioritising matching constituencies
- update suggestion UI copy and add regressions covering state-focused topics and constituency filtering