""" Provider (szolgáltató) Pydantic sémák a crowdsourced partnerkeresőhöz. Tartalmazza a keresési, gyors felvételi és szerkesztési sémákat. P1 CRITICAL ALIGN (2026-06-17): Atomizált címmezők bevezetése. - street -> address_street_name, address_street_type, address_house_number - A ProviderSearchResult is tartalmazza az atomizált címmezőket. P3 UNIFIED ADDRESS REFACTOR (2026-07-01): Egységes AddressIn/AddressOut. - ProviderSearchResult: address_detail: Optional[AddressOut] a meglévő flat mezők mellett - ProviderQuickAddIn: address_detail: Optional[AddressIn] a meglévő flat mezők mellett - ProviderUpdateIn: address_detail: Optional[AddressIn] a meglévő flat mezők mellett - A flat mezők (address_zip, address_street_name, stb.) MEGMARADNAK a backward compatibility miatt 4-Level Category Architecture (2026-06-17): =========================================== - CategoryTreeNode: Hierarchikus fa struktúra a frontend checkboxaihoz - CategoryAutocompleteItem: Szöveges kereső találatok Szint 2 és Szint 3 címkékhez - ProviderUpdateIn: kibővítve category_ids listával a hibrid mentéshez """ from pydantic import BaseModel, Field, ConfigDict from typing import Optional, List from app.schemas.address import AddressIn, AddressOut # ────────────────────────────────────────────── # 4-LEVEL CATEGORY SCHEMAS (2026-06-17) # ────────────────────────────────────────────── class CategoryTreeNode(BaseModel): """Egy csomópont a kategória hierarchikus fában. Tartalmazza a gyermek csomópontokat rekurzívan. """ id: int key: str name_i18n: dict = Field(default_factory=dict, description="Lokalizált név (JSONB: {\"hu\": \"...\", \"en\": \"...\"})") description_i18n: Optional[dict] = Field(None, description="Lokalizált leírás (JSONB)") level: int = 0 path: Optional[str] = None is_official: bool = True children: List["CategoryTreeNode"] = Field(default_factory=list) model_config = ConfigDict(from_attributes=True) class CategoryAutocompleteItem(BaseModel): """Egy találat a kategória autocomplete keresőben. Csak Szint 2 (Szakma) és Szint 3 (Specifikus feladat) címkéket listáz, ahol is_official=True. """ id: int key: str name_i18n: dict = Field(default_factory=dict, description="Lokalizált név (JSONB: {\"hu\": \"...\", \"en\": \"...\"})") level: int = 0 path: Optional[str] = None parent_id: Optional[int] = None model_config = ConfigDict(from_attributes=True) # ────────────────────────────────────────────── # EXISTING SCHEMAS (extended) # ────────────────────────────────────────────── class ExpertiseCategoryOut(BaseModel): """Expertise kategória kimeneti sémája a frontend dropdown számára. Kibővítve a 4-level hierarchy mezőkkel. """ id: int key: str name_i18n: dict = Field(default_factory=dict, description="Lokalizált név (JSONB: {\"hu\": \"...\", \"en\": \"...\"})") category: Optional[str] = None level: int = 0 parent_id: Optional[int] = None path: Optional[str] = None model_config = ConfigDict(from_attributes=True) class CategoryInfo(BaseModel): """Egy kategória rövid adatai a keresési találatokhoz. Tartalmazza a kategória ID-ját, nevét és szintjét, hogy a frontend meg tudja jeleníteni a kapcsolódó szolgáltatásokat a kártyákon. """ id: int name_i18n: dict = Field(default_factory=dict, description="Lokalizált név (JSONB: {\"hu\": \"...\", \"en\": \"...\"})") level: int = 0 key: str class ProviderSearchResult(BaseModel): """Egy szolgáltató keresési találatának adatai. P1 CRITICAL ALIGN (2026-06-17): Atomizált címmezők. - address_street_name, address_street_type, address_house_number - A frontend ezeket intelligensen fűzi össze megjelenítéskor. P3 UNIFIED ADDRESS REFACTOR (2026-07-01): address_detail mező. - address_detail: Optional[AddressOut] — egységes cím objektum. - A flat mezők (address_zip, address_street_name, stb.) MEGMARADNAK a backward compatibility miatt. P0 PHASE 2 ADDRESS UNIFICATION (2026-07-07): - address_detail: Optional[AddressOut] — first-class citizen. - A flat mezők [DEPRECATED] — Phase 3-ban törlendő. FEATURE (2026-06-17): categories mező. - A szolgáltatóhoz tartozó kategóriák (ExpertiseTag) listája. - Tartalmazza az ID-t, nevet és szintet a frontend chip megjelenítéshez. """ id: int name: str category: Optional[str] = None specialization: List[str] = Field(default_factory=list) categories: List[CategoryInfo] = Field(default_factory=list) city: Optional[str] = Field(None, description="[DEPRECATED] Use address_detail.city instead") address: Optional[str] = Field(None, description="[DEPRECATED] Use address_detail.full_address_text instead") address_zip: Optional[str] = Field(None, description="[DEPRECATED] Use address_detail.zip instead") address_street_name: Optional[str] = Field(None, description="[DEPRECATED] Use address_detail.street_name instead") address_street_type: Optional[str] = Field(None, description="[DEPRECATED] Use address_detail.street_type instead") address_house_number: Optional[str] = Field(None, description="[DEPRECATED] Use address_detail.house_number instead") plus_code: Optional[str] = None contact_phone: Optional[str] = None contact_email: Optional[str] = None website: Optional[str] = None tags: List[str] = Field(default_factory=list) source: str = Field(..., description="Forrás: verified_org | staged_data | crowd_added") is_verified: bool = False rating: Optional[float] = None supported_vehicle_classes: Optional[List[str]] = None specializations: Optional[dict] = None # P0 PHASE 2: Egységes cím objektum (first-class citizen) address_detail: Optional[AddressOut] = None model_config = ConfigDict(from_attributes=True) class ProviderSearchResponse(BaseModel): """Keresési eredmények lapozással.""" results: List[ProviderSearchResult] = Field(default_factory=list) total: int = 0 page: int = 1 per_page: int = 20 class ProviderQuickAddIn(BaseModel): """Gyors szolgáltató felvétel bemeneti adatai. P1 CRITICAL ALIGN (2026-06-17): Atomizált címmezők. - street ELTÁVOLÍTVA, helyette address_street_name, address_street_type, address_house_number P3 UNIFIED ADDRESS REFACTOR (2026-07-01): address_detail mező. - address_detail: Optional[AddressIn] — egységes cím objektum. - A flat mezők (address_zip, address_street_name, stb.) MEGMARADNAK a backward compatibility miatt. P0 PHASE 2 ADDRESS UNIFICATION (2026-07-07): - address_detail: Optional[AddressIn] — first-class citizen. - A flat mezők [DEPRECATED] — Phase 3-ban törlendő. P0 CRITICAL BUGFIX (2026-06-17): category_id most már opcionális. - A 4-level kategória rendszer bevezetésével a frontend a category_ids tömböt használja a kategóriák kiválasztására, nem a régi category_id-t. - category_id megtartva opcionálisként a backward compatibility miatt. - category_ids: A kiválasztott kategória ID-k listája (Szint 0-3 checkboxokból) - new_tags: A user által gépelt új (még nem létező) címkék listája """ name: str = Field(..., min_length=2, max_length=200, description="Szolgáltató neve") category_id: Optional[int] = Field(None, ge=1, description="Elsődleges kategória ID (opcionális, backward compatibility)") category_ids: Optional[List[int]] = Field(None, description="Kiválasztott kategória ID-k (Szint 0-3 checkboxokból)") new_tags: Optional[List[str]] = Field(None, description="User által gépelt új címkenevek (is_official=False, level=3)") city: Optional[str] = Field(None, max_length=100, description="[DEPRECATED] Use address_detail.city instead") address_zip: Optional[str] = Field(None, max_length=20, description="[DEPRECATED] Use address_detail.zip instead") address_street_name: Optional[str] = Field(None, max_length=150, description="[DEPRECATED] Use address_detail.street_name instead") address_street_type: Optional[str] = Field(None, max_length=50, description="[DEPRECATED] Use address_detail.street_type instead") address_house_number: Optional[str] = Field(None, max_length=20, description="[DEPRECATED] Use address_detail.house_number instead") plus_code: Optional[str] = Field(None, max_length=20, description="Google Plus Code (pl. 8FVC9X8V+XV)") contact_phone: Optional[str] = Field(None, max_length=50, description="Telefonszám") contact_email: Optional[str] = Field(None, max_length=255, description="Email cím") website: Optional[str] = Field(None, max_length=255, description="Weboldal URL") tags: Optional[List[str]] = Field(None, description="Szolgáltatás címkék (specialization_tags)") supported_vehicle_classes: Optional[List[str]] = Field( None, description="Támogatott járműosztályok (pl. ['car', 'hgv', 'van'])" ) specializations: Optional[dict] = Field( None, description="Specializációk (pl. {'brands': ['Volvo'], 'propulsion': ['EV']})" ) # P0 PHASE 2: Egységes cím objektum (first-class citizen) address_detail: Optional[AddressIn] = None class ProviderQuickAddResponse(BaseModel): """Gyors szolgáltató felvétel válasza.""" id: int name: str status: str = "pending_verification" message: str = "Szolgáltató sikeresen rögzítve. Ellenőrzés alatt." earned_points: int = 0 class ProviderUpdateIn(BaseModel): """Szolgáltató adatainak szerkesztése (PUT /providers/{id}). P1 CRITICAL ALIGN (2026-06-17): Atomizált címmezők. - street ELTÁVOLÍTVA, helyette address_street_name, address_street_type, address_house_number - Tartalmazza a contact_phone, contact_email, website és tags mezőket is. P3 UNIFIED ADDRESS REFACTOR (2026-07-01): address_detail mező. - address_detail: Optional[AddressIn] — egységes cím objektum. - A flat mezők (address_zip, address_street_name, stb.) MEGMARADNAK a backward compatibility miatt. P0 PHASE 2 ADDRESS UNIFICATION (2026-07-07): - address_detail: Optional[AddressIn] — first-class citizen. - A flat mezők [DEPRECATED] — Phase 3-ban törlendő. 4-Level Category (2026-06-17): - category_ids: A kiválasztott kategória ID-k listája (Szint 0-3) - new_tags: A user által gépelt, új (még nem létező) címkék listája """ name: str = Field(..., min_length=2, max_length=200, description="Szolgáltató neve") city: Optional[str] = Field(None, max_length=100, description="[DEPRECATED] Use address_detail.city instead") address_zip: Optional[str] = Field(None, max_length=20, description="[DEPRECATED] Use address_detail.zip instead") address_street_name: Optional[str] = Field(None, max_length=150, description="[DEPRECATED] Use address_detail.street_name instead") address_street_type: Optional[str] = Field(None, max_length=50, description="[DEPRECATED] Use address_detail.street_type instead") address_house_number: Optional[str] = Field(None, max_length=20, description="[DEPRECATED] Use address_detail.house_number instead") plus_code: Optional[str] = Field(None, max_length=20, description="Google Plus Code (pl. 8FVC9X8V+XV)") contact_phone: Optional[str] = Field(None, max_length=50, description="Telefonszám") contact_email: Optional[str] = Field(None, max_length=255, description="Email cím") website: Optional[str] = Field(None, max_length=255, description="Weboldal URL") tags: Optional[List[str]] = Field(None, description="Szolgáltatás címkék (specialization_tags)") category_ids: Optional[List[int]] = Field( None, description="Kiválasztott kategória ID-k (Szint 0-3 checkboxokból)" ) new_tags: Optional[List[str]] = Field( None, description="User által gépelt új címkenevek (is_official=False, level=3)" ) supported_vehicle_classes: Optional[List[str]] = Field( None, description="Támogatott járműosztályok (pl. ['car', 'hgv', 'van'])" ) specializations: Optional[dict] = Field( None, description="Specializációk (pl. {'brands': ['Volvo'], 'propulsion': ['EV']})" ) # P0 PHASE 2: Egységes cím objektum (first-class citizen) address_detail: Optional[AddressIn] = None class ProviderUpdateResponse(BaseModel): """Szolgáltató szerkesztés válasza.""" id: int name: str message: str = "Szolgáltató adatai sikeresen frissítve." earned_points: int = 0