05ed037fe8
- DNS: pilot.landvex.com -> 16.170.83.169 - TLS: Let's Encrypt certificate (expires 2026-09-30) - Nginx: reverse proxy with SSL termination - API: https://pilot.landvex.com/api/v1/missions - UI: https://pilot.landvex.com/ - Upload: POST /api/v1/missions/import (multipart/form-data) Verified: ✅ https://pilot.landvex.com/health ✅ https://pilot.landvex.com/version ✅ https://pilot.landvex.com/api/v1/missions (list) ✅ https://pilot.landvex.com/api/v1/missions/:id (get) ✅ POST /api/v1/missions/import (video upload) ✅ UI loads with title 'LandveX Intelligence Lab' Next: Pilot 001 — Break the system!
616 lines
25 KiB
Markdown
616 lines
25 KiB
Markdown
# LandveX Enterprise Platform — Rapporterings- och Analysmodul
|
|
|
|
## Översikt
|
|
|
|
Denna modul ger kommuner och andra kunder self-service rapportering med schemaläggning, delning och export. Den integreras med LandveX befintliga datakällor och exponeras via ett REST-API.
|
|
|
|
---
|
|
|
|
## 1. Datamodell
|
|
|
|
### 1.1 Entiteter (sammansatt vy)
|
|
|
|
```
|
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
│ Report │ │ ReportSchedule │ │ ReportShare │
|
|
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
|
|
│ id (PK) │ │ id (PK) │ │ id (PK) │
|
|
│ tenantId (FK) │◄────┤ reportId (FK) │ │ reportId (FK) │
|
|
│ name │ │ cronExpression │ │ sharedWithUserId│
|
|
│ description │ │ nextRunAt │ │ sharedWithEmail │
|
|
│ type │ │ lastRunAt │ │ permission │
|
|
│ config (JSON) │ │ status │ │ expiresAt │
|
|
│ dataSourceId │ │ createdBy │ │ createdAt │
|
|
│ createdBy │ └─────────────────┘ └─────────────────┘
|
|
│ createdAt │
|
|
│ updatedAt │ ┌─────────────────┐ ┌─────────────────┐
|
|
└─────────────────┘ │ ReportRun │ │ DataSource │
|
|
├─────────────────┤ ├─────────────────┤
|
|
│ id (PK) │ │ id (PK) │
|
|
│ reportId (FK) │ │ tenantId (FK) │
|
|
│ status │ │ name │
|
|
│ startedAt │ │ type │
|
|
│ completedAt │ │ connection │
|
|
│ resultUrl │ │ config (JSON) │
|
|
│ rowCount │ │ status │
|
|
│ errorMessage │ └─────────────────┘
|
|
└─────────────────┘
|
|
```
|
|
|
|
### 1.2 PostgreSQL DDL
|
|
|
|
```sql
|
|
-- Rapporter
|
|
CREATE TABLE reports (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
type VARCHAR(50) NOT NULL CHECK (type IN ('table','chart','pivot','map','dashboard')),
|
|
config JSONB NOT NULL DEFAULT '{}',
|
|
data_source_id UUID REFERENCES data_sources(id),
|
|
created_by UUID NOT NULL REFERENCES users(id),
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- Körningshistorik
|
|
CREATE TABLE report_runs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
report_id UUID NOT NULL REFERENCES reports(id) ON DELETE CASCADE,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','running','completed','failed')),
|
|
started_at TIMESTAMPTZ,
|
|
completed_at TIMESTAMPTZ,
|
|
result_url TEXT,
|
|
row_count INTEGER,
|
|
error_message TEXT,
|
|
triggered_by UUID REFERENCES users(id),
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- Scheman
|
|
CREATE TABLE report_schedules (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
report_id UUID NOT NULL REFERENCES reports(id) ON DELETE CASCADE,
|
|
cron_expression VARCHAR(100) NOT NULL,
|
|
timezone VARCHAR(50) DEFAULT 'Europe/Stockholm',
|
|
next_run_at TIMESTAMPTZ,
|
|
last_run_at TIMESTAMPTZ,
|
|
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active','paused','disabled')),
|
|
created_by UUID NOT NULL REFERENCES users(id),
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- Delning
|
|
CREATE TABLE report_shares (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
report_id UUID NOT NULL REFERENCES reports(id) ON DELETE CASCADE,
|
|
shared_with_user_id UUID REFERENCES users(id),
|
|
shared_with_email VARCHAR(255),
|
|
permission VARCHAR(20) NOT NULL DEFAULT 'read' CHECK (permission IN ('read','write','admin')),
|
|
expires_at TIMESTAMPTZ,
|
|
created_by UUID NOT NULL REFERENCES users(id),
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- Datakällor (befintlig tabell, utökas)
|
|
CREATE TABLE data_sources (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
type VARCHAR(50) NOT NULL CHECK (type IN ('postgresql','mysql','api','s3','bigquery','snowflake')),
|
|
connection JSONB NOT NULL,
|
|
config JSONB DEFAULT '{}',
|
|
status VARCHAR(20) DEFAULT 'active',
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- Index
|
|
CREATE INDEX idx_reports_tenant ON reports(tenant_id);
|
|
CREATE INDEX idx_report_runs_report ON report_runs(report_id, created_at DESC);
|
|
CREATE INDEX idx_schedules_next_run ON report_schedules(next_run_at) WHERE status = 'active';
|
|
CREATE INDEX idx_shares_user ON report_shares(shared_with_user_id);
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Rapport-API
|
|
|
|
### 2.1 REST Endpoints
|
|
|
|
| Metod | Endpoint | Beskrivning |
|
|
|-------|----------|-------------|
|
|
| GET | `/api/v1/reports` | Lista rapporter (tenant-scoped) |
|
|
| POST | `/api/v1/reports` | Skapa ny rapport |
|
|
| GET | `/api/v1/reports/:id` | Hämta rapport med config |
|
|
| PUT | `/api/v1/reports/:id` | Uppdatera rapport |
|
|
| DELETE | `/api/v1/reports/:id` | Ta bort rapport |
|
|
| POST | `/api/v1/reports/:id/run` | Kör rapport on-demand |
|
|
| GET | `/api/v1/reports/:id/runs` | Körningshistorik |
|
|
| GET | `/api/v1/reports/:id/runs/:runId/result` | Hämta resultat (JSON/CSV) |
|
|
| POST | `/api/v1/reports/:id/export` | Exportera rapport |
|
|
| POST | `/api/v1/reports/:id/schedule` | Skapa schema |
|
|
| GET | `/api/v1/reports/:id/schedule` | Visa schema |
|
|
| PUT | `/api/v1/reports/:id/schedule` | Uppdatera schema |
|
|
| DELETE | `/api/v1/reports/:id/schedule` | Ta bort schema |
|
|
| POST | `/api/v1/reports/:id/share` | Dela rapport |
|
|
| GET | `/api/v1/reports/:id/shares` | Lista delningar |
|
|
| DELETE | `/api/v1/reports/:id/shares/:shareId` | Återkalla delning |
|
|
|
|
### 2.2 OpenAPI-spec (kärn-endpoints)
|
|
|
|
```yaml
|
|
openapi: 3.0.3
|
|
info:
|
|
title: LandveX Reporting API
|
|
version: 1.0.0
|
|
paths:
|
|
/api/v1/reports:
|
|
get:
|
|
summary: Lista rapporter
|
|
parameters:
|
|
- name: tenantId
|
|
in: query
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
- name: type
|
|
in: query
|
|
schema: { type: string, enum: [table, chart, pivot, map, dashboard] }
|
|
- name: q
|
|
in: query
|
|
schema: { type: string }
|
|
responses:
|
|
200:
|
|
description: Lista med rapporter
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Report'
|
|
meta:
|
|
type: object
|
|
properties:
|
|
total: { type: integer }
|
|
page: { type: integer }
|
|
pageSize: { type: integer }
|
|
post:
|
|
summary: Skapa rapport
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ReportInput'
|
|
responses:
|
|
201:
|
|
description: Skapad
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Report'
|
|
|
|
/api/v1/reports/{id}/run:
|
|
post:
|
|
summary: Kör rapport
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
parameters:
|
|
type: object
|
|
description: Körningsparametrar (ersätter default)
|
|
format:
|
|
type: string
|
|
enum: [json, csv, xlsx, pdf]
|
|
default: json
|
|
responses:
|
|
202:
|
|
description: Körs
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ReportRun'
|
|
|
|
/api/v1/reports/{id}/export:
|
|
post:
|
|
summary: Exportera rapport
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
format:
|
|
type: string
|
|
enum: [csv, xlsx, pdf, json]
|
|
default: csv
|
|
filters:
|
|
type: object
|
|
email:
|
|
type: string
|
|
description: Skicka via e-post istället för nedladdning
|
|
responses:
|
|
200:
|
|
description: Fil eller bekräftelse
|
|
202:
|
|
description: E-post kommer skickas
|
|
|
|
/api/v1/reports/{id}/schedule:
|
|
post:
|
|
summary: Skapa schema
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ScheduleInput'
|
|
responses:
|
|
201:
|
|
description: Schema skapat
|
|
|
|
components:
|
|
schemas:
|
|
Report:
|
|
type: object
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
tenantId: { type: string, format: uuid }
|
|
name: { type: string }
|
|
description: { type: string }
|
|
type: { type: string, enum: [table, chart, pivot, map, dashboard] }
|
|
config: { type: object }
|
|
dataSourceId: { type: string, format: uuid }
|
|
createdBy: { type: string, format: uuid }
|
|
createdAt: { type: string, format: date-time }
|
|
updatedAt: { type: string, format: date-time }
|
|
|
|
ReportInput:
|
|
type: object
|
|
required: [name, type, config, dataSourceId]
|
|
properties:
|
|
name: { type: string, minLength: 1, maxLength: 255 }
|
|
description: { type: string }
|
|
type: { type: string, enum: [table, chart, pivot, map, dashboard] }
|
|
config: { type: object }
|
|
dataSourceId: { type: string, format: uuid }
|
|
|
|
ReportRun:
|
|
type: object
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
reportId: { type: string, format: uuid }
|
|
status: { type: string, enum: [pending, running, completed, failed] }
|
|
startedAt: { type: string, format: date-time }
|
|
completedAt: { type: string, format: date-time }
|
|
resultUrl: { type: string }
|
|
rowCount: { type: integer }
|
|
errorMessage: { type: string }
|
|
|
|
ScheduleInput:
|
|
type: object
|
|
required: [cronExpression]
|
|
properties:
|
|
cronExpression: { type: string, example: "0 6 * * 1" }
|
|
timezone: { type: string, default: "Europe/Stockholm" }
|
|
status: { type: string, enum: [active, paused, disabled], default: active }
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Tre Standardrapporter
|
|
|
|
### 3.1 Rapport A: "Fastighetsöversikt per Kommun"
|
|
|
|
**Typ:** Tabell + Karta
|
|
**Beskrivning:** Översikt över alla fastigheter inom kommunens gränser med nyckeltal.
|
|
|
|
```json
|
|
{
|
|
"name": "Fastighetsöversikt per Kommun",
|
|
"type": "dashboard",
|
|
"description": "Sammanställning av fastigheter, areal och värden per kommun",
|
|
"dataSourceId": "ds-landvex-gis",
|
|
"config": {
|
|
"widgets": [
|
|
{
|
|
"type": "metric",
|
|
"title": "Totalt antal fastigheter",
|
|
"query": "SELECT COUNT(*) FROM properties WHERE municipality_id = :municipalityId"
|
|
},
|
|
{
|
|
"type": "metric",
|
|
"title": "Total areal (ha)",
|
|
"query": "SELECT SUM(area_hectares) FROM properties WHERE municipality_id = :municipalityId"
|
|
},
|
|
{
|
|
"type": "table",
|
|
"title": "Fastigheter per typ",
|
|
"query": "SELECT type, COUNT(*) as count, SUM(area_hectares) as total_area FROM properties WHERE municipality_id = :municipalityId GROUP BY type",
|
|
"columns": [
|
|
{ "field": "type", "header": "Fastighetstyp" },
|
|
{ "field": "count", "header": "Antal" },
|
|
{ "field": "total_area", "header": "Total areal (ha)" }
|
|
]
|
|
},
|
|
{
|
|
"type": "map",
|
|
"title": "Fastighetskarta",
|
|
"layer": "properties",
|
|
"filter": { "municipality_id": ":municipalityId" }
|
|
}
|
|
],
|
|
"parameters": [
|
|
{
|
|
"name": "municipalityId",
|
|
"type": "string",
|
|
"required": true,
|
|
"label": "Kommun",
|
|
"source": "tenant.config.municipality_id"
|
|
}
|
|
],
|
|
"defaultFormat": "pdf"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3.2 Rapport B: "Transaktionshistorik med Trender"
|
|
|
|
**Typ:** Diagram + Tabell
|
|
**Beskrivning:** Historik över fastighetstransaktioner med prisutveckling och trender.
|
|
|
|
```json
|
|
{
|
|
"name": "Transaktionshistorik med Trender",
|
|
"type": "chart",
|
|
"description": "Transaktioner över tid med prisutveckling",
|
|
"dataSourceId": "ds-landvex-transactions",
|
|
"config": {
|
|
"charts": [
|
|
{
|
|
"type": "line",
|
|
"title": "Prisutveckling över tid",
|
|
"xAxis": { "field": "transaction_month", "label": "Månad" },
|
|
"yAxis": { "field": "avg_price_per_hectare", "label": "Kr/ha (genomsnitt)" },
|
|
"query": "SELECT DATE_TRUNC('month', transaction_date) as transaction_month, AVG(price_per_hectare) as avg_price_per_hectare FROM transactions WHERE municipality_id = :municipalityId AND transaction_date >= :fromDate GROUP BY 1 ORDER BY 1"
|
|
},
|
|
{
|
|
"type": "bar",
|
|
"title": "Antal transaktioner per månad",
|
|
"xAxis": { "field": "transaction_month" },
|
|
"yAxis": { "field": "transaction_count" },
|
|
"query": "SELECT DATE_TRUNC('month', transaction_date) as transaction_month, COUNT(*) as transaction_count FROM transactions WHERE municipality_id = :municipalityId AND transaction_date >= :fromDate GROUP BY 1 ORDER BY 1"
|
|
}
|
|
],
|
|
"detailTable": {
|
|
"title": "Senaste transaktioner",
|
|
"query": "SELECT t.id, t.property_id, p.address, t.transaction_date, t.price, t.area_hectares, t.price_per_hectare FROM transactions t JOIN properties p ON t.property_id = p.id WHERE t.municipality_id = :municipalityId ORDER BY t.transaction_date DESC LIMIT 100",
|
|
"columns": [
|
|
{ "field": "id", "header": "Transaktions-ID" },
|
|
{ "field": "address", "header": "Fastighet" },
|
|
{ "field": "transaction_date", "header": "Datum", "format": "date" },
|
|
{ "field": "price", "header": "Pris (kr)", "format": "currency" },
|
|
{ "field": "area_hectares", "header": "Areal (ha)", "format": "number" },
|
|
{ "field": "price_per_hectare", "header": "Kr/ha", "format": "currency" }
|
|
]
|
|
},
|
|
"parameters": [
|
|
{
|
|
"name": "municipalityId",
|
|
"type": "string",
|
|
"required": true,
|
|
"source": "tenant.config.municipality_id"
|
|
},
|
|
{
|
|
"name": "fromDate",
|
|
"type": "date",
|
|
"required": false,
|
|
"default": "-1 year",
|
|
"label": "Från datum"
|
|
}
|
|
],
|
|
"defaultFormat": "xlsx"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3.3 Rapport C: "Skogsbruksplan — Åtgärdsförslag"
|
|
|
|
**Typ:** Tabell + Pivot
|
|
**Beskrivning:** Åtgärdsförslag från skogsbruksplaner med tidsplan och volymuppskattning.
|
|
|
|
```json
|
|
{
|
|
"name": "Skogsbruksplan — Åtgärdsförslag",
|
|
"type": "pivot",
|
|
"description": "Åtgärdsförslag från skogsbruksplaner sorterade på prioritet och tidsplan",
|
|
"dataSourceId": "ds-landvex-forestry",
|
|
"config": {
|
|
"pivot": {
|
|
"rows": ["action_type", "forest_stand_id"],
|
|
"columns": ["planned_year"],
|
|
"values": [
|
|
{ "field": "estimated_volume", "aggregation": "sum", "format": "number" },
|
|
{ "field": "estimated_cost", "aggregation": "sum", "format": "currency" }
|
|
]
|
|
},
|
|
"sourceQuery": "SELECT fa.action_type, fa.forest_stand_id, fa.planned_year, fa.estimated_volume_m3 as estimated_volume, fa.estimated_cost, fa.priority, fa.status FROM forestry_actions fa JOIN forest_stands fs ON fa.forest_stand_id = fs.id WHERE fs.municipality_id = :municipalityId AND fa.planned_year BETWEEN :fromYear AND :toYear",
|
|
"detailTable": {
|
|
"title": "Åtgärdslista",
|
|
"query": "SELECT fa.id, fa.action_type, fs.stand_name, fa.planned_year, fa.estimated_volume_m3, fa.estimated_cost, fa.priority, fa.status, fa.notes FROM forestry_actions fa JOIN forest_stands fs ON fa.forest_stand_id = fs.id WHERE fs.municipality_id = :municipalityId AND fa.planned_year BETWEEN :fromYear AND :toYear ORDER BY fa.priority, fa.planned_year",
|
|
"columns": [
|
|
{ "field": "action_type", "header": "Åtgärd" },
|
|
{ "field": "stand_name", "header": "Bestånd" },
|
|
{ "field": "planned_year", "header": "Planerat år" },
|
|
{ "field": "estimated_volume_m3", "header": "Volym (m³)", "format": "number" },
|
|
{ "field": "estimated_cost", "header": "Kostnad (kr)", "format": "currency" },
|
|
{ "field": "priority", "header": "Prioritet" },
|
|
{ "field": "status", "header": "Status" }
|
|
]
|
|
},
|
|
"parameters": [
|
|
{
|
|
"name": "municipalityId",
|
|
"type": "string",
|
|
"required": true,
|
|
"source": "tenant.config.municipality_id"
|
|
},
|
|
{
|
|
"name": "fromYear",
|
|
"type": "integer",
|
|
"required": false,
|
|
"default": "current_year",
|
|
"label": "Från år"
|
|
},
|
|
{
|
|
"name": "toYear",
|
|
"type": "integer",
|
|
"required": false,
|
|
"default": "current_year + 5",
|
|
"label": "Till år"
|
|
}
|
|
],
|
|
"defaultFormat": "xlsx"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Arkitektur & Integration
|
|
|
|
### 4.1 Komponentdiagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ LandveX Enterprise Platform │
|
|
│ │
|
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
│ │ Web UI │ │ Mobile App │ │ API-klient │ │
|
|
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
|
│ │ │ │ │
|
|
│ └──────────────────┼──────────────────┘ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────┐ │
|
|
│ │ Reporting API │ │
|
|
│ │ (REST + Auth/tenant) │ │
|
|
│ └───────────┬─────────────┘ │
|
|
│ │ │
|
|
│ ┌────────────────┼────────────────┐ │
|
|
│ ▼ ▼ ▼ │
|
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
│ │ Scheduler │ │ Query Eng. │ │ Export Svc │ │
|
|
│ │ (BullMQ) │ │ (PostgREST)│ │ (CSV/XLSX/ │ │
|
|
│ │ │ │ (Direct) │ │ PDF/JSON) │ │
|
|
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
|
│ │ │ │ │
|
|
│ ▼ ▼ ▼ │
|
|
│ ┌─────────────────────────────────────────────────┐ │
|
|
│ │ LandveX Datakällor │ │
|
|
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐│ │
|
|
│ │ │GIS/PostGIS│ │Transakt.│ │Skogsbr. │ │ Extern ││ │
|
|
│ │ │ │ │ DB │ │ DB │ │ API ││ │
|
|
│ │ └─────────┘ └─────────┘ └─────────┘ └────────┘│ │
|
|
│ └─────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────┐ │
|
|
│ │ Delning & Notifikationer │ │
|
|
│ │ • E-post (SendGrid/AWS SES) │ │
|
|
│ │ • Länk-baserad delning (token) │ │
|
|
│ │ • Inbäddad rapport (iframe) │ │
|
|
│ └─────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 4.2 Scheduler-flöde (BullMQ)
|
|
|
|
```
|
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
│ Cron-jobb │────►│ Query Engine │────►│ Store result│
|
|
│ (var 1 min) │ │ (timeout 5m) │ │ (S3/DB) │
|
|
└──────────────┘ └──────────────┘ └──────────────┘
|
|
│
|
|
▼
|
|
┌──────────────┐
|
|
│ Notifiera │
|
|
│ (e-post/ │
|
|
│ webhook) │
|
|
└──────────────┘
|
|
```
|
|
|
|
### 4.3 Exempel: Köra rapport via API
|
|
|
|
```bash
|
|
# 1. Skapa rapport
|
|
curl -X POST https://api.landvex.se/v1/reports \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d @fastighetsöversikt.json
|
|
|
|
# 2. Kör rapport
|
|
curl -X POST https://api.landvex.se/v1/reports/$REPORT_ID/run \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-d '{"format": "pdf", "parameters": {"municipalityId": "0180"}}'
|
|
# → { "id": "run-uuid", "status": "pending" }
|
|
|
|
# 3. Polling tills klar
|
|
curl https://api.landvex.se/v1/reports/$REPORT_ID/runs/$RUN_ID \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
# → { "status": "completed", "resultUrl": "https://cdn.landvex.se/..." }
|
|
|
|
# 4. Ladda ner resultat
|
|
curl -O "$RESULT_URL"
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Säkerhet & Rättigheter
|
|
|
|
| Nivå | Behörighet |
|
|
|------|-----------|
|
|
| `read` | Se rapport och resultat |
|
|
| `write` | Redigera config, köra manuellt |
|
|
| `admin` | Ändra schema, dela, ta bort |
|
|
|
|
- **Tenant-isolering:** Alla queries filtreras automatiskt på `tenant_id`
|
|
- **Parametriserade queries:** Inga strängkonkateneringar — använd prepared statements
|
|
- **Resultat-URL:er:** Tidsbegränsade signerade URL:er (S3 presigned, 1h default)
|
|
- **Audit-logg:** Alla körningar och exporter loggas med `user_id` och `timestamp`
|
|
|
|
---
|
|
|
|
## 6. Leverabler
|
|
|
|
| Fil | Innehåll |
|
|
|-----|----------|
|
|
| `DESIGN.md` | Denna fil — översikt och design |
|
|
| `schema.sql` | PostgreSQL DDL |
|
|
| `openapi.yaml` | Fullständig OpenAPI-spec |
|
|
| `examples/` | 3 standardrapporter (JSON) |
|
|
| `architecture.md` | Djupare arkitekturbeskrivning |
|