# /opt/docker/dev/service_finder/backend/app/models/marketplace/service.py import enum 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, ENUM as SQLEnum 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 ServiceStatus(str, enum.Enum): ghost = "ghost" # Nyers, robot által talált, nem validált active = "active" # Publikus, aktív szerviz flagged = "flagged" # Gyanús, kézi ellenőrzést igényel suspended = "suspended" # Felfüggesztett, tiltott szerviz 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[ServiceStatus] = mapped_column( SQLEnum(ServiceStatus, name="service_status", schema="marketplace"), server_default=ServiceStatus.ghost.value, nullable=False, 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) key: Mapped[str] = mapped_column(String(50), unique=True, index=True) name_hu: Mapped[Optional[str]] = mapped_column(String(100)) name_en: Mapped[Optional[str]] = mapped_column(String(100)) category: Mapped[Optional[str]] = mapped_column(String(30), index=True) # --- 🎮 GAMIFICATION ÉS DISCOVERY --- is_official: Mapped[bool] = mapped_column(Boolean, default=True, server_default=text("true")) suggested_by_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id")) discovery_points: Mapped[int] = mapped_column(Integer, default=10, server_default=text("10")) search_keywords: Mapped[Any] = mapped_column(JSONB, server_default=text("'[]'::jsonb")) usage_count: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0")) icon: Mapped[Optional[str]] = mapped_column(String(50)) description: Mapped[Optional[str]] = mapped_column(Text) 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()) services: Mapped[List["ServiceExpertise"]] = relationship("ServiceExpertise", back_populates="tag") suggested_by: Mapped[Optional["Person"]] = relationship("Person") class ServiceExpertise(Base): """ KAPCSOLÓTÁBLA: Ez köti össze a szervizt a szakmáival. """ __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")) 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()")) 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")) # Audit fix: contact_email hossza rögzítve a DB szinkronhoz contact_email: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) 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))