docs: add PEJ section design spec

Hybrid "lens" approach — PEJ section filters existing ZOPK data
by nuclear project IDs, adding dedicated routes, templates, and
CSV export without new database tables.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-16 10:47:36 +01:00
parent 39f1d33f44
commit 9ab41b13cb

View File

@ -0,0 +1,237 @@
# PEJ Section — Dedicated Nuclear Energy Space on nordabiznes.pl
**Date:** 2026-03-16
**Status:** Draft
**Approach:** Hybrid — "soczewka" na dane ZOPK + własne treści PEJ
## Problem
Izba Norda Biznes aktywnie angażuje się w projekt PEJ (Polskie Elektrownie Jądrowe). Po spotkaniu z dyrektorem PEJ ds. local content (Grzegorz Maj) pojawiła się pilna potrzeba:
1. Dostarczenia PEJ listy firm członkowskich z kontaktami i branżami
2. Motywowania członków do uzupełnienia profili na portalu
3. Śledzenia aktualności i postępów projektu nuklearnego
4. Wydzielenia PEJ z ogólnej sekcji ZOPK — PEJ to strategiczny kierunek Izby, nie tylko jeden z projektów Kaszubia
## Architecture
### Hybrid "Lens" Approach
PEJ section **nie tworzy nowych tabel** — konsumuje istniejące dane ZOPK, filtrując po projektach nuklearnych. Dodaje jedynie:
- Nowe routes i templates pod `/pej`
- Routes w istniejących blueprintach `public` i `admin` (spójnie z wzorcem ZOPK)
- Kategorię `pej` w systemie ogłoszeń
- Endpoint eksportu CSV firm dla PEJ
```
┌─────────────────────────────────────────────────┐
│ ZOPK Pipeline │
│ (news fetch → scrape → extract → embed) │
│ Zbiera dane: PEJ, offshore, Kongsberg, H2... │
└──────────────────┬──────────────────────────────┘
│ filtruje po project_id
│ (nuclear-plant) i category='nuclear'
┌─────────────────────────────────────────────────┐
│ Sekcja PEJ (/pej) │
│ Landing page + Local Content + Aktualności │
│ + Ogłoszenia Izby (kategoria 'pej') │
└─────────────────────────────────────────────────┘
```
### Data Sources — Co skąd ciągniemy
| Dane | Źródło ZOPK | Filtr | Join path |
|------|-------------|-------|-----------|
| Aktualności | `zopk_news` | `project_id IN (nuclear_project_ids)` | bezpośredni |
| Milestones/Timeline | `zopk_milestones` | `category = 'nuclear'` | bezpośredni |
| Firmy dostawcy | `zopk_company_links` | `project_id IN (nuclear_project_ids)` | bezpośredni |
| Fakty wiedzy | `zopk_knowledge_facts` | `source_news_id → zopk_news.project_id` | 1 JOIN przez `source_news_id` |
| Encje (Westinghouse, Bechtel...) | `zopk_knowledge_entities` | `zopk_project_id IN (nuclear_project_ids)` | bezpośredni |
### Nuclear Project ID Resolution
```python
NUCLEAR_PROJECT_SLUGS = ['nuclear-plant'] # explicite lista, łatwa do rozszerzenia o SMR
def get_nuclear_project_ids(db_session):
"""Zwraca ID projektów nuklearnych z ZOPK."""
projects = db_session.query(ZOPKProject.id).filter(
ZOPKProject.slug.in_(NUCLEAR_PROJECT_SLUGS),
ZOPKProject.project_type == 'energy'
).all()
return [p.id for p in projects]
```
Filtr po explicit slug list + `project_type = 'energy'` — bezpieczny przed przypadkowym dopasowaniem.
### Ogłoszenia Izby (np. notatki ze spotkań z PEJ)
Wykorzystujemy istniejący model `Announcement` z nową kategorią `pej`. Treści jak notatka WhatsApp o spotkaniu z Grzegorzem Majem → ogłoszenie z `category = 'pej'`, widoczne na stronie PEJ.
**Wymagane zmiany kodu (bez migracji SQL):**
- `database.py``Announcement.CATEGORIES` list — dodać `'pej'`
- `database.py``Announcement.CATEGORY_LABELS` dict — dodać `'pej': 'PEJ / Energetyka jądrowa'`
- Template `admin/announcements_form.html` renderuje checkboxy z `CATEGORIES` automatycznie — brak zmian
## Routes
### Public Routes
Wymagają `@login_required` — w odróżnieniu od ZOPK (publiczne), PEJ zawiera dane kontaktowe firm (email, telefon) w sekcji Local Content, dlatego wymaga logowania.
| Route | Funkcja | Opis |
|-------|---------|------|
| `GET /pej` | `pej_index()` | Landing page — hero, stats, najnowsze aktualności, timeline nuklearny, top 6 firm Local Content, ogłoszenia Izby |
| `GET /pej/local-content` | `pej_local_content()` | Pełna lista firm z matchingiem do PEJ — filtry po branży, typie współpracy, score |
| `GET /pej/aktualnosci` | `pej_news()` | Aktualności nuklearne (paginacja, z ZOPK news filtered) |
### Admin Routes
| Route | Funkcja | Opis |
|-------|---------|------|
| `GET /admin/pej/export` | `pej_export_csv()` | Eksport CSV firm: nazwa, email kontaktowy, branża, PKD, usługi, typ współpracy, opis, score |
### File Structure (spójne z wzorcem ZOPK)
```
blueprints/public/routes_pej.py # 3 public routes (jak routes_zopk.py)
blueprints/admin/routes_pej.py # 1 admin export route (jak routes_zopk_*.py)
templates/pej/
├── index.html # Landing page
├── local_content.html # Lista firm
└── news.html # Aktualności nuklearne
```
Rejestracja w `app.py` — import routes w odpowiednich blueprintach (public, admin).
## Templates
### 1. `templates/pej/index.html` — Landing Page
```
┌─────────────────────────────────────────────────────┐
│ HERO: "Elektrownia Jądrowa — Szanse dla Naszych │
│ Firm" │
│ Krótki opis: Izba Norda Biznes aktywnie uczestniczy │
│ w projekcie PEJ. Tu znajdziesz aktualności, │
│ listę firm gotowych do współpracy i informacje │
│ o możliwościach dla członków. │
├─────────────────────────────────────────────────────┤
│ STATS BAR: [X firm gotowych] [Y aktualności] │
│ [Z milestones] │
├─────────────────────────────────────────────────────┤
│ OGŁOSZENIA IZBY (kategoria 'pej') │
│ Np. notatka o spotkaniu z PEJ │
├──────────────────────┬──────────────────────────────┤
│ AKTUALNOŚCI (3-4) │ TIMELINE nuklearny │
│ Najnowsze newsy │ (milestones category= │
│ z ZOPK nuclear │ nuclear) │
│ [Zobacz wszystkie →] │ │
├──────────────────────┴──────────────────────────────┤
│ LOCAL CONTENT — Firmy z Izby gotowe do współpracy │
│ Top 6 firm (wg relevance_score) │
│ Każda karta: nazwa, branża, typ współpracy, opis │
│ [Zobacz pełną listę →] [Eksportuj CSV →] │
└─────────────────────────────────────────────────────┘
```
Kolorystyka: odróżniona od ZOPK (zielony). PEJ → fioletowo-niebieski (#7c3aed / #4f46e5) — nawiązanie do koloru projektu `nuclear-plant` w bazie.
### 2. `templates/pej/local_content.html` — Lista Firm
```
┌─────────────────────────────────────────────────────┐
│ HEADER: "Local Content — Firmy Izby Norda dla PEJ" │
│ [Eksportuj CSV] │
├─────────────────────────────────────────────────────┤
│ FILTRY: [Branża ▼] [Typ współpracy ▼] [Szukaj...] │
├─────────────────────────────────────────────────────┤
│ LISTA FIRM (karty): │
│ ┌───────────────────────────────────────────────┐ │
│ │ Logo | Nazwa firmy | Score: 85/100 │ │
│ │ | Branża: IT, Services | Dostawca ⚡ │ │
│ │ | Opis współpracy z AI matching │ │
│ │ | Email: kontakt@firma.pl │ │
│ │ | [Zobacz profil →] │ │
│ └───────────────────────────────────────────────┘ │
│ ... (paginacja) │
└─────────────────────────────────────────────────────┘
```
Sortowanie domyślne: `relevance_score DESC`. Filtry:
- Branża (z `Company.category`)
- Typ współpracy (z `ZOPKCompanyLink.link_type`)
- Szukaj (po nazwie firmy)
Wyświetlane firmy: `ZOPKCompanyLink.relevance_score >= 25` (próg z matchingu AI), wszystkie statusy (suggested, confirmed, active) — na tym etapie nie rozróżniamy, bo wszystkie linki pochodzą z AI matchingu.
### 3. `templates/pej/news.html` — Aktualności
Analogicznie do `zopk/news_list.html`, ale z filtrem na projekt nuklearny. Bez filtra po projekcie (bo wszystko jest już nuklearne).
## CSV Export
Endpoint: `GET /admin/pej/export` (wymaga admin/OFFICE_MANAGER)
Kolumny:
```
Nazwa firmy;Email;Telefon;Branża;PKD (główny);Usługi;Typ współpracy PEJ;Opis współpracy;Score;Miasto
```
Źródło: `ZOPKCompanyLink JOIN Company WHERE project_id IN (nuclear_ids) AND relevance_score >= 25` — sortowane po score DESC.
Kodowanie: UTF-8 BOM (dla Excela). Separator: `;` (standard PL).
Filename: `pej-local-content-YYYY-MM-DD.csv`. MIME: `text/csv; charset=utf-8`.
## Navigation
Dodajemy link "PEJ" w nawigacji `base.html`:
**Dla zwykłych zalogowanych użytkowników:**
- Obok istniejącego linku "Kaszubia" dodajemy "PEJ" → `/pej`
- Oba linki jako osobne elementy w top nav (nawigacja nie jest jeszcze przepełniona)
**Dla admina (w dropdown "Więcej"):**
- Dodajemy "PEJ" pod "Kaszubia" w tym samym dropdown
**Mobile:**
- Oba linki w hamburger menu, pod sobą
## Data Model Changes
### Brak nowych tabel
Cała sekcja PEJ operuje na istniejących modelach ZOPK.
### Announcement category (zmiana w kodzie, bez migracji)
Dwa miejsca w `database.py`:
1. `Announcement.CATEGORIES` — dodać `'pej'` do listy
2. `Announcement.CATEGORY_LABELS` — dodać `'pej': 'PEJ / Energetyka jądrowa'`
Kolumna `categories` to `ARRAY(String)` bez CHECK constraint — brak potrzeby migracji SQL.
## WhatsApp Content Strategy
Treść notatki WhatsApp o spotkaniu z PEJ:
1. **Ogłoszenie na portalu**`Announcement` z `category=['pej']`, widoczne na `/pej` i `/ogloszenia`
2. **Hero content na landing page** — wyróżniony blok "Ostatnie spotkanie z PEJ" na stronie `/pej`
3. **Motywacja do uzupełnienia profili** — CTA na stronie PEJ: "Uzupełnij profil firmy, aby znaleźć się na liście Local Content"
## Scope Exclusions (YAGNI)
Celowo **nie** robimy teraz:
- Formularz deklaracji gotowości firmy do PEJ (wystarczy matching AI)
- Integracja NordaGPT z filtrem PEJ-only (chatbot i tak rozpoznaje pytania o PEJ)
- Osobny pipeline news dla PEJ (ZOPK pipeline już zbiera te dane)
- PDF export (CSV wystarczy na potrzeby PEJ)
- Osobna sekcja dla SMR / OSGE (przyszłościowo — dodanie slugów do `NUCLEAR_PROJECT_SLUGS`)
- Dropdown "Projekty" grupujący Kaszubia + PEJ (nie potrzebny przy 2 linkach)
## Testing
- Weryfikacja na staging.nordabiznes.pl przed wdrożeniem na produkcję
- Sprawdzenie czy firmy z matchingiem nuklearnym wyświetlają się poprawnie
- Test eksportu CSV — otwarcie w Excel, weryfikacja polskich znaków (UTF-8 BOM)
- Test nawigacji — link PEJ widoczny dla zalogowanych
- Test responsywności — mobile, tablet