A Django web application that displays US commercial sites on an interactive map and estimates their solar energy potential using NREL's PVWatts API. Sites are geocoded from street addresses, then analyzed for annual AC energy production and capacity factor using a standard 100 kW commercial system model.
- Python 3.10+
- pip
- A free NREL API key
git clone <repo-url>
cd solar-mapper
python3 -m venv venv
source venv/bin/activatepip install -r requirements.txtrequirements.txt pins exact versions for all direct and transitive dependencies. Installing from it guarantees an identical environment to the one used during development.
- Go to developer.nrel.gov/signup
- Fill out the short form — the key is issued instantly by email
- The key unlocks both the Solar Resource API and PVWatts v8
cp .env.example .envOpen .env and replace the placeholder:
NREL_API_KEY=your_actual_key_here
The .env file is listed in .gitignore and will never be committed.
python3 manage.py migratepython3 manage.py createsuperuserOr use the pre-configured development credentials:
echo "from django.contrib.auth import get_user_model; U = get_user_model(); U.objects.create_superuser('admin', 'admin@example.com', 'admin123')" | python3 manage.py shellThis single command resets the database, loads the fixture, geocodes all addresses, and fetches solar data:
python3 manage.py load_sitesExpected output:
Loaded 5 site(s) from fixture.
--- Geocoding ---
[101] Tesla Gigafactory Texas ... OK (30.22193, -97.61877)
[102] Amazon Fulfillment Center ... FAILED — No results found
[103] Google Data Center ... OK (41.25766, -95.89954)
[104] Walmart Distribution Center ... OK (32.97827, -97.30684)
[105] Invalid Address Test ... FAILED — No results found
--- Solar Data ---
[101] Tesla Gigafactory Texas ... OK 147,130 kWh/yr
...
--- Summary ---
[OK] Tesla Gigafactory Texas → (30.2219, -97.6188) 147,130 kWh/yr
[FAILED] Amazon Fulfillment Center → No results found for address: ...
[OK] Google Data Center → (41.2577, -95.8995) 140,930 kWh/yr
[OK] Walmart Distribution Center → (32.9783, -97.3068) 157,027 kWh/yr
[FAILED] Invalid Address Test → No results found for address: ...
python3 manage.py runserverOpen http://127.0.0.1:8000/ in your browser.
You will see:
- A full-screen interactive map centered on the continental US
- Blue markers for successfully geocoded and analyzed sites
- Red markers for sites where geocoding failed (positioned in the Gulf region for visibility)
- Clicking any marker opens a popup with the site name and a link to its detail page
- The detail page shows solar irradiance, annual energy production, and capacity factor for successful sites, or a clear error banner for failed ones
The Django admin is available at http://127.0.0.1:8000/admin/ (credentials: admin / admin123). The site list can be filtered by geocode status using the right-hand sidebar.
Django was chosen because it offers a mature ORM, built-in admin interface, and management commands out of the box, all directly useful for this scope. The admin panel alone provides a free UI for verifying geocoding results without writing any additional code. Python is also the natural language for data-adjacent work like API integration and batch processing.
Nominatim (OpenStreetMap's geocoding service) is free and requires no API key, making the project fully runnable without any account setup beyond NREL. It has strong coverage for US commercial addresses. A 1-second delay is enforced between requests per Nominatim's usage policy.
CARTO's basemap tiles (light_all) are more permissive than the OSM default tile server for development use. The OSM tile CDN enforces referrer restrictions that cause 403 errors in local Django development. CARTO tiles require no API key and provide a clean, low-contrast base layer that keeps site markers visually prominent.
Geocoding and solar data fetching are batch operations with rate limits and external costs. Running them as management commands (geocode_sites, fetch_solar_data) means they execute intentionally on demand rather than on every page load. Each command is independently re-runnable: geocode_sites only touches pending sites; fetch_solar_data only touches geocoded sites with no existing solar data.
Every external API call returns a normalized result dict (success, data fields, error). The geocode_status field on each Site tracks state (pending / success / failed), and geocode_error stores the human-readable reason. Failed sites are never silently dropped and they appear as red markers on the map with their error message in the popup, and their detail page shows a prominent warning banner explaining why solar analysis is unavailable.
| Decision | Rationale |
|---|---|
JSONField for solar data |
NREL responses vary by endpoint; a flexible field avoids premature schema design and makes it easy to store both Solar Resource and PVWatts outputs together |
| Separate geocoding and solar commands | Decoupled retry logic — if NREL is down, geocoding results are preserved; if an address is wrong, only that step needs to be re-run |
| Red vs blue markers | Immediate visual distinction between operational and failed sites; failed sites remain visible on the map rather than being silently hidden |
| 1-second Nominatim delay | Nominatim's usage policy requires a maximum of 1 request/second; the delay is enforced in services.py at the call site, not left to the caller |
update_fields on save |
Management commands use update_fields to avoid race conditions and unintentionally overwriting unrelated fields during batch updates |
| Command | What it does |
|---|---|
python3 manage.py load_sites |
Full reset: delete all sites, load fixture, geocode, fetch solar data |
python3 manage.py geocode_sites |
Geocode all pending sites |
python3 manage.py fetch_solar_data |
Fetch NREL data for all geocoded sites missing solar data |