# Instrukcja dla Claude Code — Scraper leadów kancelarii prawnych

## Kontekst projektu

Budujesz system scrapowania leadów dla klientki — adwokat specjalizującej się w prawie kanonicznym (postępowania o stwierdzenie nieważności małżeństwa), która chce dotrzeć do kancelarii prawnych zajmujących się **prawem rodzinnym / rozwodami** w całej Polsce. Zebrane leady trafiają do CRM (dgred.com), skąd agent AI będzie prowadził outreach emailowy.

---

## Stack techniczny

- **Język**: Python 3.12
- **Lokalizacja**: `/var/www/html/scraper-prawny.klono.ai/`
- **CRM**: dgred.com — API docs w pliku `api_docs.md`
- **Scraping trudnych źródeł**: Apify (konto do ustalenia)
- **Scheduler**: cron job na serwerze Linux

---

## CRM — dane dostępowe

```
Base URL:  https://app.dgred.com/api/v1
API Key:   w zmiennej środowiskowej CRM_API_KEY (plik .env). Aby zobaczyć: cat .env | grep CRM_API_KEY
Auth:      GET parameter ?apiKey=...
```

### Kluczowe endpointy CRM których będziesz używać:

- `POST /clients` — dodanie leada
- `GET /clients` — lista klientów (do deduplikacji)
- `POST /clients/{id}/notes` — dodanie notatki (np. źródło, data scrapu)
- `PUT /client/{id}` — aktualizacja leada

---

## Struktura projektu do stworzenia

```
/var/www/html/scraper-prawny.klono.ai/
├── scraper/
│   ├── main.py              # entry point, orchestrator
│   ├── config.py            # konfiguracja (limity, klucze, źródła)
│   ├── crm.py               # klasa CRMClient (wrapper na API dgred)
│   ├── deduplicator.py      # sprawdzanie czy lead już istnieje w CRM
│   ├── sources/
│   │   ├── __init__.py
│   │   ├── base.py          # bazowa klasa ScraperSource
│   │   ├── ora.py           # Rejestr Adwokatury (adwokatura.pl)
│   │   ├── oirp.py          # Rejestr Radców Prawnych (kirp.pl)
│   │   ├── google_maps.py   # Google Places API
│   │   └── panoramafirm.py  # panoramafirm.pl
│   └── logs/
│       └── scraper.log
└── requirements.txt
```

---

## Model danych — lead

Każdy lead wrzucany do CRM musi mieć:

```python
{
    "name": "Kancelaria Adwokacka Jan Kowalski",   # wymagane
    "nameMore": "Adwokat / Prawo rodzinne",         # specjalizacja
    "city": "Kraków",
    "street": "ul. Przykładowa 1",
    "postalCode": "31-000",
    "email": "kontakt@kancelaria.pl",
    "phone": "+48123456789",
    "tags": [
        {"tag": "prawo-rodzinne", "color": 2},
        {"tag": "źródło:ora", "color": 3},          # TAG ŹRÓDŁA — obowiązkowy
        {"tag": "nowy-lead", "color": 1}
    ],
    "categories": ["kancelaria-prawna"]
}
```

### Tagi źródeł (obowiązkowe, jeden na leada):
| Źródło | Tag |
|---|---|
| Rejestr Adwokatury | `źródło:ora` |
| Rejestr Radców | `źródło:oirp` |
| Google Maps | `źródło:google-maps` |
| Panoramafirm | `źródło:panoramafirm` |
| LinkedIn/Apify | `źródło:linkedin` |

---

## Konfiguracja (config.py)

```python
DAILY_LIMIT = 100          # łączny limit leadów dziennie (0 = bez limitu)
DELAY_SECONDS = 2          # opóźnienie między requestami HTTP
REQUEST_TIMEOUT = 10       # timeout requestów w sekundach

SOURCE_LIMITS = {
    "ora":          40,    # ile leadów z tego źródła dziennie
    "oirp":         30,
    "google_maps":  20,
    "panoramafirm": 10,
}

GOOGLE_MAPS_API_KEY = ""   # uzupełnić
APIFY_API_KEY = ""         # uzupełnić

KEYWORDS = [
    "prawo rodzinne",
    "rozwód",
    "adwokat rodzinny",
    "kancelaria rodzinna",
    "podział majątku",
    "alimenty"
]
```

---

## Źródła danych — szczegóły implementacji

### 1. ORA — Rejestr Adwokatury (`ora.py`)
- URL: `https://adwokatura.pl/koa/wyszukaj-adwokata/`
- Filtruj po specjalizacji: "prawo rodzinne"
- Paginacja przez parametry GET
- Pobierz: imię nazwisko, miasto, email, telefon, specjalizacja
- Używaj `requests` + `BeautifulSoup`

### 2. OIRP — Rejestr Radców (`oirp.py`)
- URL: `https://kirp.pl/wyszukiwarka-radcow-prawnych/`
- Analogicznie jak ORA
- Filtruj po specjalizacji: prawo rodzinne / cywilne

### 3. Google Maps (`google_maps.py`)
- Użyj Google Places API (`textsearch`)
- Query: `"kancelaria adwokacka prawo rodzinne" + miasto`
- Iteruj przez główne miasta Polski (lista w config)
- Pola: name, formatted_address, formatted_phone_number, website, email (ze strony www)
- Jeśli brak emaila w Places API → wejdź na stronę www i wyciągnij email regexem

### 4. Panoramafirm (`panoramafirm.py`)
- URL: `https://panoramafirm.pl/szukaj?q=kancelaria+prawna+prawo+rodzinne`
- Paginacja po stronach
- Pobierz: nazwa, adres, telefon, email, www

---

## Klasa CRMClient (`crm.py`)

Zaimplementuj następujące metody:

```python
class CRMClient:
    def add_lead(self, data: dict) -> dict         # POST /clients
    def get_all_leads(self) -> list                # GET /clients (wszystkie strony)
    def lead_exists(self, email: str, phone: str) -> bool  # deduplikacja
    def add_note(self, client_id: int, note: str)  # POST /clients/{id}/notes
    def update_lead(self, client_id: int, data: dict)      # PUT /client/{id}
```

---

## Deduplicator (`deduplicator.py`)

- Przy starcie scrapera pobierz wszystkie emaile i telefony z CRM do setu w pamięci
- Przed każdym dodaniem leada sprawdź czy email LUB telefon już istnieje
- Jeśli istnieje → pomiń i zaloguj jako `DUPLICATE`
- Cache odświeżaj co 1h podczas długich sesji

---

## Orchestrator (`main.py`)

```python
# Logika działania:
1. Załaduj config
2. Pobierz istniejące leady z CRM (do deduplikacji)
3. Dla każdego źródła (wg priorytetów):
   a. Uruchom scraper
   b. Deduplikuj
   c. Wrzuć do CRM z tagiem źródła
   d. Sprawdź dzienny limit
   e. Czekaj DELAY_SECONDS między requestami
4. Zapisz log z podsumowaniem

# Uruchomienie:
python main.py                    # wszystkie źródła, domyślny limit
python main.py --source ora       # tylko jedno źródło
python main.py --limit 50         # nadpisz dzienny limit
python main.py --dry-run          # tylko pokaż co by dodał, nie wrzuca do CRM
```

---

## Logowanie

- Plik: `logs/scraper.log`
- Format: `2024-01-15 10:23:45 | ORA | ADDED | Kancelaria Kowalski | kraków | kontakt@kancelaria.pl`
- Statusy: `ADDED`, `DUPLICATE`, `SKIPPED`, `ERROR`
- Na końcu sesji wypisz summary: ile dodano, ile duplikatów, ile błędów

---

## Requirements.txt

```
requests==2.31.0
beautifulsoup4==4.12.2
lxml==5.1.0
python-dotenv==1.0.0
```

---

## Ważne zasady

1. **Zawsze respektuj `robots.txt`** przed scrapowaniem domeny
2. **Delay między requestami** — minimum 2 sekundy, nie bombarduj serwerów
3. **User-Agent** — ustaw sensowny: `Mozilla/5.0 (compatible; LegalLeadBot/1.0)`
4. **Obsługa błędów** — każdy scraper musi mieć try/except i nie crashować całego pipeline'u
5. **Nie wrzucaj leadów bez emaila I bez telefonu** — minimum jedno z dwóch musi być
6. **Dry-run mode** — zawsze testuj najpierw z `--dry-run`

---

## Kolejność implementacji

1. `config.py` — konfiguracja
2. `crm.py` — wrapper CRM + test połączenia
3. `deduplicator.py`
4. `sources/base.py` — bazowa klasa
5. `sources/ora.py` — pierwsze źródło, przetestuj end-to-end
6. `main.py` — orchestrator
7. Pozostałe źródła: `oirp.py`, `panoramafirm.py`, `google_maps.py`

Zacznij od kroku 1 i testuj każdy moduł osobno zanim przejdziesz do następnego.
