STABLE: Final schema sync, optimized gitignore

This commit is contained in:
Kincses
2026-02-26 08:19:25 +01:00
parent 893f39fa15
commit 505543330a
203 changed files with 11590 additions and 9542 deletions

View File

@@ -1,163 +1,104 @@
# /opt/docker/dev/service_finder/backend/app/models/service.py
import uuid
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, JSON, text, Text, Float, Index, Numeric
from sqlalchemy.orm import relationship, backref
from datetime import datetime
from typing import Any, List, Optional
from sqlalchemy import Integer, String, Boolean, DateTime, ForeignKey, text, Text, Float, Index, Numeric
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB
from geoalchemy2 import Geometry # PostGIS támogatás
from geoalchemy2 import Geometry
from sqlalchemy.sql import func
from app.db.base_class import Base
# 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ó kiterjesztett adatai (v1.3.1).
Egy Organization-höz (org_type='service') kapcsolódik.
Támogatja a hierarchiát (Franchise/Telephely) és az automatizált dúsítást.
"""
""" Szerviz szolgáltató adatai (v1.3.1). """
__tablename__ = "service_profiles"
__table_args__ = (
# Egyedi ujjlenyomat index a robot számára a duplikációk elkerülésére
Index('idx_service_fingerprint', 'fingerprint', unique=True),
{"schema": "data"}
)
id = Column(Integer, primary_key=True, index=True)
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.organizations.id"), unique=True)
parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.service_profiles.id"))
# --- KAPCSOLAT A CÉGES IKERHEZ (Twin) ---
organization_id = Column(Integer, ForeignKey("data.organizations.id"), unique=True)
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)
# --- HIERARCHIA (Fa struktúra) ---
# Ez tárolja a szülő egység ID-ját (pl. hálózat központja)
parent_id = Column(Integer, ForeignKey("data.service_profiles.id"), nullable=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)
# --- ROBOT IDENTITÁS ---
# Normalize(Név + Város + Utca) hash, hogy ne legyen duplikáció
fingerprint = Column(String(255), nullable=False, index=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"))
# PostGIS GPS pont (SRID 4326 = WGS84 koordináták)
location = Column(Geometry(geometry_type='POINT', srid=4326), index=True)
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"))
# Állapotkezelés: ghost (robot találta), active, flagged, inactive
status = Column(String(20), server_default=text("'ghost'"), index=True)
last_audit_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
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)
# --- GOOGLE ÉS KÜLSŐ ADATOK ---
google_place_id = Column(String(100), unique=True)
rating = Column(Float)
user_ratings_total = Column(Integer)
# Kapcsolatok
organization: Mapped["Organization"] = relationship("Organization", back_populates="service_profile")
expertises: Mapped[List["ServiceExpertise"]] = relationship("ServiceExpertise", back_populates="service")
# --- MÉLYFÚRÁS (Deep Enrichment) ADATOK ---
# AI elemzés: {"tone": "barátságos", "pricing": "közép", "reliability": "magas"}
vibe_analysis = Column(JSONB, server_default=text("'{}'::jsonb"))
# Közösségi háló: {"facebook": "url", "tiktok": "url", "insta": "url"}
social_links = Column(JSONB, server_default=text("'{}'::jsonb"))
# Speciális szűrő címkék: {"brands": ["Yamaha", "Suzuki"], "specialty": ["engine", "tuning"]}
specialization_tags = Column(JSONB, server_default=text("'{}'::jsonb"))
# Trust Engine (Bot Discovery=30, User Entry=50, Admin/Partner=100)
trust_score = Column(Integer, default=30)
is_verified = Column(Boolean, default=False)
verification_log = Column(JSONB, server_default=text("'{}'::jsonb"))
# --- ELÉRHETŐSÉG ---
opening_hours = Column(JSONB, server_default=text("'{}'::jsonb"))
contact_phone = Column(String)
contact_email = Column(String)
website = Column(String)
bio = Column(Text)
# --- KAPCSOLATOK ---
organization = relationship("Organization", back_populates="service_profile")
expertises = relationship("ServiceExpertise", back_populates="service")
# --- ÖNMAGÁRA HIVATKOZÓ KAPCSOLAT (Hierarchia) ---
sub_services = relationship(
"ServiceProfile",
backref=backref("parent_service", remote_side=[id]),
cascade="all, delete-orphan"
)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
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 szempontok taxonómiája."""
__tablename__ = "expertise_tags"
__table_args__ = {"schema": "data"}
id = Column(Integer, primary_key=True)
key = Column(String(50), unique=True, index=True) # pl. 'bmw_gs_specialist'
name_hu = Column(String(100))
category = Column(String(30)) # 'repair', 'fuel', 'food', 'emergency'
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))
category: Mapped[Optional[str]] = mapped_column(String(30))
class ServiceExpertise(Base):
"""Kapcsolótábla a szerviz és a szakterület között."""
__tablename__ = "service_expertises"
__table_args__ = {"schema": "data"}
service_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.service_profiles.id"), primary_key=True)
expertise_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.expertise_tags.id"), primary_key=True)
validation_level: Mapped[int] = mapped_column(Integer, default=0)
service_id = Column(Integer, ForeignKey("data.service_profiles.id"), primary_key=True)
expertise_id = Column(Integer, ForeignKey("data.expertise_tags.id"), primary_key=True)
# Validációs szint (0-100% - Mennyire hiteles ez a szakértelem)
validation_level = Column(Integer, default=0)
service = relationship("ServiceProfile", back_populates="expertises")
expertise = relationship("ExpertiseTag")
service: Mapped["ServiceProfile"] = relationship("ServiceProfile", back_populates="expertises")
expertise: Mapped["ExpertiseTag"] = relationship("ExpertiseTag")
class ServiceStaging(Base):
"""
Átmeneti tábla a Hunter (n8n/scraping) adatoknak.
"""
""" Hunter (robot) adatok tárolója. """
__tablename__ = "service_staging"
__table_args__ = (
Index('idx_staging_fingerprint', 'fingerprint', unique=True),
{"schema": "data"}
)
id = Column(Integer, primary_key=True, index=True)
# --- Alapadatok ---
name = Column(String, nullable=False, index=True)
# --- Strukturált cím adatok ---
postal_code = Column(String(10), index=True)
city = Column(String(100), index=True)
street_name = Column(String(150))
street_type = Column(String(50))
house_number = Column(String(20))
stairwell = Column(String(20))
floor = Column(String(20))
door = Column(String(20))
hrsz = Column(String(50))
full_address = Column(String)
contact_phone = Column(String, nullable=True)
email = Column(String, nullable=True)
website = Column(String, nullable=True)
# --- Forrás és Azonosítás ---
source = Column(String(50), nullable=True, index=True)
external_id = Column(String(100), nullable=True, index=True)
# Robot ujjlenyomat a Staging szintű deduplikációhoz
fingerprint = Column(String(255), nullable=False)
# --- Adatmentés ---
raw_data = Column(JSONB, server_default=text("'{}'::jsonb"))
# --- Státusz és Bizalom ---
status = Column(String(20), server_default=text("'pending'"), index=True)
trust_score = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), server_default=func.now())
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"))
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."""
""" Robot vezérlési paraméterek adminból. """
__tablename__ = "discovery_parameters"
__table_args__ = {"schema": "data"}
id = Column(Integer, primary_key=True)
city = Column(String(100), nullable=False)
keyword = Column(String(100), nullable=False)
country_code = Column(String(2), default="HU")
is_active = Column(Boolean, default=True)
last_run_at = Column(DateTime(timezone=True))
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))