erdemerikci 45d80dfaa6 Initial import: Lightning_Report with n8n integration
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
2026-04-22 15:13:08 +03:00

4.2 KiB

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:

{
  "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

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:

curl -sS http://127.0.0.1:8000/health

Running with Docker (alongside n8n)

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.