szolgáltatók beálltásai, szerkesztése , létrehozása
This commit is contained in:
271
plans/logic_spec_provider_taxonomy_search.md
Normal file
271
plans/logic_spec_provider_taxonomy_search.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# 🏗️ Logic Spec: Provider Taxonomy Audit & Smart Search API
|
||||
|
||||
## 1. Modul Célja és Masterbook 2.0 Illeszkedés
|
||||
|
||||
**Mérföldkő:** Crowdsourced Partnerkereső (Marketplace Engine)
|
||||
**Cél:** A szolgáltatói kategóriarendszer (taxonómia) feltöltése és egy okos, egyesített kereső API (search + quick-add) megvalósítása a crowdsourced partnerkereséshez.
|
||||
|
||||
### Masterbook 2.0 Kapcsolódás
|
||||
- **Epic:** Marketplace & Service Discovery (08_Marketplace)
|
||||
- **Robot Kapcsolat:** A meglévő `service_staging` adatok (robotok által gyűjtött) publikusan kereshetővé tétele
|
||||
- **Üzleti Logika:** Bárki (még nem verifikált) gyorsan felvehet egy szolgáltatót, ami automatikusan a `service_staging` táblába kerül, ahol a robotok később feldolgozhatják
|
||||
|
||||
## 2. Adatbázis Audit Eredmények
|
||||
|
||||
### Meglévő Táblák Állapota
|
||||
|
||||
| Tábla | Séma | Állapot | Rekordok |
|
||||
|-------|------|---------|----------|
|
||||
| `expertise_tags` | `marketplace` | **ÜRES** - nincs egyetlen kategória sem | 0 |
|
||||
| `service_specialties` | `marketplace` | **ÜRES** - egyszerű (id, parent_id, name, slug) | 0 |
|
||||
| `service_providers` | `marketplace` | **ÜRES** - crowdsourced provider tábla | 0 |
|
||||
| `organizations` (org_type='service_provider') | `fleet` | **NINCS** ilyen rekord | 0 |
|
||||
| `service_staging` | `marketplace` | Van benne ~2000+ rekord (robot gyűjtés) | ~2000 |
|
||||
|
||||
### Használandó Meglévő Struktúrák
|
||||
|
||||
1. **`marketplace.expertise_tags`** - A kategória mestertábla (twin: name_hu/name_en, search_keywords JSONB). Ez a legalkalmasabb.
|
||||
2. **`fleet.organizations.aliases`** - JSONB mező a szolgáltató alternatív neveihez (már létezik!)
|
||||
3. **`fleet.organizations.tags`** - JSONB mező a crowdsourced címkékhez (már létezik!)
|
||||
4. **`marketplace.service_staging`** - Robotok által gyűjtött adatok (már létezik adatokkal)
|
||||
5. **`marketplace.service_providers`** - Crowdsourced provider tábla (üres, de használható)
|
||||
|
||||
## 3. Technikai Terv
|
||||
|
||||
### 3.1 Seed Script: `backend/app/scripts/seed_expertise_tags.py`
|
||||
|
||||
**Fájl:** `backend/app/scripts/seed_expertise_tags.py`
|
||||
|
||||
Feltölti az `expertise_tags` táblát az alábbi alapkategóriákkal:
|
||||
|
||||
| key | name_hu | name_en | category | search_keywords |
|
||||
|-----|---------|---------|----------|-----------------|
|
||||
| auto_szerelo | Autószerelő | Car Mechanic | vehicle_service | ["szerelő", "autószerelő", "mechanic", "garage"] |
|
||||
| motor_szerelo | Motorkerékpár szerelő | Motorcycle Mechanic | vehicle_service | ["motor", "motorszerelő", "motorcycle"] |
|
||||
| gumiszerviz | Gumiszerviz | Tire Service | vehicle_service | ["gumi", "gumis", "tire", "tyre", "abroncs"] |
|
||||
| karosszerialakatos | Karosszérialakatos | Body Shop | body_paint | ["karosszéria", "lakatos", "bodyshop"] |
|
||||
| fényező | Fényező | Painter | body_paint | ["fényező", "fényezés", "painter", "spray"] |
|
||||
| autómentő | Autómentő / Motormentő | Tow Truck / Roadside | roadside | ["mentő", "autómentő", "tow", "roadside"] |
|
||||
| benzinkút | Benzinkút | Gas Station | fuel | ["benzin", "kút", "gas station", "fuel"] |
|
||||
| alkatrész_kereskedés | Alkatrész kereskedés | Parts Store | parts | ["alkatrész", "parts", "spares"] |
|
||||
| vizsgaállomás | Vizsgaállomás | Inspection Station | inspection | ["vizsga", "műszaki", "inspection", "MOT"] |
|
||||
| autókozmetika | Autókozmetika | Car Detailing | detailing | ["kozmetika", "detailing", "takarítás"] |
|
||||
| egyéb | Egyéb szolgáltató | Other Service | other | ["egyéb", "other", "szolgáltató"] |
|
||||
|
||||
**Futtatás:** `docker exec sf_api python3 /app/backend/app/scripts/seed_expertise_tags.py`
|
||||
|
||||
### 3.2 API Végpont: `GET /api/v1/providers/search`
|
||||
|
||||
**Fájl:** `backend/app/api/v1/endpoints/providers.py`
|
||||
|
||||
#### Végpont Specifikáció
|
||||
|
||||
```
|
||||
GET /api/v1/providers/search?q={keresőszó}&category={kategória}&city={város}&limit={limit}&offset={offset}
|
||||
```
|
||||
|
||||
#### Keresés Logikája (három tábla egyesítése)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[GET /providers/search?q=kereső] --> B{Ellenőrizd a q paramétert}
|
||||
B -->|Van| C[Három tábla egyesített keresése]
|
||||
B -->|Nincs| D[Kategória/Város szűrés, ha van]
|
||||
|
||||
C --> E1[1. fleet.organizations]
|
||||
C --> E2[2. marketplace.service_staging]
|
||||
C --> E3[3. marketplace.service_providers]
|
||||
|
||||
E1 --> F1{org_type = service_provider}
|
||||
F1 --> G1[ILIKE a name-re]
|
||||
F1 --> G2[ILIKE az aliases JSONB-ben]
|
||||
|
||||
E2 --> G3[ILIKE a name-re]
|
||||
E2 --> G4[ILIKE a city-re]
|
||||
|
||||
E3 --> G5[ILIKE a name-re]
|
||||
|
||||
D --> H[Közös UNION / egységesítés]
|
||||
H --> I[Forrás jelölés: verified_org / staged_data / crowd_added]
|
||||
I --> J[Rendezes + lapozás]
|
||||
J --> K[Response]
|
||||
```
|
||||
|
||||
#### Response Schema (ProviderSearchResult)
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "MOL Autószerviz",
|
||||
"category": null,
|
||||
"specialization": ["autószerelő", "gumiszerviz"],
|
||||
"city": "Budapest",
|
||||
"address": "Budapest, Kossuth u. 1",
|
||||
"source": "verified_org",
|
||||
"is_verified": true,
|
||||
"rating": 4.5
|
||||
}
|
||||
],
|
||||
"total": 42,
|
||||
"page": 1,
|
||||
"per_page": 20
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 API Végpont: `POST /api/v1/providers/quick-add`
|
||||
|
||||
**Fájl:** `backend/app/api/v1/endpoints/providers.py`
|
||||
|
||||
#### Végpont Specifikáció
|
||||
|
||||
```
|
||||
POST /api/v1/providers/quick-add
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Teszt Autószerviz",
|
||||
"category_id": 1,
|
||||
"city": "Budapest",
|
||||
"street": "Kossuth utca 1",
|
||||
"tags": ["olcsó", "gyors"]
|
||||
}
|
||||
```
|
||||
|
||||
#### Folyamat
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[POST /providers/quick-add] --> B[Authentikáció: current_user]
|
||||
B --> C[Validáció: name kötelező]
|
||||
C --> D{Kategória ID ellenőrzése}
|
||||
D -->|Létezik| E[Létrehozás: Organization]
|
||||
D -->|Nem létezik| F[400: Invalid category]
|
||||
|
||||
E --> G[Organization létrehozása]
|
||||
G --> H[org_type = service_provider]
|
||||
H --> I[is_verified = False]
|
||||
I --> J[tags = megadott tömbből]
|
||||
|
||||
J --> K[ServiceProfile létrehozása]
|
||||
K --> L[expertise_tags kapcsolat]
|
||||
|
||||
L --> M[Branch létrehozása a címmel]
|
||||
M --> N[commit]
|
||||
N --> O[Response: quick-add success]
|
||||
```
|
||||
|
||||
#### Request Schema (ProviderQuickAddIn)
|
||||
|
||||
| Mező | Típus | Kötelező | Leírás |
|
||||
|------|-------|----------|--------|
|
||||
| name | string | Igen | Szolgáltató neve (2-200 karakter) |
|
||||
| category_id | int | Igen | Kategória ID (expertise_tags.id) |
|
||||
| city | string | Nem | Város |
|
||||
| street | string | Nem | Utca/házszám |
|
||||
| tags | string[] | Nem | Címkék (pl. ["olcsó", "gyors"]) |
|
||||
|
||||
#### Response Schema (ProviderQuickAddResponse)
|
||||
|
||||
| Mező | Típus | Leírás |
|
||||
|------|-------|--------|
|
||||
| id | int | Az új szervezet ID-ja |
|
||||
| name | string | A szolgáltató neve |
|
||||
| status | string | "pending_verification" |
|
||||
| message | string | Sikeres üzenet |
|
||||
|
||||
### 3.4 Router Regisztráció
|
||||
|
||||
A `providers.py`-t be kell regisztrálni az `api.py`-ban:
|
||||
|
||||
```python
|
||||
from app.api.v1.endpoints import providers
|
||||
|
||||
api_router.include_router(providers.router, prefix="/providers", tags=["Providers"])
|
||||
```
|
||||
|
||||
### 3.5 Szükséges Schémák
|
||||
|
||||
**Fájl:** `backend/app/schemas/provider.py` (ÚJ)
|
||||
|
||||
```python
|
||||
class ProviderSearchResult(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
category: Optional[str] = None
|
||||
specialization: List[str] = []
|
||||
city: Optional[str] = None
|
||||
address: Optional[str] = None
|
||||
source: str # "verified_org" | "staged_data" | "crowd_added"
|
||||
is_verified: bool = False
|
||||
rating: Optional[float] = None
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
class ProviderSearchResponse(BaseModel):
|
||||
results: List[ProviderSearchResult]
|
||||
total: int
|
||||
page: int = 1
|
||||
per_page: int = 20
|
||||
|
||||
class ProviderQuickAddIn(BaseModel):
|
||||
name: str = Field(..., min_length=2, max_length=200)
|
||||
category_id: int = Field(..., ge=1)
|
||||
city: Optional[str] = Field(None, max_length=100)
|
||||
street: Optional[str] = Field(None, max_length=255)
|
||||
tags: List[str] = Field(default_factory=list)
|
||||
|
||||
class ProviderQuickAddResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
status: str = "pending_verification"
|
||||
message: str = "Szolgáltató sikeresen rögzítve. Ellenőrzés alatt."
|
||||
```
|
||||
|
||||
### 3.6 Service Réteg
|
||||
|
||||
**Fájl:** `backend/app/services/provider_service.py` (ÚJ)
|
||||
|
||||
A service réteg tartalmazza:
|
||||
- `search_providers(db, q, category, city, limit, offset)` - Egyesített keresés
|
||||
- `quick_add_provider(db, data, user_id)` - Gyors szolgáltató felvétel
|
||||
|
||||
## 4. Adatmodell Változások
|
||||
|
||||
### 4.1 NINCS új modell vagy migráció!
|
||||
A meglévő modellek és adatbázis táblák TELJESEN lefedik a funkcionalitást:
|
||||
- `marketplace.expertise_tags` - már létezik, csak adat kell
|
||||
- `fleet.organizations` - már van `aliases`, `tags`, `org_type` mező
|
||||
- `marketplace.service_staging` - már létezik adatokkal
|
||||
- `marketplace.service_providers` - már létezik (üres)
|
||||
|
||||
### 4.2 Csak Seed Adat Kell
|
||||
A `seed_expertise_tags.py` INSERT utasításokkal tölti fel az `expertise_tags` táblát.
|
||||
|
||||
## 5. Megvalósítási Terv
|
||||
|
||||
### 5.1 Végrehajtandó Lépések
|
||||
|
||||
| # | Lépés | Fájl | Leírás |
|
||||
|---|-------|------|--------|
|
||||
| 1 | Seed script | `backend/app/scripts/seed_expertise_tags.py` | Feltölti a 11 alapkategóriát |
|
||||
| 2 | Seed futtatás | Terminál | `docker exec sf_api python3 /app/backend/app/scripts/seed_expertise_tags.py` |
|
||||
| 3 | Pydantic schémák | `backend/app/schemas/provider.py` (ÚJ) | ProviderSearchResult, ProviderQuickAddIn |
|
||||
| 4 | Service réteg | `backend/app/services/provider_service.py` (ÚJ) | search_providers, quick_add_provider |
|
||||
| 5 | Endpointok | `backend/app/api/v1/endpoints/providers.py` | GET /search, POST /quick-add |
|
||||
| 6 | Router regisztráció | `backend/app/api/v1/api.py` | providers router hozzáadása |
|
||||
| 7 | Ellenőrző teszt | Terminál | API hívások tesztelése |
|
||||
|
||||
### 5.2 Függőségek
|
||||
- **Bemenet:** Meglévő modell réteg (Organization, ServiceStaging, ServiceProvider, ExpertiseTag)
|
||||
- **Kimenet:** A `/api/v1/providers/search` és `/api/v1/providers/quick-add` végpontok
|
||||
- **Blokkoló:** Nincs - minden meglévő kód használható
|
||||
|
||||
## 6. Admin Kontroll
|
||||
|
||||
A Seed script FUTTATHATÓ többször is (idempotens) - `ON CONFLICT DO NOTHING` klauzulával.
|
||||
|
||||
A kategóriák később az Admin felületen keresztül módosíthatók/bővíthetők.
|
||||
|
||||
## 7. Geo-Logika
|
||||
A keresés jelenleg NEM tartalmaz PostGIS távolságszűrést (az külön endpoint: `/search/match`). A `/providers/search` csak szöveges keresés (ILIKE).
|
||||
123
plans/logic_spec_provider_update_fix.md
Normal file
123
plans/logic_spec_provider_update_fix.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# 🔧 Logic Spec: Provider Update & Search Fix Csomag
|
||||
|
||||
## 🎯 Cél
|
||||
A szolgáltató cégek adatainak rögzítésében és szerkesztésében fellépő hibák javítása, valamint az adatok részletes rögzíthetőségének biztosítása.
|
||||
|
||||
## 📋 Problémák Összefoglalása
|
||||
|
||||
### 1. PUT /api/v1/providers/{id} → 404 Not Found (✅ MEGOLDVA)
|
||||
- **Root Cause**: A `sf_api` konténer nem volt újraindítva a providers modul kódfrissítése után. A `pre_start.sh` fájlban `uvicorn` `--reload` flag nélkül fut.
|
||||
- **Javítás**: `docker compose restart sf_api` (végrehajtva)
|
||||
- **Verifikáció**: PUT /providers/58 → 200 OK
|
||||
|
||||
### 2. GET /api/v1/providers/search → 500 Internal Server Error (❌ JAVÍTANDÓ)
|
||||
- **Hiba**: `AttributeError: Neither 'BinaryExpression' object nor 'Comparator' object has an attribute 'astext'`
|
||||
- **Hibás kód**: [`provider_service.py:207`](../backend/app/services/provider_service.py:207)
|
||||
```python
|
||||
(Organization.external_integration_config["source"].astext == "crowdsourced", literal("crowd_added")),
|
||||
```
|
||||
- **Root Cause**: Az [`external_integration_config`](../backend/app/models/marketplace/organization.py:109) `JSON` típusú oszlop. A `["source"]` subscript `BinaryExpression`-t ad vissza, amelyen NINCS `.astext`.
|
||||
- **Javítás**: `cast` használata:
|
||||
```python
|
||||
(cast(Organization.external_integration_config["source"], String) == "crowdsourced", literal("crowd_added")),
|
||||
```
|
||||
|
||||
### 3. Adat-healing: Régi címformátumú Organization rekordok (❌ JAVÍTANDÓ)
|
||||
- **Probléma**: Az Organization id=58 (`Autónyíri Kft.`) adatai a régi formátumban:
|
||||
- `street_name = "Egressy u. 4."` (nem atomizált)
|
||||
- `address_street_name = NULL`, `address_street_type = NULL`, `address_house_number = NULL`
|
||||
- `zip = NULL`
|
||||
- **Következmény**: A frontend DetailModal üres címet mutat.
|
||||
- **Javítás**: Adat-healing script a `street_name` mezőből atomizált komponensek kinyerésére.
|
||||
|
||||
### 4. Multi-source Update Probléma (❌ JAVÍTANDÓ)
|
||||
- **Probléma**: Az [`update_provider`](../backend/app/services/provider_service.py:477) CSAK `Organization`-ben keres. A search UNION-nal dolgozza fel a szolgáltatókat 3 forrásból (Organization, ServiceStaging, ServiceProvider).
|
||||
- **Javítás**: Multi-source update logika.
|
||||
|
||||
### 5. Szerver Restart Workflow Hiánya (❌ JAVÍTANDÓ)
|
||||
- **Probléma**: A [`pre_start.sh`](../backend/app/scripts/pre_start.sh) `--reload` nélkül indul.
|
||||
- **Javítás**: `--reload` flag fejlesztői módban.
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Érintett Fájlok
|
||||
|
||||
| Fájl | Változtatás | Prioritás |
|
||||
|------|-------------|-----------|
|
||||
| [`provider_service.py:207`](../backend/app/services/provider_service.py:207) | `.astext` → `cast()` | **KRITIKUS** |
|
||||
| [`provider_service.py:477-562`](../backend/app/services/provider_service.py:477) | Multi-source update | **MAGAS** |
|
||||
| Új: `heal_provider_addresses.py` | Adat-healing script | **MAGAS** |
|
||||
| [`pre_start.sh`](../backend/app/scripts/pre_start.sh) | `--reload` dev módban | **ALACSONY** |
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Részletes Javítási Terv
|
||||
|
||||
### 1. `.astext` → `cast()` javítás
|
||||
|
||||
**Fájl**: [`provider_service.py`](../backend/app/services/provider_service.py:207)
|
||||
|
||||
**Jelenlegi:**
|
||||
```python
|
||||
(Organization.external_integration_config["source"].astext == "crowdsourced", literal("crowd_added")),
|
||||
```
|
||||
|
||||
**Javított:**
|
||||
```python
|
||||
(cast(Organization.external_integration_config["source"], String) == "crowdsourced", literal("crowd_added")),
|
||||
```
|
||||
|
||||
### 2. Multi-source Update Logika
|
||||
|
||||
**Fájl**: [`provider_service.py:477`](../backend/app/services/provider_service.py:477)
|
||||
|
||||
Az `update_provider` ellenőrizze mindhárom forrást:
|
||||
1. `db.get(Organization, provider_id)` → meglévő logika
|
||||
2. `db.get(ServiceStaging, provider_id)` → migrálás Organization-be
|
||||
3. `db.get(ServiceProvider, provider_id)` → migrálás Organization-be
|
||||
4. Ha egyikben sem → ValueError
|
||||
|
||||
### 3. Adat-healing Script
|
||||
|
||||
**Fájl**: `backend/app/scripts/heal_provider_addresses.py`
|
||||
|
||||
1. Lekérdezni Organization rekordokat, ahol `org_type='service_provider'` ÉS `address_street_name IS NULL` ÉS `street_name IS NOT NULL`
|
||||
2. Regex: `r'^([^\d]+?)\s+(u\.|utca|út|tér|köz|sor|körút|liget|part|fasor|sétány|park|híd|sugárút|rakpart|dűlő|telep|szőlő)\s*(.*)$'`
|
||||
3. Frissíteni az atomizált mezőket
|
||||
4. Naplózás
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tesztelési Terv
|
||||
|
||||
### Teszt 1: search működés
|
||||
```bash
|
||||
docker compose exec sf_api python3 -c "
|
||||
import httpx, asyncio
|
||||
async def t():
|
||||
async with httpx.AsyncClient(base_url='http://localhost:8000') as c:
|
||||
r = await c.post('/api/v1/auth/login', data={'username': 'admin@profibot.hu', 'password': 'Admin123!'})
|
||||
t = r.json()['access_token']
|
||||
r2 = await c.get('/api/v1/providers/search', params={'q': 'Dunakeszi', 'limit': 5}, headers={'Authorization': f'Bearer {t}'})
|
||||
print(f'Search: {r2.status_code}')
|
||||
print(r2.text[:500])
|
||||
asyncio.run(t())
|
||||
"
|
||||
```
|
||||
|
||||
### Teszt 2: PUT működés
|
||||
```bash
|
||||
docker compose exec sf_api python3 -c "
|
||||
import httpx, asyncio
|
||||
async def t():
|
||||
async with httpx.AsyncClient(base_url='http://localhost:8000') as c:
|
||||
r = await c.post('/api/v1/auth/login', data={'username': 'admin@profibot.hu', 'password': 'Admin123!'})
|
||||
t = r.json()['access_token']
|
||||
r2 = await c.put('/api/v1/providers/58',
|
||||
json={'address_zip': '2120', 'city': 'Dunakeszi', 'name': 'Autónyíri Kft.'},
|
||||
headers={'Authorization': f'Bearer {t}'})
|
||||
print(f'PUT: {r2.status_code}')
|
||||
print(r2.text)
|
||||
asyncio.run(t())
|
||||
"
|
||||
```
|
||||
Reference in New Issue
Block a user