# /opt/docker/dev/service_finder/backend/app/models/marketplace/service.py import uuid from datetime import datetime from typing import Any, List, Optional from sqlalchemy import Integer, String, Boolean, DateTime, ForeignKey, text, Text, Float, Index, Numeric, BigInteger from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB from geoalchemy2 import Geometry from sqlalchemy.sql import func # MB 2.0: Központi aszinkron adatbázis motorból húzzuk be a Base-t from app.database import Base class ServiceProfile(Base): """ Szerviz szolgáltató adatai (v1.3.1). """ __tablename__ = "service_profiles" __table_args__ = ( Index('idx_service_fingerprint', 'fingerprint', unique=True), {"schema": "marketplace"} ) id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), unique=True) parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("marketplace.service_profiles.id")) fingerprint: Mapped[str] = mapped_column(String(255), index=True, nullable=False) location: Mapped[Any] = mapped_column(Geometry(geometry_type='POINT', srid=4326, spatial_index=False), index=True) status: Mapped[str] = mapped_column(String(20), server_default=text("'ghost'"), index=True) last_audit_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) google_place_id: Mapped[Optional[str]] = mapped_column(String(100), unique=True) rating: Mapped[Optional[float]] = mapped_column(Float) user_ratings_total: Mapped[Optional[int]] = mapped_column(Integer) # Aggregated verified review ratings (Social 3) rating_verified_count: Mapped[Optional[int]] = mapped_column(Integer, server_default=text("0")) rating_price_avg: Mapped[Optional[float]] = mapped_column(Float) rating_quality_avg: Mapped[Optional[float]] = mapped_column(Float) rating_time_avg: Mapped[Optional[float]] = mapped_column(Float) rating_communication_avg: Mapped[Optional[float]] = mapped_column(Float) rating_overall: Mapped[Optional[float]] = mapped_column(Float) last_review_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) vibe_analysis: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb")) social_links: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb")) specialization_tags: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb")) trust_score: Mapped[int] = mapped_column(Integer, default=30) is_verified: Mapped[bool] = mapped_column(Boolean, default=False) verification_log: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb")) opening_hours: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb")) contact_phone: Mapped[Optional[str]] = mapped_column(String) contact_email: Mapped[Optional[str]] = mapped_column(String) website: Mapped[Optional[str]] = mapped_column(String) bio: Mapped[Optional[str]] = mapped_column(Text) # Kapcsolatok organization: Mapped["Organization"] = relationship("Organization", back_populates="service_profile") expertises: Mapped[List["ServiceExpertise"]] = relationship("ServiceExpertise", back_populates="service") reviews: Mapped[List["ServiceReview"]] = relationship("ServiceReview", back_populates="service") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), onupdate=func.now()) class ExpertiseTag(Base): """ Szakmai címkék mesterlistája (MB 2.0). Ez a tábla vezérli a robotok keresését és a Gamification pontozást is. """ __tablename__ = "expertise_tags" __table_args__ = {"schema": "marketplace"} id: Mapped[int] = mapped_column(Integer, primary_key=True) # Egyedi azonosító kulcs (pl. 'ENGINE_REBUILD') key: Mapped[str] = mapped_column(String(50), unique=True, index=True) # Megjelenítendő nevek name_hu: Mapped[Optional[str]] = mapped_column(String(100)) name_en: Mapped[Optional[str]] = mapped_column(String(100)) # Főcsoport (pl. 'MECHANICS', 'ELECTRICAL', 'EMERGENCY') category: Mapped[Optional[str]] = mapped_column(String(30), index=True) # --- 🎮 GAMIFICATION ÉS DISCOVERY --- # Hivatalos címke (True) vagy júzer/robot által javasolt (False) is_official: Mapped[bool] = mapped_column(Boolean, default=True, server_default=text("true")) # Ha júzer javasolta, itt tároljuk, ki volt az (XP jóváíráshoz) suggested_by_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id")) # ÁLLÍTHATÓ PONTÉRTÉK: Az adatbázisból jön, így bármikor módosítható. # Ritka szakmáknál magasabb, gyakoriaknál alacsonyabb érték állítható be. discovery_points: Mapped[int] = mapped_column(Integer, default=10, server_default=text("10")) # Robot kulcsszavak (JSONB): ["fék", "betét", "tárcsa", "fékfolyadék"] # A Scout robot ez alapján azonosítja be a szervizt a weboldala alapján. search_keywords: Mapped[Any] = mapped_column(JSONB, server_default=text("'[]'::jsonb")) # Népszerűségi mutató (hányszor lett felhasználva a rendszerben) usage_count: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0")) # UI ikon azonosító (pl. 'wrench', 'tire-flat', 'car-electric') icon: Mapped[Optional[str]] = mapped_column(String(50)) # Leírás a szakmáról (Adminisztratív célokra) description: Mapped[Optional[str]] = mapped_column(Text) # Időbélyegek created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), onupdate=func.now()) # --- KAPCSOLATOK --- services: Mapped[List["ServiceExpertise"]] = relationship("ServiceExpertise", back_populates="tag") # Visszamutatás a beküldőre (ha van) suggested_by: Mapped[Optional["Person"]] = relationship("Person") class ServiceExpertise(Base): """ KAPCSOLÓTÁBLA: Ez köti össze a szervizt a szakmáival. Itt tároljuk, hogy az adott szerviznél mennyire validált egy szakma. """ __tablename__ = "service_expertises" __table_args__ = {"schema": "marketplace"} id: Mapped[int] = mapped_column(Integer, primary_key=True) service_id: Mapped[int] = mapped_column(Integer, ForeignKey("marketplace.service_profiles.id", ondelete="CASCADE")) expertise_id: Mapped[int] = mapped_column(Integer, ForeignKey("marketplace.expertise_tags.id", ondelete="CASCADE")) # Mennyire biztos ez a tudás? (0: robot találta, 1: júzer mondta, 2: igazolt szakma) confidence_level: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0")) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=text("now()")) # Kapcsolatok visszafelé service = relationship("ServiceProfile", back_populates="expertises") tag = relationship("ExpertiseTag", back_populates="services") class ServiceStaging(Base): """ Hunter (robot) adatok tárolója. """ __tablename__ = "service_staging" __table_args__ = ( Index('idx_staging_fingerprint', 'fingerprint', unique=True), {"schema": "marketplace"} ) id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String, index=True, nullable=False) postal_code: Mapped[Optional[str]] = mapped_column(String(10), index=True) city: Mapped[Optional[str]] = mapped_column(String(100), index=True) full_address: Mapped[Optional[str]] = mapped_column(String) fingerprint: Mapped[str] = mapped_column(String(255), nullable=False) raw_data: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb")) # Additional contact and identification fields contact_phone: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) website: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) external_id: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, index=True) status: Mapped[str] = mapped_column(String(20), server_default=text("'pending'"), index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) class DiscoveryParameter(Base): """ Robot vezérlési paraméterek adminból. """ __tablename__ = "discovery_parameters" __table_args__ = {"schema": "marketplace"} id: Mapped[int] = mapped_column(Integer, primary_key=True) city: Mapped[str] = mapped_column(String(100)) keyword: Mapped[str] = mapped_column(String(100)) is_active: Mapped[bool] = mapped_column(Boolean, default=True) last_run_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))