Zum Inhalt springen

Project Logo

WIdO - Wissenschaftliches Institut der AOK
Leichte SpracheVorlesen
WIdO - Wissenschaftliches Institut der AOK
Leichte SpracheVorlesen

Architektur

Überblick über Aufbau und technische Realisierung des Portals. Ziel ist, dass neue Teammitglieder schnell verstehen, wo welche Logik liegt und wie Daten von der Datenbank bis in die Charts gelangen.


Systemübersicht

PharMaAnalyst besteht aus einem Next.js Frontend (Pages Router) mit API-Routen für Datenzugriffe. In Produktion läuft die App hinter einem Nginx Reverse Proxy; im Backend wird eine PostgreSQL Datenbank verwendet.

High-level Architektur

flowchart LR
  U[User / Browser] -->|HTTPS| N[Nginx Reverse Proxy]
  N -->|HTTP| A["Next.js (Frontend + API Routes)"]
  A -->|SQL| DB[(PostgreSQL)]
  A -->|JSON| U
Datenzugriff

Analyse-Requests laufen primär über frontend/pages/api/data.js (Medikament/Wirkstoff) sowie separate Endpunkte für Ranking/Hersteller. Die API liefert neben den eigentlichen Daten auch die effektiv verwendeten Parameter zurück.

Defaults (Auto-Load)

Beim Klick auf eine Analyse-Seite wird automatisch ein interessanter Default-Datensatz geladen. Best Practice: Die Default-Policy ist zentral und serverseitig, damit Frontend und Export konsistent bleiben.

- Default-Policy/Queries: frontend/lib/defaults.js
- Default-Resolution + Kennzeichnung: frontend/pages/api/data.js
- Frontend Auto-Load Trigger: frontend/pages/index.js

Default-Load Datenfluss

sequenceDiagram
  autonumber
  participant UI as UI (Next.js)
  participant API as /api/data
  participant Defaults as defaults.js
  participant DB as PostgreSQL

  UI->>API: GET /api/data?mode=medikament (ohne Auswahl/Zeitraum)
  API->>Defaults: last full year + default selection
  Defaults->>DB: Aggregation queries (Top 10 / ATC L1 Top)
  DB-->>Defaults: Default IDs / ATC + Zeitraum
  Defaults-->>API: resolved defaults
  API->>DB: Data queries (summary/timeseries/...)
  DB-->>API: result rows
  API-->>UI: JSON + resolvedParams + defaultsApplied
Deployment (Prod)

Die Produktions-Instanz wird als Next.js Standalone über PM2 gestartet. Der Wrapper stellt sicher, dass Umgebungsvariablen geladen werden und statische Assets verfügbar bleiben:
frontend/scripts/pm2-standalone.js

Weiterführend

Details zu Default-Datensätzen und Zeiträumen sind zusätzlich im Repository dokumentiert: README (Defaults)

Hinweis zu Mermaid: Die Diagramme werden client-seitig gerendert und nur auf dieser Seite lazy-loaded. Das ist in der Regel nicht ressourcenlastig, solange die Diagramme klein bleiben.

ER-Diagramm (Datenbank)

Snapshot des aktuellen Schemas (Stand: 2025-12-13, DB: pad). PK/FK markieren Primary Key und Foreign Key (NOT NULL wird hier nicht extra annotiert).

ER-Diagramm (PostgreSQL)

erDiagram
  age_groups {
    int age_group_id PK
    text age_group_description
  }

  aggregated_data {
    int data_id PK
    int medication_id FK
    int year
    int month
    int region_id FK
    int age_group_id FK
    int gender_id FK
    numeric verordnungen
    numeric nettokosten
    numeric tagesdosen_ddd
    numeric kosten_je_verordnung
    numeric kosten_je_tagesdosis
    timestamp loaded_at
  }

  atc_codes {
    text atc_code PK
    text description
    int level
    text parent_atc_code FK
  }

  genders {
    int gender_id PK
    text gender_description
  }

  market_totals {
    int id PK
    int year
    int month
    text market_segment
    text atc_level1
    numeric total_verordnungen
    numeric total_nettokosten
    numeric total_ddd
    int medication_count
    timestamp created_at
  }

  medications {
    int medication_id PK
    text pzn
    text product_name
    text manufacturer
    text atc_code FK
    boolean is_generic
    text market_type
  }

  physician_groups {
    int physician_group_id PK
    text group_code
    text group_name
    text category
    int sort_order
  }

  physician_prescriptions {
    int id PK
    int medication_id FK
    int physician_group_id FK
    int year
    int month
    numeric verordnungen
    numeric nettokosten
    numeric tagesdosen_ddd
    timestamp created_at
  }

  regions {
    int region_id PK
    text region_code
    text region_name
  }

  age_groups ||--o{ aggregated_data : "age_group_id"
  genders ||--o{ aggregated_data : "gender_id"
  medications ||--o{ aggregated_data : "medication_id"
  regions ||--o{ aggregated_data : "region_id"
  atc_codes ||--o{ atc_codes : "parent_atc_code"
  atc_codes ||--o{ medications : "atc_code"
  medications ||--o{ physician_prescriptions : "medication_id"
  physician_groups ||--o{ physician_prescriptions : "physician_group_id"