nordabiz/CLAUDE.md
Maciej Pienczyn 07171b46b7 docs: Raport incydentu CPU + ostrzeżenia o uruchamianiu skryptów
- Dodano INCIDENT_REPORT_20260115.md dokumentujący incydent
  wysokiego CPU spowodowany wielokrotnym uruchomieniem skryptu
- Dodano ostrzeżenia do CLAUDE.md o uruchamianiu skryptów:
  - SSH timeout NIE oznacza nieudanego wykonania
  - Sprawdzaj procesy przed ponownym uruchomieniem
  - Używaj QEMU guest agent jako alternatywy

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 07:35:05 +01:00

1142 lines
40 KiB
Markdown

# Norda Biznes Hub - Instrukcje dla Claude
## Opis projektu
Platforma katalogowa i networkingowa dla członków stowarzyszenia Norda Biznes z Wejherowa.
- **Produkcja:** https://nordabiznes.pl
- **Status:** LIVE (od 2025-11-23)
- **Firmy:** 80 członków Norda Biznes (100% pokrycia)
## Struktura projektu
```
nordabiz/
├── app.py # Główna aplikacja Flask (routes, auth, API)
├── database.py # Modele SQLAlchemy (Company, User, Chat)
├── gemini_service.py # Integracja Google Gemini AI
├── nordabiz_chat.py # Silnik chatu AI z kontekstem firm
├── search_service.py # Unified SearchService (synonimy, FTS, fuzzy)
├── templates/ # Szablony Jinja2
├── static/ # CSS, JS, obrazy
├── database/ # Schematy SQL, migracje
├── data/ # Dane źródłowe JSON
├── tests/ # Testy jakości AI
│ ├── ai_quality_evaluator.py
│ ├── ai_quality_test_cases.json
│ └── results/ # Wyniki testów (JSON)
├── scripts/ # Narzędzia Node.js
└── docs/ # Dokumentacja
├── architecture/ # Architektura systemu (diagramy, przepływy)
├── INCIDENT_REPORT_20260102.md
└── INCIDENT_REPORT_20260115.md
```
## Dokumentacja architektury
Kompletna dokumentacja architektury systemu dostępna w katalogu `docs/architecture/`.
### 📚 Dokumenty główne
| Dokument | Opis |
|----------|------|
| [**README.md**](docs/architecture/README.md) | Przegląd całej dokumentacji architektury (start tutaj!) |
| [01-system-context.md](docs/architecture/01-system-context.md) | Kontekst systemu (C4 Level 1) - aktorzy i systemy zewnętrzne |
| [02-container-diagram.md](docs/architecture/02-container-diagram.md) | Diagram kontenerów (C4 Level 2) - Flask, PostgreSQL, NPM, API |
| [03-deployment-architecture.md](docs/architecture/03-deployment-architecture.md) | Architektura wdrożenia - serwery, porty, infrastruktura |
| [04-flask-components.md](docs/architecture/04-flask-components.md) | Komponenty Flask - routes, services, models |
| [05-database-schema.md](docs/architecture/05-database-schema.md) | Schemat bazy danych - 36 tabel, relacje, indeksy |
| [06-external-integrations.md](docs/architecture/06-external-integrations.md) | Integracje API - Gemini, Brave, PageSpeed, Places, KRS, MS Graph |
| [07-network-topology.md](docs/architecture/07-network-topology.md) | Topologia sieci - Fortigate, NPM, routing, DNS |
| [08-critical-configurations.md](docs/architecture/08-critical-configurations.md) | Konfiguracje krytyczne - NPM proxy, SSL, PostgreSQL, systemd |
| [09-security-architecture.md](docs/architecture/09-security-architecture.md) | Architektura bezpieczeństwa - RBAC, CSRF, strefy zaufania |
| [10-api-endpoints.md](docs/architecture/10-api-endpoints.md) | Referencja API - 90+ endpointów, auth, rate limiting |
### 🔄 Przepływy danych (Data Flows)
| Przepływ | Opis |
|----------|------|
| [01-authentication-flow.md](docs/architecture/flows/01-authentication-flow.md) | Rejestracja, login, reset hasła, sesje |
| [02-search-flow.md](docs/architecture/flows/02-search-flow.md) | Wyszukiwanie firm - synonimy, FTS, fuzzy matching |
| [03-ai-chat-flow.md](docs/architecture/flows/03-ai-chat-flow.md) | Chat AI - kontekst, Gemini API, tracking kosztów |
| [04-seo-audit-flow.md](docs/architecture/flows/04-seo-audit-flow.md) | Audyt SEO - PageSpeed API, analiza on-page/technical |
| [05-news-monitoring-flow.md](docs/architecture/flows/05-news-monitoring-flow.md) | Monitoring newsów - Brave API, filtrowanie AI, moderacja |
| [06-http-request-flow.md](docs/architecture/flows/06-http-request-flow.md) | Przepływ HTTP - user → NPM → Flask → PostgreSQL |
### ⚡ Szybki start
**Dla deweloperów:**
- Zacznij od [architecture/README.md](docs/architecture/README.md)
- Przejrzyj [04-flask-components.md](docs/architecture/04-flask-components.md) i [05-database-schema.md](docs/architecture/05-database-schema.md)
- Sprawdź przepływy w [flows/](docs/architecture/flows/) dla zrozumienia logiki biznesowej
**Dla DevOps:**
- [03-deployment-architecture.md](docs/architecture/03-deployment-architecture.md) - infrastruktura
- [07-network-topology.md](docs/architecture/07-network-topology.md) - sieć i routing
- [08-critical-configurations.md](docs/architecture/08-critical-configurations.md) - **KRYTYCZNE!** NPM port 5000
**Dla architektów:**
- [01-system-context.md](docs/architecture/01-system-context.md) - widok wysokopoziomowy
- [02-container-diagram.md](docs/architecture/02-container-diagram.md) - komponenty główne
- [06-external-integrations.md](docs/architecture/06-external-integrations.md) - zależności zewnętrzne
### 🛡️ Kluczowe ostrzeżenia z dokumentacji
**NPM Proxy (KRYTYCZNE!):**
- Port forward **MUSI być 5000**, NIE 80!
- Szczegóły: [08-critical-configurations.md](docs/architecture/08-critical-configurations.md#npm-reverse-proxy)
- Incydent: [INCIDENT_REPORT_20260102.md](docs/INCIDENT_REPORT_20260102.md)
**PostgreSQL:**
- Skrypty używają `localhost (127.0.0.1)`, NIE `10.22.68.249`
- Szczegóły: [08-critical-configurations.md](docs/architecture/08-critical-configurations.md#database-configuration)
**API Limity:**
- Gemini: 1,500 req/dzień (free tier)
- PageSpeed: 25,000 req/dzień
- Brave Search: 2,000 req/miesiąc
- Szczegóły: [06-external-integrations.md](docs/architecture/06-external-integrations.md)
**Uruchamianie skryptów na produkcji (KRYTYCZNE!):**
- SSH timeout NIE oznacza że komenda nie została wykonana!
- ZAWSZE sprawdź czy poprzedni proces nie działa przed ponowną próbą: `ps aux | grep <skrypt>`
- Dla długich operacji używaj `nohup` lub `screen`
- Przy problemach z SSH użyj QEMU guest agent: `ssh root@10.22.68.123 "qm guest exec 249 -- <komenda>"`
- Incydent: [INCIDENT_REPORT_20260115.md](docs/INCIDENT_REPORT_20260115.md)
## Technologie
| Warstwa | Technologia |
|---------|-------------|
| Backend | Flask 3.0, SQLAlchemy 2.0, Python 3.9+ |
| Frontend | HTML5, CSS3, Vanilla JS, Jinja2 |
| Baza danych | PostgreSQL (prod i dev via Docker) |
| AI | Google Gemini 2.0 Flash (free tier, 200 req/dzień) |
| Security | Flask-Login, Flask-WTF (CSRF), Flask-Limiter |
## Środowiska
### Development (lokalne)
- **Baza:** PostgreSQL via Docker (`localhost:5433/nordabiz`)
- **Port:** 5000 lub 5001
- **Uruchomienie:** `python3 app.py`
- **Docker DB:** `docker compose up -d` (jeśli nie działa)
### Production
- **Serwer:** NORDABIZ-01 (VM 249, IP 10.22.68.249)
- **Baza:** PostgreSQL na 10.22.68.249:5432
- **Reverse Proxy:** NPM na R11-REVPROXY-01 (VM 119, IP 10.22.68.250)
- **Domena:** nordabiznes.pl (DNS w OVH)
- **SSL:** Let's Encrypt (auto-renewal)
### NPM Proxy Configuration (KRYTYCZNE!)
**Proxy Host ID:** 27
**Forward Port:** 5000 (NIE 80!)
```
PRAWIDŁOWA KONFIGURACJA:
NPM (10.22.68.250) → Backend (10.22.68.249:5000) ✓
BŁĘDNA KONFIGURACJA (powoduje pętlę przekierowań):
NPM (10.22.68.250) → Backend (10.22.68.249:80) ✗
```
**UWAGA:** Na serwerze 10.22.68.249 działa nginx na porcie 80 który przekierowuje na HTTPS.
Flask/Gunicorn działa na porcie 5000. Przy edycji proxy hosta ZAWSZE sprawdź czy port = 5000!
**Weryfikacja po zmianach NPM:**
```bash
curl -I https://nordabiznes.pl/health
# Oczekiwany: HTTP 200
```
**Raport incydentu:** `docs/INCIDENT_REPORT_20260102.md`
## Git & Deployment
### Repozytoria Git
| Remote | URL | Cel |
|--------|-----|-----|
| **origin** (GitHub) | `git@github.com:pienczyn/nordabiz.git` | Cloud backup, CI/CD ready |
| **inpi** (Gitea) | `git@10.22.68.180:maciejpi/nordabiz.git` | Wewnętrzny backup, deploy source |
**Konta:**
- GitHub: `pienczyn`
- Gitea (r11-git-inpi): `maciejpi` (osobiste), `gitadmin` (admin Gitea)
### Workflow Deployment
```
┌─────────┐ git push ┌─────────┐ git pull ┌─────────┐
│ DEV │ ────────────► │ Gitea │ ◄──────────── │ PROD │
│ (Mac) │ │ (INPI) │ │ │
└─────────┘ └─────────┘ └─────────┘
└──── git push ────► GitHub (backup)
```
**Komendy deployment:**
```bash
# 1. DEV: Push do obu repozytoriów
git push origin master && git push inpi master
# 2. PROD: Pull i restart
ssh maciejpi@10.22.68.249 "cd /var/www/nordabiznes && sudo -u www-data git pull && sudo systemctl restart nordabiznes"
```
### Serwery Git
| Serwer | IP | Port | Usługa |
|--------|-----|------|--------|
| r11-git-inpi | 10.22.68.180 | 3000 (HTTPS) | Gitea |
| GitHub | github.com | 22/443 | GitHub |
**Gitea wymaga HTTPS** (nie HTTP) - URL: `https://10.22.68.180:3000/`
### PROD Git Config
- **Remote:** `https://10.22.68.180:3000/maciejpi/nordabiz.git`
- **User:** www-data
- **SSL verify:** disabled (`git -c http.sslVerify=false`)
## Auto Claude - Konfiguracja i rozwiązywanie problemów
### Pliki stanu Auto Claude (WAŻNE!)
Auto Claude tworzy lokalne pliki stanu które **NIE POWINNY** być commitowane:
- `.auto-claude-security.json` - stan bezpieczeństwa projektu
- `.auto-claude-status` - status bieżącego zadania
- `.auto-claude/` - katalog roboczy Auto Claude
**Problem:** Auto Claude czasami dodaje te pliki do staging area w worktree branches, co powoduje konflikty merge gdy branch jest mergowany do master.
**Rozwiązanie (wdrożone 2026-01-10):**
1. **`.gitignore`** - pliki są ignorowane:
```
.auto-claude/
.auto-claude-security.json
.auto-claude-status
```
2. **Pre-commit hook** - automatycznie usuwa te pliki ze staging area:
```
.git/hooks/pre-commit
```
Hook sprawdza przed każdym commitem czy pliki Auto Claude są staged i automatycznie je usuwa.
3. **Pliki usunięte z śledzenia** - wykonano `git rm --cached` na master
### Rozwiązywanie konfliktów merge z Auto Claude
Jeśli pojawi się konflikt merge z plikami `.auto-claude-*`:
```bash
# 1. Sprawdź czy to konflikt zmiana/usunięcie
git status
# 2. Usuń pliki Auto Claude z merge (akceptuj usunięcie z master)
git rm .auto-claude-security.json .auto-claude-status
# 3. Dokończ merge
git commit -m "Merge branch 'feature' - resolve Auto Claude file conflicts"
```
### Worktrees Auto Claude
Auto Claude tworzy worktrees dla każdego zadania w:
```
.auto-claude/worktrees/tasks/<task-id>/
```
Każdy worktree ma własny branch i własne pliki stanu. Po zamergowaniu zadania worktree może być usunięty.
**Sprawdzenie aktywnych worktrees:**
```bash
git worktree list
```
**Usunięcie nieaktualnego worktree:**
```bash
git worktree remove .auto-claude/worktrees/tasks/<task-id>
git branch -d auto-claude/<task-id>
```
## Konwencje danych
### Identyfikatory firm
- **Slug:** kebab-case z nazwy, np. `pixlab-sp-z-o-o`
- **NIP:** 10 cyfr bez myślników, np. `5882436505`
- **REGON:** 9 lub 14 cyfr
- **KRS:** 10 cyfr (tylko spółki)
### Kategorie firm
- `IT` - IT i Technologie
- `Construction` - Budownictwo
- `Services` - Usługi (prawne, księgowe, doradcze)
- `Production` - Produkcja
- `Trade` - Handel
- `Other` - Pozostałe
### Poziomy jakości danych
- `basic` - Nazwa, NIP, kontakt
- `enhanced` - Pełne dane, zweryfikowane
- `complete` - Wzbogacone o usługi, kompetencje, certyfikaty
## Ważne zasady
### Bezpieczeństwo
- NIE edytuj bezpośrednio bazy produkcyjnej PostgreSQL
- Zawsze testuj zmiany na DEV PostgreSQL (Docker: localhost:5433) przed wdrożeniem
- Klucze API i hasła tylko w `.env` (nigdy w kodzie)
- Rate limiting: 200 req/dzień, 50 req/godzinę
#### Mechanizmy bezpieczeństwa (wdrożone)
| Mechanizm | Ocena | Opis |
|-----------|-------|------|
| **2FA (TOTP)** | ★★★★★ | Uwierzytelnianie dwuskładnikowe przez aplikacje mobilne |
| **CSRF Protection** | ★★★★★ | Tokeny CSRF w formularzach (Flask-WTF) |
| **HTTPS/TLS** | ★★★★★ | Let's Encrypt SSL z auto-renewal |
| **Hashowanie haseł** | ★★★★★ | Werkzeug bcrypt |
| **SQL Injection** | ★★★★★ | SQLAlchemy ORM (parametryzowane zapytania) |
| **XSS Protection** | ★★★★★ | Jinja2 autoescape |
| **GeoIP Blocking** | ★★★★☆ | Blokowanie krajów: RU, CN, KP, IR, BY, SY, VE, CU |
| **Rate Limiting** | ★★★★☆ | Flask-Limiter + Redis |
| **Account Lockout** | ★★★★☆ | Blokada po 5 nieudanych logowaniach |
| **Audit Log** | ★★★★☆ | Śledzenie działań adminów |
| **Honeypot** | ★★★☆☆ | Wykrywanie botów (/.env, /wp-admin) |
| **Security Alerting** | ★★★☆☆ | Powiadomienia email o krytycznych zdarzeniach |
**Panel bezpieczeństwa:** `/admin/security` (dla adminów)
- Zakładka "Mechanizmy" - lista wszystkich mechanizmów z oceną gwiazdkową
- Zakładka "GeoIP" - statystyki blokowania (dzienne/miesięczne/roczne/od początku)
- Zakładka "Alerty" - alerty bezpieczeństwa do rozwiązania
- Zakładka "Audit log" - historia działań administracyjnych
- Zakładka "Zablokowane konta" - konta zablokowane przez brute-force
**GeoIP Configuration (.env):**
```
GEOIP_ENABLED=true
GEOIP_DB_PATH=/var/www/nordabiznes/geoip/GeoLite2-Country.mmdb
```
**MaxMind GeoLite2:**
- Account ID: 1282843
- Baza danych: GeoLite2-Country (aktualizowana co miesiąc)
- Lokalizacja: `/var/www/nordabiznes/geoip/GeoLite2-Country.mmdb`
#### Zarządzanie danymi uwierzytelniającymi (KRYTYCZNE!)
**NIGDY nie umieszczaj haseł i kluczy API bezpośrednio w kodzie źródłowym!**
Jest to krytyczna podatność bezpieczeństwa (CWE-798: Use of Hard-coded Credentials). Narusza standardy bezpieczeństwa i może prowadzić do kompromitacji systemu jeśli repozytorium zostanie ujawnione.
**Zasady obowiązkowe:**
1. **Używaj zmiennych środowiskowych dla wszystkich wrażliwych danych:**
```python
# PRAWIDŁOWO:
DATABASE_URL = os.getenv('DATABASE_URL')
API_KEY = os.getenv('GOOGLE_PAGESPEED_API_KEY')
# BŁĘDNIE - NIGDY TAK NIE RÓB:
DATABASE_URL = 'postgresql://user:password123@localhost/db'
API_KEY = 'AIzaSyAbc123...'
```
2. **Konfiguruj wartości domyślne jako bezpieczne placeholdery:**
```python
# PRAWIDŁOWO - wartość domyślna która wymusi konfigurację .env:
DATABASE_URL = os.getenv('DATABASE_URL', 'postgresql://user:CHANGE_ME@localhost/nordabiz')
# BŁĘDNIE - wartość produkcyjna jako fallback:
DATABASE_URL = os.getenv('DATABASE_URL', 'postgresql://user:RealPassword@10.22.68.249/nordabiz')
```
3. **Przechowuj dane uwierzytelniające w plikach .env:**
- Produkcja: `/var/www/nordabiznes/.env`
- Development: `.env` w katalogu projektu
- Wzorzec: `.env.example` (bez prawdziwych wartości!)
4. **Wymagane zmienne środowiskowe:**
| Zmienna | Cel | Przykład |
|---------|-----|----------|
| `DATABASE_URL` | Połączenie PostgreSQL dla skryptów Python | `postgresql://user:pass@127.0.0.1:5432/nordabiz` |
| `PGPASSWORD` | Hasło PostgreSQL dla skryptów shell | `export PGPASSWORD='your_password'` |
| `GOOGLE_PAGESPEED_API_KEY` | API Key Google PageSpeed | `AIzaSy...` |
| `BRAVE_SEARCH_API_KEY` | API Key Brave Search | `BSA...` |
| `GEMINI_API_KEY` | API Key Google Gemini AI | `AIzaSy...` |
5. **Skrypty shell - zawsze sprawdzaj czy zmienne są ustawione:**
```bash
# PRAWIDŁOWO:
if [ -z "$PGPASSWORD" ]; then
echo "ERROR: PGPASSWORD not set"
exit 1
fi
psql -h localhost -U nordabiz_app -d nordabiz
# BŁĘDNIE:
PGPASSWORD='hardcoded_password' psql -h localhost -U nordabiz_app -d nordabiz
```
6. **NIGDY nie commituj plików z credentials:**
- `.env` jest w `.gitignore`
- Sprawdzaj przed commitem: `git diff` i `git status`
- W razie wątpliwości: `git log -p | grep -i password`
7. **Co zrobić jeśli przypadkowo scommitujesz hasło:**
- ⚠️ **NATYCHMIAST zmień hasło w bazie/API**
- Nie wystarczy usunąć z najnowszego commita - hasło pozostaje w historii Git
- Rozważ użycie `git filter-branch` lub `BFG Repo-Cleaner` (skomplikowane)
- Najlepiej: zmień hasło i traktuj stare jako skompromitowane
8. **Wyjątki (kiedy dozwolone jest hasło w kodzie):**
- ✅ Pliki dokumentacji (np. przykłady w CLAUDE.md, README)
-`.env.example` jako szablon (z placeholderami)
- ⛔ NIGDY w plikach wykonywalnych (.py, .sh, .js)
**Weryfikacja przed wdrożeniem:**
```bash
# Sprawdź czy nie ma hardcoded credentials w kodzie:
grep -r "PGPASSWORD=" --include="*.sh" .
grep -r "postgresql://.*:.*@" --include="*.py" . | grep -v "CHANGE_ME" | grep -v ".example" | grep -v "PASSWORD"
# Oczekiwany wynik: brak znalezisk (lub tylko w dokumentacji/placeholderach)
```
### Import danych
- Używaj skryptów `import_*.py` do dodawania firm
- Weryfikuj NIP przez API przed importem
- Zachowaj spójność slugów (unikalne, lowercase)
### Deployment
- Przed wdrożeniem: `python -m py_compile app.py`
- SSH do NORDABIZ-01: `ssh maciejpi@10.22.68.249` (ZAWSZE jako maciejpi, NIE root!)
- Ścieżka aplikacji: `/var/www/nordabiznes`
- Restart: `sudo systemctl restart nordabiznes`
- **ZAWSZE** aktualizuj historię zmian (`release_notes` w app.py) po wdrożeniu
- Historia zmian: efekt końcowy, bez powtórzeń, prostym językiem
### Szablony Jinja2 - WAŻNE!
- Blok `{% block extra_js %}` w `base.html` jest już wewnątrz tagu `<script>`
- **NIE DODAWAJ** własnych tagów `<script>` w `extra_js` - spowoduje zagnieżdżenie i błąd JS
- Prawidłowo: `{% block extra_js %}function foo() {...}{% endblock %}`
- Błędnie: `{% block extra_js %}<script>function foo() {...}</script>{% endblock %}`
### Uprawnienia PostgreSQL
- Po utworzeniu nowych tabel: `GRANT ALL ON TABLE ... TO nordabiz_app`
- Po utworzeniu sekwencji: `GRANT USAGE, SELECT ON SEQUENCE ... TO nordabiz_app`
- Baza: `nordabiz`, użytkownik aplikacji: `nordabiz_app`
### Testowanie na produkcji
- **ZAWSZE używaj kont testowych** do weryfikacji funkcjonalności
- Używaj przeglądarki (browser automation) do testów wymagających logowania
**Konta testowe (PROD):**
| Konto | Email | Hasło | Rola |
|-------|-------|-------|------|
| Test User | `test@nordabiznes.pl` | `&Rc2LdbSw&jiGR0ek@Bz` | Zwykły użytkownik |
| Test Admin | `testadmin@nordabiznes.pl` | `cSfQbbwegwv1v3Q2Dm0Q` | Administrator |
**Użycie:**
- **Test User** - do testowania funkcji dostępnych dla zwykłych użytkowników
- **Test Admin** - do testowania panelu admina (rekomendacje, składki, kalendarz, forum, news)
## Skrypty danych
### Import (wykonywać kolejno)
```bash
python import_norda_companies.py # Batch 1 (56 firm)
python import_norda_batch2.py # Batch 2
python import_norda_batch3.py # Batch 3
python import_norda_batch4.py # Batch 4 (9 firm)
python import_norda_batch5.py # Batch 5 (8 firm)
```
### Weryfikacja
```bash
python verify_all_companies_data.py # Raport jakości danych
python fix_krs_verification.py # Weryfikacja KRS
```
## API Endpoints
| Endpoint | Metoda | Opis |
|----------|--------|------|
| `/` | GET | Katalog firm |
| `/company/<slug>` | GET | Profil firmy |
| `/search` | GET | Wyszukiwanie |
| `/api/companies` | GET | Lista firm (JSON) |
| `/api/verify-nip` | GET | Weryfikacja NIP |
| `/health` | GET | Health check |
| `/chat` | GET | Interfejs chatu AI |
| `/admin/news` | GET/POST | Panel moderacji newsów (wymaga admin) |
| `/api/notifications` | GET | Powiadomienia użytkownika (JSON) |
## SearchService (search_service.py)
Unified search dla chatbota AI i wyszukiwarki `/search`.
### Funkcje:
- **NIP/REGON lookup** - bezpośrednie wyszukiwanie po identyfikatorach
- **Synonym expansion** - rozszerzenie słów kluczowych (np. "strony" → www, web, portal)
- **PostgreSQL FTS** - full-text search z tsvector (DEV i PROD)
- **SQLite fallback** - keyword scoring (tylko jako fallback, nieużywane)
- **Fuzzy matching** - pg_trgm dla literówek (gdy dostępne)
### Scoring:
- Nazwa firmy: +10 punktów
- Opis: +5 punktów
- Usługi: +8 punktów
- Kompetencje: +7 punktów
- Miasto: +3 punktów
### Użycie:
```python
from search_service import search_companies
results = search_companies(db, "strony www", limit=10)
# Zwraca List[SearchResult] z company, score, match_type
```
### UWAGA (PostgreSQL):
- Gdy FTS się nie powiedzie, wykonywany jest `db.rollback()` przed fallbackiem
- Bez tego następuje błąd `InFailedSqlTransaction`
## Chatbot AI (nordabiz_chat.py)
### Konfiguracja:
- **Limit firm do AI:** 8 (zmienne w `_build_conversation_context`, linia ~312)
- **Historia wiadomości:** 10 ostatnich
- **Search:** używa `search_companies()` z SearchService
## Testy jakości AI
### Uruchomienie:
```bash
python run_ai_quality_tests.py -v # Verbose output
python run_ai_quality_tests.py -v -s # Verbose + save report
python run_ai_quality_tests.py -q # Quick (tylko high-priority)
```
### Przypadki testowe (`tests/ai_quality_test_cases.json`):
- 15 przypadków w 8 kategoriach
- Próg zaliczenia: 70%
- Kategorie: IT/Web, Services/Legal, Services/Accounting, Production/Metal, Construction, HVAC, Energy/Renewable, IT/Security
## Powiązane zasoby
- **Źródło danych:** https://norda-biznes.info/czlonkowie
- **Monitoring:** Zabbix (do konfiguracji)
- **Backup:** Proxmox Backup Server (VM snapshots)
- **DNS wewnętrzny:** nordabiznes.inpi.local
## Kontakty
- **Projekt:** Norda Biznes Hub
- **Infrastruktura:** INPI (skills: proxmox-manager, dns-manager, npm-manager)
## Szablon profilu firmy (company profile)
### Zatwierdzone zmiany do wdrożenia
Sekcje do **połączenia** (redukcja duplikatów):
1. **"O firmie" + "Profil działalności"** → jedna sekcja "O firmie"
2. **"Oferta i usługi" + "Słowa kluczowe"** → jedna sekcja "Usługi i kompetencje"
3. **"Wyróżniki" + "Wartości firmy"** → jedna sekcja "Wyróżniki"
4. **Blok "Jakość Danych"** → usunąć (badge przy nazwie wystarczy)
### Sekcje które MUSZĄ pozostać
- **Social Media (6 kafelków)** - pokazywać WSZYSTKIE platformy, także te bez profilu ("Brak profilu") - ważne by widzieć czego brakuje
- **Dane kontaktowe (sekcja z kartami)** - to docelowe miejsce na WSZYSTKIE dane kontaktowe firmy (adres, telefony, emaile, godziny otwarcia, itp.)
### Docelowa struktura profilu (po optymalizacji)
```
1. Header (nazwa, kategoria, badge weryfikacji, krótki opis)
2. Pasek kontaktowy (www, email, telefon, lokalizacja) - szybki dostęp
3. O firmie (połączone opisy)
4. Usługi i kompetencje (połączone tagi)
5. Wyróżniki (połączone z wartościami)
6. Dane kontaktowe (pełne karty - główne miejsce na kontakt)
7. Informacje prawne i biznesowe (NIP, REGON, KRS, rok założenia)
8. Social Media (wszystkie 6 platform - widoczne braki)
9. Strona WWW (analiza techniczna) - zawsze na końcu
```
### Szablon: templates/company_detail.html
Plik do modyfikacji przy implementacji zmian.
## Plan rozwoju - Aktualności
### Social Media Audit (WDROŻONE)
**Status:** Wdrożone (2026-01-09)
**Panel:** `/admin/social-media`
**Funkcje:**
- Audyt profili Social Media firm (Facebook, Instagram, LinkedIn, YouTube, TikTok, Twitter)
- Weryfikacja aktywności profili (last_checked_at, followers_count)
- Raportowanie brakujących profili
### Priorytet 1: Social Media Integration (Posts/Events)
**Status:** Planowane
**Cel:** Pobieranie postów i wydarzeń z Social Media firm
**Źródła danych:**
- Facebook Pages firm członkowskich (posty, wydarzenia)
- LinkedIn Company Pages
- Google My Business (recenzje, posty)
**Wymagania techniczne:**
- Facebook Graph API (wymaga App Review dla pages_read_engagement)
- LinkedIn Marketing API (wymaga partnera lub OAuth)
- Google Business Profile API
**Typy zdarzeń do importu:**
- `social_post` - posty z social media
- `social_event` - wydarzenia z Facebooka
- `review` - nowe recenzje Google
### Priorytet 2: News Monitoring (Google/Brave API)
**Status:** Wdrożone (2025-12-29)
**Źródła danych:**
- Wzmianki o firmach w mediach lokalnych/branżowych
- Artykuły prasowe
- Komunikaty branżowe
**Wymagania techniczne:**
- Brave Search API (bezpłatny tier) LUB
- Google Custom Search API ($5/1000 queries)
- Cykliczne wyszukiwanie nazw firm
**Typy zdarzeń do importu:**
- `news_mention` - wzmianka w mediach
- `press_release` - komunikat prasowy
- `award` - nagroda/wyróżnienie
### Priorytet 3: Zarząd i Wspólnicy (rejestr.io)
**Status:** Planowane
**Cel:** Wyświetlanie osób powiązanych z firmą bezpośrednio na stronie profilu
**Dane do pobrania z rejestr.io:**
- Zarząd (Prezes, Wiceprezes, Członkowie Zarządu)
- Prokurenci
- Wspólnicy z % udziałów
- Beneficjenci rzeczywiści
- Linki do profili osób (powiązania z innymi firmami)
**Wymagania techniczne:**
- Tabela `company_people` (company_id, name, role, shares_percent, person_url)
- Scraper Playwright (już mamy bazę w `analyze_connections.py`)
- Sekcja w `company_detail.html` po "Informacje prawne i biznesowe"
**Przykład wyświetlania:**
```
👥 ZARZĄD I WSPÓLNICY
┌─────────────────────────────────────────┐
│ 👔 Jan Kowalski - Prezes Zarządu │
│ 👔 Anna Nowak - Członek Zarządu │
│ 💼 Firma XYZ Sp. z o.o. - 60% udziałów │
│ 💼 Jan Kowalski - 40% udziałów │
└─────────────────────────────────────────┘
```
**Korzyści:**
- Widoczne powiązania między firmami Norda Biznes
- Ułatwiony networking (kto zna kogo)
- Transparentność struktury właścicielskiej
### Notatki implementacyjne
- Scraper powinien deduplikować wydarzenia (hash tytułu + daty)
- Moderacja: nowe wydarzenia jako "pending" do zatwierdzenia przez admina
- Rate limiting: max 100 requestów/dzień do zewnętrznych API
## News Monitoring
### Opis funkcjonalności
System automatycznego monitoringu wzmianek o firmach Norda Biznes w mediach lokalnych i branżowych.
### Tabela w bazie danych
```sql
company_news (
id SERIAL PRIMARY KEY,
company_id INTEGER REFERENCES companies(id),
title VARCHAR(500) NOT NULL,
description TEXT,
url VARCHAR(1000) NOT NULL,
source VARCHAR(200), -- nazwa portalu/medium
published_at TIMESTAMP,
news_type VARCHAR(50), -- news_mention, press_release, award
relevance_score FLOAT, -- 0.0-1.0 (AI scoring)
status VARCHAR(20) DEFAULT 'pending', -- pending, approved, rejected
moderated_by INTEGER, -- user_id admina
moderated_at TIMESTAMP,
rejection_reason TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(company_id, url)
)
```
**Statusy newsów:**
- `pending` - oczekuje na moderację
- `approved` - zatwierdzony, widoczny na profilu firmy
- `rejected` - odrzucony (spam, nieistotny, duplikat)
### Brave Search API Integration
**Konfiguracja:**
- API Key w `.env`: `BRAVE_SEARCH_API_KEY`
- Endpoint: `https://api.search.brave.com/res/v1/news/search`
- Limit: 2000 req/miesiąc (free tier)
**Parametry wyszukiwania:**
```python
params = {
"q": f'"{company_name}" OR "{nip}"',
"count": 10,
"freshness": "pw", # past week
"country": "pl",
"search_lang": "pl"
}
```
**Skrypt pobierania:** (do implementacji)
```bash
# TODO: Skrypt fetch_company_news.py wymaga implementacji
# cd /var/www/nordabiznes
# sudo -u www-data /var/www/nordabiznes/venv/bin/python3 scripts/fetch_company_news.py
```
### AI Filtering (Gemini)
Filtracja wyników przez Google Gemini:
- Ocena relevance_score (0.0-1.0)
- Kategoryzacja news_type
- Wykrywanie duplikatów/spamu
- Automatyczne odrzucanie wyników < 0.3 relevance
**Prompt systemu:**
```
Oceń czy artykuł dotyczy działalności firmy {company_name}.
Zwróć JSON: {"relevance": 0.0-1.0, "type": "news_mention|press_release|award", "reason": "..."}
```
### Panel admina /admin/news
**URL:** `/admin/news`
**Wymaga:** Zalogowany użytkownik z `is_admin=True`
**Funkcje:**
- Lista newsów pending do moderacji
- Filtrowanie po firmie, statusie, dacie
- Zatwierdzanie/odrzucanie z powodem
- Podgląd oryginalnego artykułu
- Bulk actions (zatwierdź wszystkie z relevance > 0.8)
### Sekcja Aktualności na profilu firmy
**Lokalizacja:** `templates/company_detail.html`
**Warunek:** Tylko newsy ze statusem `approved`
**Sortowanie:** Po `published_at` DESC
**Limit:** 5 ostatnich newsów
**Struktura wyświetlania:**
```
AKTUALNOŚCI
├── [data] Tytuł artykułu (źródło)
│ Krótki opis...
│ [Czytaj więcej →]
└── [data] Kolejny artykuł...
```
### System powiadomień
**Endpoint:** `/api/notifications`
**Zwraca:** JSON z listą powiadomień użytkownika
**Typy powiadomień:**
- `new_news` - nowy news o obserwowanej firmie (dla zalogowanych)
- `news_approved` - news firmy użytkownika został zatwierdzony
- `news_rejected` - news firmy użytkownika został odrzucony
**Struktura odpowiedzi:**
```json
{
"notifications": [
{
"id": 1,
"type": "new_news",
"message": "Nowa wzmianka o PIXLAB",
"url": "/company/pixlab-sp-z-o-o#news",
"created_at": "2025-12-29T10:30:00",
"read": false
}
],
"unread_count": 3
}
```
### Skrypty i cron (do implementacji)
```bash
# TODO: Skrypty do pobierania newsów wymagają implementacji
# Planowane komendy:
# python scripts/fetch_company_news.py --all
# python scripts/fetch_company_news.py --company pixlab-sp-z-o-o
# Cron job (do skonfigurowania po implementacji)
# 0 */6 * * * cd /var/www/nordabiznes && /var/www/nordabiznes/venv/bin/python3 scripts/fetch_company_news.py --all >> /var/log/nordabiznes/news_fetch.log 2>&1
```
## ZOP Kaszubia News (ZOPK)
### Opis
System monitoringu newsów związanych z projektem **Zielony Okręg Przemysłowy Kaszubia**.
Panel admina: `/admin/zopk/news`
### Tematy ZOP Kaszubia (istotne)
- **Zielony Okręg Przemysłowy Kaszubia** - główny projekt
- **Elektrownia jądrowa na Pomorzu** - Lubiatowo-Kopalino
- **Offshore wind Bałtyk** - farmy wiatrowe, Baltic Power, Baltica
- **Via Pomerania** - droga ekspresowa Ustka-Bydgoszcz
- **Droga Czerwona** - połączenie z Portem Gdynia
- **Kongsberg** - norweskie inwestycje zbrojeniowe w Rumi
- **Pakt Bezpieczeństwa Pomorze Środkowe** - MON
- **Izba Przedsiębiorców NORDA** - lokalne organizacje biznesowe
### Tematy NIEZWIĄZANE (do odrzucenia)
- Turystyka na Kaszubach (kuligi, lodowiska, hotele)
- Polityka ogólnopolska (Ziobro, polexit)
- Inne regiony Polski (Śląsk, Lubuskie, Małopolska)
- Wypadki i wydarzenia kryminalne
- Clickbait i lifestyle
### Reguły auto-approve (WAŻNE!)
**Próg auto-approve: score >= 3** (verified 2026-01-15)
| Score | Status | Opis |
|-------|--------|------|
| 1-2 | `pending` | Wymaga ręcznej moderacji |
| 3-5 | `auto_approved` | Automatycznie zatwierdzony |
**Plik:** `zopk_news_service.py` (linie 890, 1124, 1145)
### Tabela zopk_news
```sql
zopk_news (
id, title, url, description,
source_name, source_domain, source_type,
ai_relevance_score INTEGER, -- 1-5 gwiazdek
status VARCHAR(20), -- pending, auto_approved, approved, rejected
confidence_score, source_count,
created_at, updated_at
)
```
## Social Media - Stan aktualny
### Statystyki (2025-12-29)
| Platforma | Liczba firm | Pokrycie |
|-----------|-------------|----------|
| Facebook | 39 | 49% |
| Instagram | 26 | 33% |
| LinkedIn | 22 | 28% |
| YouTube | 17 | 21% |
| Twitter/X | 7 | 9% |
| TikTok | 4 | 5% |
**Łącznie:** 115 profili dla 53 firm (66% pokrycia)
**Firmy bez Social Media:** 27
### Tabela w bazie danych
```sql
company_social_media (
id, company_id, platform, url,
verified_at, source, is_valid,
last_checked_at, check_status,
page_name, followers_count,
created_at, updated_at
)
```
**Platformy:** facebook, instagram, youtube, linkedin, tiktok, twitter
### Skrypty aktualizacji
```bash
# Aktualizacja Social Media z pliku JSON
cd /var/www/nordabiznes
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 update_social_media.py --dry-run # Test
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 update_social_media.py # Produkcja
```
**Pliki:**
- `social_media_found.json` - wyniki wyszukiwania (źródło danych)
- `update_social_media.py` - skrypt aktualizujący bazę
### Firmy z najlepszym pokryciem Social Media
1. **PORTA KMI** - Facebook, Instagram, YouTube, LinkedIn (4 platformy, 124K FB fans)
2. **Rumia Invest Park** - Facebook, Instagram, YouTube, LinkedIn, Twitter (5 platform)
3. **GRAAL** - Facebook, Instagram, YouTube, LinkedIn (4 platformy)
4. **Chopin Telewizja Kablowa** - Facebook, Instagram, YouTube, Twitter (4 platformy)
5. **Hotel SPA Wieniawa** - Facebook, Instagram, YouTube, TikTok (4 platformy)
### Firmy bez Social Media (do uzupełnienia)
SIM Rumia, Rubinsolar, KORNIX, KBMS, Semerling Security, ARD Invest, AMA,
Jubiler Agat, P&P, Progress Optima, Ampery, Bibrokers, CoolAir, Joker,
KAMMET, Alumech, Litwic&Litwic, Orlex MG, Pro-Invest, Round Two, SCROL,
ALMARES, Pucka Gospodarka Komunalna, Hebel Masiak, Lenap Hale, MKonsult, Portal
## Audyt SEO (Panel /admin/seo)
### Opis funkcjonalności
System audytu SEO stron internetowych firm członkowskich Norda Biznes.
Wykorzystuje Google PageSpeed Insights API do analizy wydajności i jakości stron.
**URL panelu:** `/admin/seo`
**Wymaga:** Zalogowany użytkownik z `is_admin=True`
### Konfiguracja API
**Google PageSpeed Insights API:**
- API Key w `.env`: `GOOGLE_PAGESPEED_API_KEY`
- Projekt Google Cloud: NORDABIZNES (`gen-lang-client-0540794446`)
- Limit: 25,000 zapytań/dzień (free tier)
- Endpoint: `https://www.googleapis.com/pagespeedonline/v5/runPagespeed`
**Klucz API:**
- **Nazwa w Google Cloud:** `Page SPEED SEO Audit v2`
- **Wartość:** Przechowywany w `.env` (GOOGLE_PAGESPEED_API_KEY)
- **UWAGA:** Nigdy nie commituj kluczy API do repozytorium!
### Metryki audytu
| Metryka | Źródło | Skala |
|---------|--------|-------|
| WYNIK SEO | PageSpeed Insights | 0-100 |
| PERFORMANCE | PageSpeed Insights | 0-100 |
| DOSTĘPNOŚĆ | PageSpeed Insights | 0-100 |
| BEST PRACTICES | PageSpeed Insights | 0-100 |
**Interpretacja wyników:**
- 90-100 (zielony) - Doskonały
- 50-89 (żółty) - Wymaga poprawy
- 0-49 (czerwony) - Słaby
### Skrypty SEO
```bash
# Audyt pojedynczej firmy
cd /var/www/nordabiznes/scripts
python seo_audit.py --company-id 26
# Audyt wsadowy (batch)
python seo_audit.py --batch 1-10
# Audyt wszystkich firm
python seo_audit.py --all
# Tryb testowy (bez zapisu)
python seo_audit.py --company-id 26 --dry-run
```
### WAŻNE - Połączenie z bazą danych
Skrypty w `scripts/` muszą używać **localhost (127.0.0.1)** do połączenia z PostgreSQL:
```python
# PRAWIDŁOWO (hasło z .env):
DATABASE_URL = 'postgresql://nordabiz_app:<PASSWORD_FROM_ENV>@127.0.0.1:5432/nordabiz'
# BŁĘDNIE (PostgreSQL nie akceptuje zewnętrznych połączeń):
DATABASE_URL = 'postgresql://nordabiz_app:<PASSWORD>@10.22.68.249:5432/nordabiz'
```
**UWAGA:** Hasło do bazy jest w `.env` na produkcji. NIE commituj haseł do repozytorium!
**Pliki z konfiguracją bazy:**
- `scripts/seo_audit.py` (linia ~79)
- `scripts/seo_report_generator.py` (linia ~47)
- `scripts/social_media_audit.py` (linia ~53)
### Tabela w bazie danych
```sql
seo_metrics (
id SERIAL PRIMARY KEY,
company_id INTEGER REFERENCES companies(id),
url VARCHAR(500),
seo_score INTEGER,
performance_score INTEGER,
accessibility_score INTEGER,
best_practices_score INTEGER,
pwa_score INTEGER,
audit_data JSONB, -- pełne dane z PageSpeed
audited_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
)
```
### UI - Stylizowane modale
Panel SEO używa niestandardowych modali (zamiast natywnych `confirm()`/`alert()`):
- Modal potwierdzenia audytu z ikoną ostrzeżenia
- Modal informacyjny o błędach
- Animacje CSS (fade in/out)
**Lokalizacja kodu:** `templates/admin_seo_dashboard.html`
## Planowane funkcjonalności (Backlog)
### Priorytet 4: System rekomendacji i zdjęć
**Status:** Planowane
**Cel:** Umożliwienie firmom członkowskim wzajemnego polecania się oraz prezentacji zdjęć
**Funkcje:**
- Rekomendacje między firmami (kto poleca kogo)
- Galeria zdjęć firmy (realizacje, zespół, biuro)
- Wyświetlanie na profilu firmy
### Priorytet 5: Status członkostwa i płatności
**Status:** Planowane
**Cel:** Śledzenie statusu członkostwa i składek miesięcznych
**Funkcje:**
- Status członka (aktywny, zawieszony, były członek)
- Informacja o opłaconych składkach
- Historia płatności
- Przypomnienia o zaległościach (dla admina)
**Tabela `membership_fees`:**
```sql
membership_fees (
id SERIAL PRIMARY KEY,
company_id INTEGER REFERENCES companies(id),
period_start DATE NOT NULL, -- początek okresu (np. 2026-01-01)
period_end DATE NOT NULL, -- koniec okresu (np. 2026-01-31)
amount DECIMAL(10,2) NOT NULL, -- kwota składki
status VARCHAR(20) DEFAULT 'pending', -- pending, paid, overdue
paid_at TIMESTAMP,
payment_method VARCHAR(50), -- przelew, gotówka
notes TEXT,
created_at TIMESTAMP DEFAULT NOW()
)
```
## Forma prawna Norda Biznes
### Stan obecny
- **Forma:** OPP (Organizacja Pożytku Publicznego)
- **Typ:** Stowarzyszenie non-profit
### Planowana transformacja
- **Docelowa forma:** Działalność gospodarcza (przy Izbie)
- **Cel:** Możliwość pozyskiwania dofinansowań (granty, projekty UE)
- **Korzyści:**
- Dostęp do funduszy na rozwój platformy
- Możliwość świadczenia płatnych usług
- Elastyczność finansowa
## Strategia monetyzacji
### Model 3-tier Pricing (Kotwiczenie ceny)
**Strategia:** Trzy poziomy cenowe z zastosowaniem psychologii kotwiczenia ceny (Price Anchoring).
Najwyższy poziom służy jako **kotwica** - sprawia że środkowy wydaje się atrakcyjny i jest docelowy.
| Poziom | Nazwa | Cena/mies. | Cel strategiczny |
|--------|-------|------------|------------------|
| **1** | Basic | ~49 zł | Entry point, ograniczone funkcje |
| **2** | Premium | ~99 zł | **DOCELOWY** - rekomendowany, najlepsza wartość |
| **3** | Enterprise | ~199 zł | **KOTWICA** - premium, pełny dostęp |
**Wyjątek:** Członkowie Izby NORDA płacący składki (~200 zł/mies.) mają specjalny status - dostęp Premium za symboliczne 1 zł.
### Psychologia 3-tier Pricing
- Ludzie naturalnie wybierają środkową opcję (efekt kompromisu)
- Wysoka cena kotwicy sprawia że środkowa wydaje się "okazją"
- Niska cena Basic sprawia że użytkownik czuje "upgrade jest wart dopłaty"
- Firmy stosujące 3-tier widzą ~30% wzrost przychodów
- Slack: dodanie Enterprise tier zwiększyło konwersję na Professional o 28%
### Matryca dostępu do funkcji
| Funkcja | Basic | Premium | Enterprise |
|---------|:-----:|:-------:|:----------:|
| Katalog firm | ✅ | ✅ | ✅ |
| Profil firmy | ✅ | ✅ | ✅ |
| Forum | ❌ | ✅ | ✅ |
| Kalendarz wydarzeń | ❌ | ✅ | ✅ |
| Chat AI (NordaGPT) | ❌ | ✅ | ✅ |
| **Raporty podstawowe** | ❌ | ✅ | ✅ |
| **Raporty zaawansowane** | ❌ | ❌ | ✅ |
| Eksport danych (CSV/PDF) | ❌ | ❌ | ✅ |
| API dostęp | ❌ | ❌ | ✅ |
| Priorytetowe wsparcie | ❌ | ❌ | ✅ |
### Implementacja techniczna (przyszłość)
```python
# Model User - nowe pola
class User(Base):
# ...
subscription_tier = Column(String(20), default='basic') # basic, premium, enterprise
subscription_expires_at = Column(DateTime)
is_norda_member = Column(Boolean, default=False) # Członek Izby = specjalny status
# Dekorator kontroli dostępu
def requires_tier(min_tier):
def decorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
tiers = ['basic', 'premium', 'enterprise']
user_tier_idx = tiers.index(current_user.subscription_tier)
required_idx = tiers.index(min_tier)
if user_tier_idx < required_idx:
flash(f'Ta funkcja wymaga konta {min_tier.title()}.', 'warning')
return redirect(url_for('pricing'))
return f(*args, **kwargs)
return wrapped
return decorator
# Użycie
@app.route('/raporty/zaawansowane')
@login_required
@requires_tier('enterprise')
def advanced_reports():
...
```
### Raporty - podział według poziomu
**Raporty podstawowe (Premium+):**
- Staż członkostwa w Izbie NORDA
- Pokrycie Social Media
- Struktura branżowa
**Raporty zaawansowane (Enterprise only):**
- Ranking SEO
- Mapa lokalizacji
- Sieć rekomendacji
- Aktywność w wydarzeniach