The app shipped the Flutter template's blue F, which said nothing about what it does and did not match the interface. The launcher icon is now a white cloud on the same seed blue the app themes itself with, so the two read as one product. The icon is drawn rather than hand-edited: tool/generate_icon.py renders it from signed distance fields — a union of three circles and a rounded box — using only the standard library, so regenerating it needs no image toolchain. The shape's framing is derived from its own bounding box, which is what keeps it centred; maintaining the bounds separately from the shape had it drifting off-centre. Adds the adaptive icon and the Android 13 monochrome layer, which were missing entirely. The generator frames the cloud against the 72dp the launcher mask always keeps and adaptive_icon_foreground_inset is 0, so the layer is not shrunk twice and the icon reads at the same size as its neighbours on a home screen. Verified on the emulator: the cloud appears in the app drawer and holds its shape down to 48px. docs/architecture.md records how to regenerate it, including the flutter_launcher_icons 0.14.4 bug that writes a string into a boolean Xcode setting and needs reverting by hand afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
99 lines
5.9 KiB
Markdown
99 lines
5.9 KiB
Markdown
# CLAUDE.md — Nuvolari
|
|
|
|
## Project
|
|
Cross-platform precipitation-radar app for the Piedmont region (Italy). Android first,
|
|
iOS later, single Flutter codebase. Region-scoped now (Piemonte) but extensible to other
|
|
regions via configuration.
|
|
|
|
## Scope
|
|
**Radar on a map, plus official alerts and rain notifications. Nothing else.**
|
|
|
|
In scope:
|
|
- Animated precipitation radar over an OpenStreetMap base map
|
|
- Saved places, municipality and coordinate search, and optional device location with a
|
|
live position marker; used to frame the map and later to anchor notifications
|
|
- Ground-station rain accumulations and 72-hour temperature from the ARPA realtime API,
|
|
published through the worker
|
|
- ARPA Piemonte official alert bulletin (XML-CAP), republished verbatim
|
|
- "Rain incoming" notifications by geographic cell
|
|
|
|
Explicitly out of scope — do not add these back without being asked:
|
|
- Weather forecasts of any kind
|
|
- Lightning
|
|
- Home-screen widget
|
|
- Advertising and consent flows
|
|
|
|
The app is currently free with **no advertising**. Ads may return later, so prefer data
|
|
sources that permit commercial use; do not adopt a non-commercial-only source on the
|
|
grounds that there are no ads today.
|
|
|
|
## Golden rules
|
|
- Code, identifiers and commit messages in English. UI text in Italian via ARB localization.
|
|
- Never commit secrets. Use .env + --dart-define; keep .gitignore updated.
|
|
- Never poll ARPA/DPC servers directly from the app: always go through our backend/CDN.
|
|
- Never store precise user locations server-side: rain notifications use geographic CELLS.
|
|
- Location is COARSE only. geolocator injects ACCESS_FINE_LOCATION; the app manifest
|
|
removes it with `tools:node="remove"`. Do not add it back without a feature that needs
|
|
it and a matching Data safety update. Saved places live in SharedPreferences on the
|
|
device and never leave it.
|
|
- Do not use ARPA/DPC name, logo or the word "ufficiale" in a way implying an official app.
|
|
|
|
## Architecture
|
|
- Monorepo: /app (Flutter), /backend (Python worker), /docs (detailed docs), /tool (scripts).
|
|
- Radar data via RadarSource interface with adapters:
|
|
DpcRadarSource (live, reads our CDN), ArpaRadarSource (stub, awaits access),
|
|
MockRadarSource (offline/demo). Active source chosen by region config + runtime flag.
|
|
- App reads PNG frames + manifest.json from CDN produced by the backend worker
|
|
(crop to Piemonte bbox, reproject to EPSG:3857).
|
|
|
|
## Data sources & licenses (attribution is mandatory on the Sources screen)
|
|
- Radar (active): Radar-DPC — base https://radar-api.protezionecivile.it/ ,
|
|
GET /findLastProductByType?type=VMI , POST /downloadProduct (GeoTIFF via presigned S3).
|
|
REQUIRE header `origin: https://radar.protezionecivile.it`. License CC BY-SA — credit
|
|
"Radar-DPC"; derivative data products must stay CC BY-SA. Docs: dpc-radar.readthedocs.io.
|
|
The rasters are on a **custom projection centred on Italy**, not EPSG:4326 or 3857, and
|
|
their GeoKeys are internally inconsistent — read the CRS from each file, never hardcode it.
|
|
- Radar (future): ARPA Piemonte (HDF5 ODIM, 5-minute volumes). The real-time access link
|
|
**must be requested by email** at info.meteo@arpa.piemonte.it — that request is the
|
|
project owner's to make. Adapter stays a disabled stub until it exists.
|
|
- Ground stations: ARPA realtime API, https://utility.arpa.piemonte.it/api_realtime —
|
|
no key, no registration. `/pie_anag` gives 374 stations with coordinates (286 with a
|
|
rain gauge); `/data_pie` gives hourly `cum_rain_1h/3h/6h/12h/24h`, temperature, wind,
|
|
snow and hydrometric level for the last 3 days. **It lags ~4.5 hours** — an observation
|
|
archive, not a live feed, and it must never be shown as current next to 5-minute radar.
|
|
Fetched by the worker, never by the app.
|
|
- ARPA licence: CC BY 4.0 per https://www.arpa.piemonte.it/note-legali, commercial use
|
|
permitted, credit "Fonte: Arpa Piemonte - www.arpa.piemonte.it". Both REST APIs link
|
|
that notice from their OpenAPI description. The radar page does not repeat it, so ask
|
|
ARPA to confirm it covers the radar volumes in the same email.
|
|
- Alerts: ARPA Piemonte XML-CAP bulletin at
|
|
https://www.arpa.piemonte.it/export/xmlcap/allerta.xml — reproduce alert levels WITHOUT
|
|
reinterpreting; link the official channel. Six level values, not four: VERDE, GIALLO,
|
|
ARANCIONE, ROSSO, plus BIANCO (avalanche scale out of season) and "-" (no data).
|
|
- Base map: OpenFreeMap (https://openfreemap.org), free OSM vector tiles with no API key
|
|
and no request limits. Mandatory credits: "© OpenMapTiles" and
|
|
"© OpenStreetMap contributors" (ODbL). OpenFreeMap's own credit is optional.
|
|
- Municipalities: 1180 Piedmont comuni bundled from Istat boundary shapefiles (CC BY 4.0),
|
|
generated by tool/generate_places.py. Offline search, no geocoding service. The source
|
|
DBF is UTF-8 despite appearances and the geometry is UTM 32N — see docs/data-sources.md
|
|
before regenerating.
|
|
- Launcher icon: a white cloud on the seed blue #1F6FB2, drawn by tool/generate_icon.py
|
|
and expanded by flutter_launcher_icons. Read the "Launcher icon" section of
|
|
docs/architecture.md before regenerating: the tool corrupts an Xcode build setting.
|
|
|
|
## Verification loop (run after each milestone)
|
|
- `dart format .` ; `flutter analyze` ; `flutter test` ; `flutter build appbundle`
|
|
- Backend: `python -m pytest` ; lint. Commit frequently with clear messages.
|
|
- `tool/verify.ps1` runs all of the above and skips stages that do not exist yet.
|
|
- Verify visually on the `nuvolari` AVD before calling a milestone done. Twice now,
|
|
running the app has caught defects that passing tests did not.
|
|
|
|
## Store / compliance targets
|
|
- Google Play: target API 36 (Android 16); closed testing 12 testers / 14 days;
|
|
Data safety section; prominent disclosure for location; signed AAB.
|
|
- No advertising, so no CMP and no IAB TCF obligations while that holds.
|
|
- iOS later: keep platform abstractions clean (privacy nutrition label to add).
|
|
|
|
## Docs to maintain in /docs
|
|
architecture.md, data-sources.md, licenses.md, stack-decisions.md, roadmap.md, privacy.md
|