nordabiz/CLAUDE.md
Maciej Pienczyn 6d589407be Sync local repo with production state
- Add MembershipFee and MembershipFeeConfig models
- Add /health endpoint for monitoring
- Add Microsoft Fluent Design CSS
- Update templates with new CSS structure
- Add Announcement model
- Update .gitignore to exclude analysis files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 22:23:28 +01:00

584 lines
19 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
```
## Technologie
| Warstwa | Technologia |
|---------|-------------|
| Backend | Flask 3.0, SQLAlchemy 2.0, Python 3.9+ |
| Frontend | HTML5, CSS3, Vanilla JS, Jinja2 |
| Baza danych | PostgreSQL (prod), SQLite (dev) |
| AI | Google Gemini 2.0 Flash (free tier, 200 req/dzień) |
| Security | Flask-Login, Flask-WTF (CSRF), Flask-Limiter |
## Środowiska
### Development (lokalne)
- **Baza:** `nordabiz_local.db` (SQLite)
- **Port:** 5000 lub 5001
- **Uruchomienie:** `python3 app.py`
### 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`
## 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 SQLite przed wdrożeniem
- Klucze API i hasła tylko w `.env` (nigdy w kodzie)
- Rate limiting: 200 req/dzień, 50 req/godzinę
### 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 konta testowego** do weryfikacji funkcjonalności
- **Konto testowe:** test@nordabiznes.pl / TestNorda2024!
- Konto ma uprawnienia zwykłego użytkownika (nie admin)
- Używaj przeglądarki (browser automation) do testów wymagających logowania
## 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 (produkcja)
- **SQLite fallback** - keyword scoring (development)
- **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
### Priorytet 1: Social Media Integration
**Status:** Do wdrożenia
**Ź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:**
```bash
cd /var/www/nordabiznes
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 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
```bash
# Jednorazowe pobranie newsów dla wszystkich firm
python fetch_company_news.py --all
# Pobranie dla konkretnej firmy
python fetch_company_news.py --company pixlab-sp-z-o-o
# Cron job (co 6 godzin)
0 */6 * * * cd /var/www/nordabiznes && /var/www/nordabiznes/venv/bin/python3 fetch_company_news.py --all >> /var/log/nordabiznes/news_fetch.log 2>&1
```
## 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
## 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