Fork of Lightning_Report adding: - n8n_report_branch.json: workflow branch for storm-triggered report delivery - report_service/: FastAPI microservice wrapping create_docx_report() so n8n can produce byte-identical reports without fighting the Python Code sandbox Made-with: Cursor
98 lines
4.2 KiB
Markdown
98 lines
4.2 KiB
Markdown
# Lightning Report Service
|
|
|
|
Tiny FastAPI wrapper around `create_docx_report()`. Lets the n8n workflow produce reports that are **byte-identical** to the CLI (`batch_generate.py`) output, since it uses the same code path.
|
|
|
|
## Why this exists
|
|
|
|
n8n 2.x's self-hosted Python Code node runs in a sandbox that strips common builtins (`hasattr`, `getattr`, etc.) and restricts imports, making it impossible to host the 3,500+ line reporting pipeline in a node. This service runs outside the sandbox and is called over HTTP.
|
|
|
|
## Endpoints
|
|
|
|
| Method | Path | Purpose |
|
|
|---|---|---|
|
|
| `GET` | `/health` | Liveness probe |
|
|
| `POST` | `/generate` | Accepts the `Report: Build Payload` JSON, returns a DOCX binary |
|
|
|
|
### Request body for `/generate`
|
|
|
|
Mirrors the `Report: Build Payload` Set node. Required keys:
|
|
|
|
```json
|
|
{
|
|
"customer_name": "Example Wind Farm",
|
|
"timezone": "Europe/Istanbul",
|
|
"centroid_lat": 40.5,
|
|
"centroid_lon": 29.7,
|
|
"boundary_m": 20000,
|
|
"rings": { "r1": 2000, "r2": 4000, "r3": 6000, "r4": 8000 },
|
|
"ring_colors": ["#B71C1C", "#F94144", "#F8961E", "#90BE6D"],
|
|
"t_start": 1729000000000,
|
|
"t_end": 1729010000000,
|
|
"n_strikes": 1234,
|
|
"strikes": [ { "latitude": ..., "longitude": ..., "peakCurrent": ..., "type": "0", "captured": "2026-04-22T13:00:00Z" } ],
|
|
"turbines": [ { "name": "T1", "latitude": ..., "longitude": ..., "unit_power_mwm": 3.6, ... } ],
|
|
|
|
"gemini_text": "optional — if provided, used verbatim; else the service calls Gemini or falls back",
|
|
"storm_records": [ { "cell_polygon_wkt": "POLYGON(...)", "lightning_severity": "medium", "effective_time": "...", "expire_time": "..." } ]
|
|
}
|
|
```
|
|
|
|
The adapter is forgiving about column names: it accepts `lat`/`latitude`, `lng`/`longitude`/`lon`, `current`/`peakCurrent`/`peak_current`, `p_type`/`type`, `local_time`/`captured`/`timestamp`.
|
|
|
|
### Response
|
|
|
|
- `Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document`
|
|
- `Content-Disposition: attachment; filename="<slug>_<start>_<end>_report.docx"`
|
|
- Headers: `X-Report-Filename`, `X-Report-Customer`, `X-Report-Strikes`
|
|
|
|
## Running locally
|
|
|
|
```bash
|
|
cd lightning_report
|
|
python -m pip install -r requirements.txt -r report_service/requirements.txt
|
|
uvicorn report_service.main:app --host 0.0.0.0 --port 8000
|
|
```
|
|
|
|
Sanity check:
|
|
|
|
```bash
|
|
curl -sS http://127.0.0.1:8000/health
|
|
```
|
|
|
|
## Running with Docker (alongside n8n)
|
|
|
|
```bash
|
|
cd lightning_report
|
|
docker compose -f report_service/docker-compose.yml up --build -d
|
|
docker logs -f lightning-report-service
|
|
```
|
|
|
|
The compose file attaches the service to an external network named `n8n`. Check your actual network name with `docker network ls` and adjust the `name:` field if needed. Once attached, the n8n container can reach the service at `http://report-service:8000`.
|
|
|
|
## Environment variables
|
|
|
|
| Variable | Default | Purpose |
|
|
|---|---|---|
|
|
| `LOG_LEVEL` | `INFO` | Uvicorn/service log level |
|
|
| `GEMINI_API_KEY` | _(unset)_ | Only used if n8n doesn't send `gemini_text` in the payload. If unset, the service falls back to the deterministic commentary in `src/reporting/gemini_commentary.py` |
|
|
| `GEMINI_MODEL` | `gemini-1.5-flash` | Only used when the service calls Gemini itself |
|
|
|
|
## n8n configuration
|
|
|
|
In the n8n workflow, the `Report: Generate DOCX` HTTP Request node points at:
|
|
|
|
```
|
|
={{ $env.REPORT_SERVICE_URL || 'http://report-service:8000' }}/generate
|
|
```
|
|
|
|
- If n8n and the service share a Docker network, the default hostname works.
|
|
- Otherwise, set `REPORT_SERVICE_URL` as an n8n environment variable (e.g. `http://192.168.1.10:8000`).
|
|
|
|
Response format is set to `file` with output property `report`, which the downstream Slack node uploads directly.
|
|
|
|
## Gemini commentary handoff
|
|
|
|
The existing n8n branch already has a `Report: Gemini Commentary` HTTP Request node that calls Gemini. Its text is forwarded to this service as `gemini_text`. When present, the service skips its own Gemini call and plugs the text straight into `create_docx_report()`'s commentary slot via a scoped monkey-patch on `generate_gemini_paragraph`.
|
|
|
|
If you'd rather let the service handle Gemini end-to-end, you can delete the `Report: Gemini Commentary` node from the n8n workflow and point `Report: Build Payload` directly at `Report: Fetch Thunderstorms`.
|