fix(admin): Naprawiono błędne nazwy endpointów w breadcrumbs

Zmieniono admin_dashboard i admin_zopk_dashboard na admin_zopk

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-01-17 09:03:01 +01:00
parent d0dda10bd7
commit 6d1f75bce5
6 changed files with 388 additions and 9 deletions

View File

@ -0,0 +1,288 @@
# Plan rozwoju bazy wiedzy ZOPK
**Data utworzenia:** 2026-01-17
**Status:** W trakcie realizacji
## Stan obecny (przed rozpoczęciem)
| Komponent | Status | Wartość |
|-----------|--------|---------|
| Artykuły scraped | ✅ | 132/163 (81%) |
| Artykuły extracted | ✅ | 120/132 (91%) |
| Chunks | ✅ | 412 |
| Fakty | ✅ | 3,414 |
| Encje | ✅ | 1,309 |
| Embeddings | ✅ | 412/412 (100%) |
| Integracja NordaGPT | ✅ | Semantic search działa |
| Automatyczny pipeline | ✅ | Cron co 1h |
---
## Priorytet 1: Panel admina bazy wiedzy
**Status:** 🔄 W trakcie
**Szacowany czas:** 1-2 dni
### Cel
Dashboard w `/admin/zopk/knowledge` z podglądem i zarządzaniem bazą wiedzy.
### Wymagane funkcje
1. **Dashboard główny** (`/admin/zopk/knowledge`)
- Statystyki: chunks, fakty, encje, embeddings
- Wykresy: ekstrakcje w czasie, top encje
- Przyciski szybkich akcji (scraping, ekstrakcja, embeddings)
2. **Lista chunks** (`/admin/zopk/knowledge/chunks`)
- Tabela z paginacją
- Filtrowanie po artykule źródłowym
- Podgląd treści chunk
- Edycja/usunięcie chunk
- Status embeddingu (✓/✗)
3. **Lista faktów** (`/admin/zopk/knowledge/facts`)
- Tabela z paginacją
- Filtrowanie po typie faktu (financial, date, decision, etc.)
- Weryfikacja faktu (✓ zweryfikowany / ✗ błędny)
- Edycja faktu
- Link do źródłowego artykułu
4. **Lista encji** (`/admin/zopk/knowledge/entities`)
- Tabela z paginacją
- Filtrowanie po typie (company, person, place, project)
- Liczba wzmianek
- Możliwość łączenia duplikatów (Priorytet 4)
- Edycja opisu encji
5. **Szczegóły artykułu** (`/admin/zopk/knowledge/article/<id>`)
- Pełna treść scraped
- Lista wyekstraktowanych chunks
- Lista wyekstraktowanych faktów
- Lista encji
- Możliwość re-ekstrakcji
### Pliki do utworzenia
- `templates/admin/zopk_knowledge_dashboard.html`
- `templates/admin/zopk_knowledge_chunks.html`
- `templates/admin/zopk_knowledge_facts.html`
- `templates/admin/zopk_knowledge_entities.html`
### Pliki do modyfikacji
- `app.py` - nowe endpointy
- `zopk_knowledge_service.py` - metody CRUD
---
## Priorytet 2: Poprawa jakości odpowiedzi
**Status:** ⏳ Oczekuje
**Szacowany czas:** 1 dzień
### Cel
Lepsze formatowanie odpowiedzi NordaGPT z linkami do źródeł.
### Wymagane zmiany
1. **Linki do źródeł**
- Zamiast: "Według Google News z 31 grudnia 2025..."
- Ma być: "Według [trojmiasto.pl](https://trojmiasto.pl/...) z 31 grudnia 2025..."
2. **Formatowanie odpowiedzi**
- Nagłówki dla sekcji
- Listy wypunktowane dla wielu faktów
- Wyróżnienie kluczowych danych (pogrubienie)
3. **Pewność odpowiedzi**
- Gdy brak danych w bazie: "Nie mam informacji na ten temat w bazie wiedzy ZOPK"
- Gdy dane niepewne: "Według dostępnych informacji (pewność: średnia)..."
4. **Źródła na końcu odpowiedzi**
```
📚 Źródła:
- [trojmiasto.pl] Kongsberg zainwestuje na Pomorzu (2025-11-23)
- [defence24.pl] Norweska fabryka w Rumi (2025-11-25)
```
### Pliki do modyfikacji
- `nordabiz_chat.py` - metoda `_get_zopk_knowledge_context()`
- `nordabiz_chat.py` - system prompt dla ZOPK
---
## Priorytet 3: Timeline ZOPK na stronie /zopk
**Status:** ⏳ Oczekuje
**Szacowany czas:** 2-3 dni
### Cel
Wizualna oś czasu projektu ZOPK pokazująca kamienie milowe.
### Wymagane funkcje
1. **Tabela `zopk_milestones`**
```sql
CREATE TABLE zopk_milestones (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
milestone_type VARCHAR(50), -- announcement, decision, construction, completion
target_date DATE,
actual_date DATE,
status VARCHAR(20), -- planned, in_progress, completed, delayed
source_news_id INTEGER REFERENCES zopk_news(id),
source_fact_id INTEGER REFERENCES zopk_knowledge_facts(id),
icon VARCHAR(50),
color VARCHAR(7),
is_featured BOOLEAN DEFAULT FALSE,
display_order INTEGER,
created_at TIMESTAMP DEFAULT NOW()
);
```
2. **Automatyczne tworzenie milestones**
- Ekstrakcja dat z faktów (np. "budowa rozpocznie się w 2027")
- Kategoryzacja: announcement, decision, construction_start, completion
- Przypisanie do projektów (Kongsberg, PEJ, Baltic Power, Via Pomerania)
3. **UI Timeline na /zopk**
- Pionowa oś czasu (scrollowalna)
- Kamienie milowe z ikonami
- Kolory: zielony (completed), niebieski (in_progress), szary (planned)
- Kliknięcie → szczegóły z linkiem do źródła
4. **Panel admina dla milestones**
- `/admin/zopk/milestones`
- Dodawanie/edycja/usuwanie
- Zmiana kolejności (drag & drop)
### Pliki do utworzenia
- `database/migrations/016_zopk_milestones.sql`
- `templates/admin/zopk_milestones.html`
### Pliki do modyfikacji
- `database.py` - model ZOPKMilestone
- `app.py` - endpointy
- `templates/zopk.html` - sekcja timeline
---
## Priorytet 4: Łączenie duplikatów encji
**Status:** ⏳ Oczekuje
**Szacowany czas:** 1 dzień
### Cel
Automatyczne i ręczne łączenie zduplikowanych encji.
### Znane duplikaty do połączenia
| Encja główna | Duplikaty | Łączna liczba wzmianek |
|--------------|-----------|------------------------|
| Morze Bałtyckie | Bałtyk | 58 + 53 = 111 |
| Polskie Elektrownie Jądrowe | PEJ | ? |
| Władysław Kosiniak-Kamysz | Kosiniak-Kamysz, Wicepremier | ? |
| Kongsberg Defence & Aerospace | Kongsberg | ? |
### Wymagane funkcje
1. **Automatyczne wykrywanie duplikatów**
- Fuzzy matching nazw (pg_trgm similarity > 0.7)
- Porównanie typów encji
- Sugestie do zatwierdzenia przez admina
2. **Panel łączenia** (`/admin/zopk/knowledge/entities/merge`)
- Lista sugerowanych duplikatów
- Wybór encji głównej
- Przycisk "Połącz" → aktualizacja wszystkich referencji
3. **Aliasy encji**
- Tabela `zopk_entity_aliases`
- Encja "Polskie Elektrownie Jądrowe" ma alias "PEJ"
- Wyszukiwanie uwzględnia aliasy
### Pliki do utworzenia
- `database/migrations/017_zopk_entity_aliases.sql`
### Pliki do modyfikacji
- `database.py` - model ZOPKEntityAlias
- `zopk_knowledge_service.py` - metody merge_entities(), get_entity_duplicates()
- `app.py` - endpointy
---
## Priorytet 5: Graf relacji encji
**Status:** ⏳ Oczekuje
**Szacowany czas:** 2-3 dni
### Cel
Wizualizacja powiązań między encjami (podobna do Mapy Powiązań firm).
### Typy relacji do ekstrakcji
| Relacja | Przykład |
|---------|----------|
| `invests_in` | Kongsberg → Rumia Invest Park |
| `manages` | MON → ZOPK |
| `builds` | PEJ → Elektrownia Lubiatowo |
| `located_in` | Baltic Power → Morze Bałtyckie |
| `partners_with` | Westinghouse → Bechtel |
| `employs` | Kongsberg → 500 osób |
### Wymagane funkcje
1. **Ekstrakcja relacji z faktów**
- Analiza AI faktów typu: "Kongsberg zainwestuje w Rumi"
- Prompt: "Wyodrębnij relacje: subject → predicate → object"
2. **Wypełnienie tabeli `zopk_knowledge_relations`**
- Tabela już istnieje (obecnie pusta)
- Kolumny: source_entity_id, target_entity_id, relation_type, confidence_score
3. **Wizualizacja grafu** (`/zopk/graph` lub modal na /zopk)
- Użycie D3.js lub vis.js (jak Mapa Powiązań)
- Węzły = encje (kolorowane po typie)
- Krawędzie = relacje (etykiety)
- Filtrowanie po typie relacji
4. **Panel admina** (`/admin/zopk/knowledge/relations`)
- Lista relacji z paginacją
- Dodawanie/edycja/usuwanie
- Weryfikacja (✓/✗)
### Pliki do modyfikacji
- `zopk_knowledge_service.py` - metoda extract_relations()
- `app.py` - endpointy
- `templates/zopk.html` - sekcja graf lub modal
---
## Harmonogram realizacji
| Priorytet | Zadanie | Szacowany czas | Status |
|-----------|---------|----------------|--------|
| 1 | Panel admina bazy wiedzy | 1-2 dni | 🔄 W trakcie |
| 2 | Poprawa jakości odpowiedzi | 1 dzień | ⏳ Oczekuje |
| 3 | Timeline ZOPK | 2-3 dni | ⏳ Oczekuje |
| 4 | Łączenie duplikatów encji | 1 dzień | ⏳ Oczekuje |
| 5 | Graf relacji encji | 2-3 dni | ⏳ Oczekuje |
**Łączny szacowany czas:** 7-10 dni
---
## Notatki techniczne
### Istniejące tabele bazy wiedzy
- `zopk_knowledge_chunks` - fragmenty tekstu z embeddingami
- `zopk_knowledge_facts` - wyekstraktowane fakty
- `zopk_knowledge_entities` - rozpoznane encje
- `zopk_knowledge_relations` - relacje między encjami (pusta)
### Istniejące serwisy
- `zopk_knowledge_service.py` - ekstrakcja i wyszukiwanie
- `zopk_content_scraper.py` - scraping treści artykułów
### Istniejący pipeline (cron)
- `scripts/zopk_knowledge_pipeline.py` - uruchamiany co godzinę
- Logi: `/var/log/nordabiznes/knowledge_pipeline.log`

91
scripts/run_migration.py Normal file
View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""
Run SQL migrations using DATABASE_URL from .env
Usage:
python scripts/run_migration.py database/migrations/016_zopk_milestones.sql
python scripts/run_migration.py database/migrations/*.sql # Run all
"""
import os
import sys
import glob
from pathlib import Path
# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))
def load_env():
"""Load .env file from project root"""
env_path = Path(__file__).parent.parent / '.env'
if env_path.exists():
with open(env_path, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
os.environ[key] = value.strip()
if not os.environ.get('DATABASE_URL'):
print("ERROR: DATABASE_URL not found in .env")
sys.exit(1)
def run_migration(sql_file: str) -> bool:
"""Run a single SQL migration file"""
from sqlalchemy import create_engine, text
if not os.path.exists(sql_file):
print(f"ERROR: File not found: {sql_file}")
return False
print(f"Running migration: {sql_file}")
try:
engine = create_engine(os.environ['DATABASE_URL'])
with engine.connect() as conn:
with open(sql_file, 'r') as f:
sql = f.read()
conn.execute(text(sql))
conn.commit()
print(f"{sql_file} - SUCCESS")
return True
except Exception as e:
print(f"{sql_file} - FAILED: {e}")
return False
def main():
if len(sys.argv) < 2:
print(__doc__)
sys.exit(1)
load_env()
# Handle glob patterns
sql_files = []
for arg in sys.argv[1:]:
if '*' in arg:
sql_files.extend(sorted(glob.glob(arg)))
else:
sql_files.append(arg)
if not sql_files:
print("No migration files found")
sys.exit(1)
print(f"Found {len(sql_files)} migration(s) to run\n")
success = 0
failed = 0
for sql_file in sql_files:
if run_migration(sql_file):
success += 1
else:
failed += 1
print(f"\n{'='*50}")
print(f"Results: {success} success, {failed} failed")
sys.exit(0 if failed == 0 else 1)
if __name__ == '__main__':
main()

View File

@ -323,9 +323,9 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="breadcrumb"> <div class="breadcrumb">
<a href="{{ url_for('admin_dashboard') }}">Panel Admina</a> <a href="{{ url_for('admin_zopk') }}">Panel Admina</a>
<span></span> <span></span>
<a href="{{ url_for('admin_zopk_dashboard') }}">ZOP Kaszubia</a> <a href="{{ url_for('admin_zopk') }}">ZOP Kaszubia</a>
<span></span> <span></span>
<a href="{{ url_for('admin_zopk_knowledge_dashboard') }}">Baza Wiedzy</a> <a href="{{ url_for('admin_zopk_knowledge_dashboard') }}">Baza Wiedzy</a>
<span></span> <span></span>

View File

@ -277,9 +277,9 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="breadcrumb"> <div class="breadcrumb">
<a href="{{ url_for('admin_dashboard') }}">Panel Admina</a> <a href="{{ url_for('admin_zopk') }}">Panel Admina</a>
<span></span> <span></span>
<a href="{{ url_for('admin_zopk_dashboard') }}">ZOP Kaszubia</a> <a href="{{ url_for('admin_zopk') }}">ZOP Kaszubia</a>
<span></span> <span></span>
<span>Baza Wiedzy</span> <span>Baza Wiedzy</span>
</div> </div>
@ -360,7 +360,7 @@
<div class="quick-link-desc">Wektory dla chunków bez embeddingów</div> <div class="quick-link-desc">Wektory dla chunków bez embeddingów</div>
</div> </div>
</div> </div>
<a href="{{ url_for('admin_zopk_dashboard') }}" class="quick-link"> <a href="{{ url_for('admin_zopk') }}" class="quick-link">
<div class="quick-link-icon">📊</div> <div class="quick-link-icon">📊</div>
<div class="quick-link-text"> <div class="quick-link-text">
<div class="quick-link-title">Dashboard ZOPK</div> <div class="quick-link-title">Dashboard ZOPK</div>

View File

@ -253,9 +253,9 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="breadcrumb"> <div class="breadcrumb">
<a href="{{ url_for('admin_dashboard') }}">Panel Admina</a> <a href="{{ url_for('admin_zopk') }}">Panel Admina</a>
<span></span> <span></span>
<a href="{{ url_for('admin_zopk_dashboard') }}">ZOP Kaszubia</a> <a href="{{ url_for('admin_zopk') }}">ZOP Kaszubia</a>
<span></span> <span></span>
<a href="{{ url_for('admin_zopk_knowledge_dashboard') }}">Baza Wiedzy</a> <a href="{{ url_for('admin_zopk_knowledge_dashboard') }}">Baza Wiedzy</a>
<span></span> <span></span>

View File

@ -243,9 +243,9 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="breadcrumb"> <div class="breadcrumb">
<a href="{{ url_for('admin_dashboard') }}">Panel Admina</a> <a href="{{ url_for('admin_zopk') }}">Panel Admina</a>
<span></span> <span></span>
<a href="{{ url_for('admin_zopk_dashboard') }}">ZOP Kaszubia</a> <a href="{{ url_for('admin_zopk') }}">ZOP Kaszubia</a>
<span></span> <span></span>
<a href="{{ url_for('admin_zopk_knowledge_dashboard') }}">Baza Wiedzy</a> <a href="{{ url_for('admin_zopk_knowledge_dashboard') }}">Baza Wiedzy</a>
<span></span> <span></span>