BE-LightningReport/README.md

8.3 KiB

Lightning Report (n8n)

Automated lightning activity reports for wind farms. An n8n workflow detects strikes via the iklim.co API, generates a Word report through a Python report service, routes it through Slack for human approval, and emails the approved report to customers via SendGrid.

Architecture

┌─────────────┐     ┌──────────────────┐     ┌─────────────────────┐
│ n8n workflow│────▶│ iklim.co API     │     │ Report service      │
│ (scheduled) │     │ lightnings +     │     │ FastAPI /generate   │
│             │     │ thunderstorms    │     │ → DOCX              │
└──────┬──────┘     └──────────────────┘     └──────────▲──────────┘
       │                                                  │
       │  Slack upload + approve/reject buttons           │
       ▼                                                  │
┌─────────────┐     storm_logs datatable (pending report) │
│ Slack       │───────────────────────────────────────────┘
└──────┬──────┘
       │ on approve
       ▼
┌─────────────┐
│ SendGrid    │──▶ customer contact_email
└─────────────┘

Repository layout

Path Description
Lightning_Report_Automatic.json n8n workflow export (import in n8n)
report_service/ FastAPI wrapper around report generation
report_service/adapter.py Maps n8n JSON payload → pandas DataFrames
src/reporting/docx.py Main DOCX builder
src/analysis/ Risk, histogram, geospatial, statistics
src/visualization/ Maps and storm-cell plots
src/config.py Default analysis parameters

Prerequisites

  • Python 3.12+ (local or Docker)
  • n8n (self-hosted, with Data Tables enabled)
  • iklim.co API credentials (test: https://api-test.iklim.co)
  • Slack app/bot for uploads and approval buttons
  • SendGrid API key with Mail Send permission
  • Optional: Gemini API key if commentary is generated inside the report service (otherwise pass gemini_text from n8n)

Report service (local)

1. Install dependencies

cd Lightning_Report_n8n
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -r requirements.txt -r report_service/requirements.txt

2. Environment variables

Create a .env file in the project root (never commit it):

REPORT_SERVICE_TOKEN=your-long-random-secret
GEMINI_API_KEY=optional-if-not-supplied-by-n8n
GEMINI_MODEL=gemini-2.5-flash-lite
LOG_LEVEL=INFO

REPORT_SERVICE_TOKEN is required. The service refuses /generate if it is unset.

3. Run the server

export REPORT_SERVICE_TOKEN="your-long-random-secret"
uvicorn report_service.main:app --host 0.0.0.0 --port 8000

Health check: GET http://localhost:8000/health

4. Expose to n8n (development)

If n8n runs elsewhere, tunnel the service:

ngrok http 8000

Point the n8n Generate Report HTTP Request node at https://<your-ngrok-host>/generate and set header X-Report-Token to the same value as REPORT_SERVICE_TOKEN.

Report service (Docker)

cd Lightning_Report_n8n
export REPORT_SERVICE_TOKEN=your-long-random-secret
docker compose -f report_service/docker-compose.yml up --build -d

From n8n on the same Docker network: http://report-service:8000/generate

API

GET /health

Liveness probe. Returns {"ok": true, ...}.

POST /generate

Header Required Description
X-Report-Token Yes Must match REPORT_SERVICE_TOKEN
Content-Type Yes application/json

Body (built by n8n Build Report Payload node):

Field Description
customer_name Wind farm / customer label
t_start, t_end Storm window (epoch ms)
centroid_lat, centroid_lon Farm centroid
boundary_m Monitoring radius (m)
rings { r1, r2, r3, r4 } distance rings
timezone e.g. Europe/Istanbul
turbines Array of turbine objects
strikes Lightning strike records (captured ISO time preferred)
storm_records Thunderstorm polygons from /v1/thunderstorms/within
gemini_text Optional pre-generated commentary

Response: DOCX file (application/vnd.openxmlformats-officedocument.wordprocessingml.document).

n8n workflow

Import Lightning_Report_Automatic.json into n8n and configure:

Credentials

Integration Used for
iklim.co login Login to iklim.co / token refresh
Slack (n8n_lightning_report_bot) Report upload, approval buttons
Slack (Tarla Slack Account) Error / email-sent notifications
SendGrid Customer email after approval

Data tables

customers

Column Purpose
customer_name Display name
contact_email SendGrid recipient
id Linked from wind turbines

wind_turbine_farm

Column Purpose
customer_id FK to customers
latitude, longitude Turbine positions
name Turbine label

storm_logs

Column Purpose
customer_name, storm_key, storm_start, storm_end Storm metadata
total_strikes, status Count; waiting for confirmationconfirmed / rejected
pending_contact_email Email snapshot at report time
pending_email_file_name DOCX filename
pending_report_base64 Queued report (survives n8n restarts)
pending_report_mime_type MIME type for attachment

Main flow (simplified)

  1. Daily trigger → authenticate → loop customers.
  2. Query lightnings within farm boundary; detect storm window.
  3. Insert/update storm_logs, fetch thunderstorms, build payload.
  4. Generate Report → upload DOCX to Slack → approval buttons.
  5. Persist pending report fields on storm_logs.
  6. On Slack Approve → load queued report → SendGrid email → clear pending fields.

Email is sent only after approval, not when the report is first generated.

Troubleshooting

channel_not_found (Slack)

The Slack channel ID in the node does not exist or the bot is not invited. Re-select the channel in n8n and run /invite @YourBot in that channel.

Account Is not Found For User … (/v1/thunderstorms/within)

Login succeeded but the iklim user has no account on the API environment (e.g. test). Ask iklim.co to provision the user, or use a service account that has thunderstorm access. Lightning-only reports can continue if the workflow skips failed thunderstorm calls (optional branch).

Histogram shows 01-01-1970 / wrong period

Strike timestamps were parsed incorrectly when timestamp (epoch ms) was preferred over captured (ISO). Fixed in report_service/adapter.py — restart the report service after pulling updates.

SendGrid: attachment must be base64 encoded

Use the Prepare SendGrid Email Payload node path; ensure pending_report_base64 in storm_logs is populated and not truncated by datatable size limits.

Binary missing at Upload Report to Slack

The datatable persist step must run after Slack upload, not before (persist nodes drop binary data).

Slack invalid_payload on approval follow-up

Do not double-JSON.stringify the body when posting to response_url. Slack response URLs expire after ~30 minutes (optional confirmation message only).

Development notes

  • Per-farm settings (rings, centroid, dates) come from the n8n payload via apply_farm_config(), not hardcoded in src/config.py.
  • Prefer strike field captured (ISO) for local_time; numeric timestamp is supported as epoch milliseconds.
  • HMAC signing for iklim API calls is implemented in n8n Code nodes (Calculate Lightning Headers, Calculate Thunderstorm Headers).

License

Internal use — iklim.co lightning reporting pipeline.