diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 976b873..86cf85f 100755 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -1,21 +1,36 @@ from app.db.base import Base -from .identity import User, Person, Wallet, UserRole +from .identity import User, Person, Wallet, UserRole, VerificationToken from .organization import Organization, OrgType from .vehicle import ( - Vehicle, - VehicleBrand, - EngineSpec, - ServiceProvider, - ServiceRecord, - OrganizationMember + VehicleCatalog, + Asset, + AssetEvent, + AssetRating, + ServiceProvider, + Vehicle, # Alias az Asset-re a kompatibilitás miatt + ServiceRecord # Alias az AssetEvent-re a kompatibilitás miatt ) -# Aliasok a kód többi részének +# Aliasok a kód többi részének szinkronizálásához UserVehicle = Vehicle +VehicleOwnership = Asset __all__ = [ - "Base", "User", "Person", "Wallet", "UserRole", - "Vehicle", "UserVehicle", "VehicleBrand", "EngineSpec", - "ServiceProvider", "ServiceRecord", "Organization", - "OrgType", "OrganizationMember" + "Base", + "User", + "Person", + "Wallet", + "UserRole", + "VerificationToken", + "Organization", + "OrgType", + "VehicleCatalog", + "Asset", + "AssetEvent", + "AssetRating", + "ServiceProvider", + "Vehicle", + "UserVehicle", + "ServiceRecord", + "VehicleOwnership" ] \ No newline at end of file diff --git a/backend/app/models/__pycache__/__init__.cpython-312.pyc b/backend/app/models/__pycache__/__init__.cpython-312.pyc index 8118be3..c06132a 100644 Binary files a/backend/app/models/__pycache__/__init__.cpython-312.pyc and b/backend/app/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/backend/app/models/__pycache__/vehicle.cpython-312.pyc b/backend/app/models/__pycache__/vehicle.cpython-312.pyc index 1d289e1..d88f26d 100644 Binary files a/backend/app/models/__pycache__/vehicle.cpython-312.pyc and b/backend/app/models/__pycache__/vehicle.cpython-312.pyc differ diff --git a/backend/app/models/vehicle.py b/backend/app/models/vehicle.py index cbe0c2c..aecdbfe 100755 --- a/backend/app/models/vehicle.py +++ b/backend/app/models/vehicle.py @@ -5,15 +5,102 @@ from sqlalchemy.sql import func import uuid from app.db.base import Base -class VehicleBrand(Base): - __tablename__ = "vehicle_brands" +# 1. GLOBÁLIS KATALÓGUS (A rendszer agya) +class VehicleCatalog(Base): + __tablename__ = "vehicle_catalog" __table_args__ = {"schema": "data"} + id = Column(Integer, primary_key=True, index=True) - name = Column(String(100), nullable=False, unique=True) - slug = Column(String(100), unique=True) - country_of_origin = Column(String(50)) - is_active = Column(Boolean, default=True) + brand = Column(String(100), nullable=False) + model = Column(String(100), nullable=False) + generation = Column(String(100)) + year_from = Column(Integer) + year_to = Column(Integer) + category = Column(String(50)) # car, bike, boat, plane, truck + engine_type = Column(String(50)) # petrol, diesel, electric, hybrid + engine_power_kw = Column(Integer) + + # DNS ADATOK: Minden technikai részlet (kerékméret, olajmennyiség, stb.) + factory_specs = Column(JSON, default={}) + # SZERVIZTERV: Gyári előírások + maintenance_plan = Column(JSON, default={}) + + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) +# 2. EGYEDI ESZKÖZ (Asset) - A felhasználó tulajdona +class Asset(Base): + __tablename__ = "assets" + __table_args__ = {"schema": "data"} + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + uai = Column(String(100), unique=True, nullable=False) # VIN, HIN vagy Serial Number + organization_id = Column(Integer, ForeignKey("data.organizations.id")) + catalog_id = Column(Integer, ForeignKey("data.vehicle_catalog.id"), nullable=True) + + asset_type = Column(String(20), nullable=False) # car, boat, plane + name = Column(String(255)) # "Kincses E39" + + # Dinamikus állapot + current_plate_number = Column(String(20)) + current_country_code = Column(String(2)) + odometer_value = Column(Numeric(12, 2), default=0) + operating_hours = Column(Numeric(12, 2), default=0) + + # Egyedi DNS (Gyári config + utólagos módosítások) + factory_config = Column(JSON, default={}) + aftermarket_mods = Column(JSON, default={}) + + status = Column(String(50), default="active") + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + + # Kapcsolatok + organization = relationship("Organization", back_populates="assets") + catalog_entry = relationship("VehicleCatalog") + events = relationship("AssetEvent", back_populates="asset", cascade="all, delete-orphan") + ratings = relationship("AssetRating", back_populates="asset") + +# 3. DIGITÁLIS SZERVIZKÖNYV / ESEMÉNYTÁR +class AssetEvent(Base): + __tablename__ = "asset_events" + __table_args__ = {"schema": "data"} + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + asset_id = Column(UUID(as_uuid=True), ForeignKey("data.assets.id")) + event_type = Column(String(50)) # SERVICE, REPAIR, INSPECTION, ACCIDENT, PLATE_CHANGE + + odometer_at_event = Column(Numeric(12, 2)) + hours_at_event = Column(Numeric(12, 2)) + + description = Column(String) + cost = Column(Numeric(12, 2)) + currency = Column(String(3), default="EUR") + + is_verified = Column(Boolean, default=False) + attachments = Column(JSON, default=[]) # Számlák, fotók linkjei + + created_at = Column(DateTime(timezone=True), server_default=func.now()) + asset = relationship("Asset", back_populates="events") + +# 4. EMOCIONÁLIS ÉRTÉKELÉS +class AssetRating(Base): + __tablename__ = "asset_ratings" + __table_args__ = {"schema": "data"} + + id = Column(Integer, primary_key=True) + asset_id = Column(UUID(as_uuid=True), ForeignKey("data.assets.id")) + user_id = Column(Integer, ForeignKey("data.users.id")) + + comfort_score = Column(Integer) # 1-10 + experience_score = Column(Integer) # 1-10 + practicality_score = Column(Integer) # 1-10 + comment = Column(String) + + created_at = Column(DateTime(timezone=True), server_default=func.now()) + asset = relationship("Asset", back_populates="ratings") + +# 5. SZOLGÁLTATÓK (Szerelők, Partnerek) class ServiceProvider(Base): __tablename__ = "service_providers" __table_args__ = {"schema": "data"} @@ -21,67 +108,9 @@ class ServiceProvider(Base): name = Column(String(255), nullable=False) official_brand_partner = Column(Boolean, default=False) technical_rating_pct = Column(Integer, default=80) - social_rating_pct = Column(Integer, default=80) location_city = Column(String(100)) - service_type = Column(String(50)) - search_tags = Column(String) - latitude = Column(Numeric(10, 8)) - longitude = Column(Numeric(11, 8)) is_active = Column(Boolean, default=True) - records = relationship("ServiceRecord", back_populates="provider") - -class EngineSpec(Base): - __tablename__ = "engine_specs" - __table_args__ = {"schema": "data"} - id = Column(Integer, primary_key=True, index=True) - engine_code = Column(String(50), unique=True) - fuel_type = Column(String(20)) - power_kw = Column(Integer) - default_service_interval_km = Column(Integer, default=15000) - vehicles = relationship("Vehicle", back_populates="engine_spec") - -class Vehicle(Base): - __tablename__ = "vehicles" - __table_args__ = {"schema": "data"} - id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) - current_company_id = Column(Integer, ForeignKey("data.organizations.id")) - brand_id = Column(Integer, ForeignKey("data.vehicle_brands.id")) - model_name = Column(String(100)) - engine_spec_id = Column(Integer, ForeignKey("data.engine_specs.id")) - identification_number = Column(String(50), unique=True) - license_plate = Column(String(20)) - tracking_mode = Column(String(10), default="km") - current_rating_pct = Column(Integer, default=100) - total_real_usage = Column(Numeric(15, 2), default=0) - created_at = Column(DateTime(timezone=True), server_default=func.now()) - - engine_spec = relationship("EngineSpec", back_populates="vehicles") - service_records = relationship("ServiceRecord", back_populates="vehicle", cascade="all, delete-orphan") - current_org = relationship("Organization", back_populates="vehicles") - -class ServiceRecord(Base): - __tablename__ = "service_records" - __table_args__ = {"schema": "data"} - id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) - vehicle_id = Column(UUID(as_uuid=True), ForeignKey("data.vehicles.id")) - provider_id = Column(Integer, ForeignKey("data.service_providers.id")) - service_date = Column(Date, nullable=False) - usage_value = Column(Numeric(15, 2)) - repair_quality_pct = Column(Integer, default=100) - - vehicle = relationship("Vehicle", back_populates="service_records") - provider = relationship("ServiceProvider", back_populates="records") - -class OrganizationMember(Base): - __tablename__ = "organization_members" - __table_args__ = {"schema": "data"} - id = Column(Integer, primary_key=True, index=True) - organization_id = Column(Integer, ForeignKey("data.organizations.id"), nullable=False) - user_id = Column(Integer, ForeignKey("data.users.id"), nullable=False) - role = Column(String, default="driver") - - organization = relationship("Organization", back_populates="members") # --- KOMPATIBILITÁSI RÉTEG --- -UserVehicle = Vehicle -VehicleOwnership = Vehicle \ No newline at end of file +Vehicle = Asset +ServiceRecord = AssetEvent \ No newline at end of file diff --git a/backend/app/services/__pycache__/harvester_robot.cpython-312.pyc b/backend/app/services/__pycache__/harvester_robot.cpython-312.pyc new file mode 100644 index 0000000..df0ec4b Binary files /dev/null and b/backend/app/services/__pycache__/harvester_robot.cpython-312.pyc differ diff --git a/backend/app/services/harvester_robot.py b/backend/app/services/harvester_robot.py new file mode 100644 index 0000000..3caf7d9 --- /dev/null +++ b/backend/app/services/harvester_robot.py @@ -0,0 +1,84 @@ +import httpx +import asyncio +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +from app.models.vehicle import VehicleCatalog # Az imént létrehozott modell + +class VehicleHarvester: + def __init__(self): + # Az ingyenes CarQueryAPI URL-je (0.3-as verzió) + self.base_url = "https://www.carqueryapi.com/api/0.3/" + self.headers = {"User-Agent": "ServiceFinder-Harvester-Bot/1.0"} + + async def get_data(self, params: dict): + """Segédfüggvény az API hívásokhoz.""" + async with httpx.AsyncClient() as client: + try: + response = await client.get(self.base_url, params=params, headers=self.headers, timeout=10.0) + if response.status_code == 200: + # Az API néha JSONP-t ad vissza, ezt itt lekezeljük (levágjuk a felesleget) + text = response.text + if text.startswith("?("): text = text[2:-2] + return response.json() + return None + except Exception as e: + print(f"Robot hiba: {str(e)}") + return None + + async def harvest_all(self, db: AsyncSession): + """A fő folyamat: Minden márka -> Minden modell szinkronizálása.""" + print("🤖 Robot: Indul a nagy adatgyűjtés...") + + # 1. Márkák lekérése + makes_data = await self.get_data({"cmd": "getMakes", "sold_in_us": 0}) + if not makes_data: return + + makes = makes_data.get("Makes", []) + + for make in makes: + make_id = make['make_id'] + make_display = make['make_display'] + print(f"--- 🚗 Feldolgozás: {make_display} ---") + + # 2. Modellek lekérése ehhez a márkához + models_data = await self.get_data({"cmd": "getModels", "make": make_id}) + if not models_data: continue + + models = models_data.get("Models", []) + + for model in models: + model_name = model['model_name'] + + # 3. Megnézzük, benne van-e már a katalógusban + stmt = select(VehicleCatalog).where( + VehicleCatalog.brand == make_display, + VehicleCatalog.model == model_name + ) + res = await db.execute(stmt) + if res.scalar_one_or_none(): + continue # Ha már megvan, ugrunk a következőre + + # 4. Új bejegyzés létrehozása alapadatokkal + # Itt a Robot később "mélyebbre" áshat a specifikációkért + new_v = VehicleCatalog( + brand=make_display, + model=model_name, + category="car", # Alapértelmezett, később finomítható + factory_specs={ + "api_make_id": make_id, + "harvester_source": "carquery" + } + ) + db.add(new_v) + print(f"✅ Robot rögzítve: {make_display} {model_name}") + + # Márkánként mentünk, hogy ne vesszen el a munka, ha megszakad + await db.commit() + await asyncio.sleep(1) # Ne terheljük túl az ingyenes API-t (Rate Limit védelem) + + print("🏁 Robot: A munka oroszlánrésze kész!") + +# Ez a rész csak a teszteléshez kell, ha manuálisan indítod a scriptet +if __name__ == "__main__": + # Itt lehetne egy külön indító logika + pass \ No newline at end of file diff --git a/backend/test_robot.py b/backend/test_robot.py new file mode 100644 index 0000000..9ac6768 --- /dev/null +++ b/backend/test_robot.py @@ -0,0 +1,20 @@ +import asyncio +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker +from app.services.harvester_robot import VehicleHarvester +from app.core.config import settings + +# Adatbázis kapcsolat felépítése a pontos névvel +engine = create_async_engine(str(settings.DATABASE_URL)) +AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) + +async def run_test(): + async with AsyncSessionLocal() as db: + harvester = VehicleHarvester() + print("🚀 Robot indítása...") + # Megpróbáljuk betölteni a katalógust + await harvester.harvest_all(db) + print("✅ Teszt lefutott.") + +if __name__ == "__main__": + asyncio.run(run_test()) \ No newline at end of file diff --git a/docs/V01_gemini/15_Changelog.md b/docs/V01_gemini/15_Changelog.md index 577891b..2206888 100644 --- a/docs/V01_gemini/15_Changelog.md +++ b/docs/V01_gemini/15_Changelog.md @@ -93,3 +93,28 @@ A fejlesztések rendben tartásához javaslom a **`17_DEVELOPER_NOTES_AND_PITFAL **Holnap reggel frissíted a GEM beállításokat is?** Ha igen, a következő lépésben elkészíthetem neked a Step 2 (KYC) végleges Pydantic sémáját és a `complete-kyc` végpont vázlatát! +## [0.2.0] - 2026-02-07 + +### ✨ Hozzáadva (Added) +- **Step 2 (KYC) Folyamat:** Teljes körű identitás-kezelés (telefonszám, születési adatok, okmányok, ICE kontakt). +- **Automata Privát Flotta:** Minden felhasználóhoz automatikusan létrejön egy `individual` típusú szervezet (Privát Széf). +- **Automata Wallet:** Minden validált felhasználó kap egy üres pénztárcát (Coin és XP egyenleggel). +- **Trust Tiers:** Bevezetésre került a fokozatos bizalmi szint (Tier 1: Email, Tier 2: KYC/Active). + +### 🛠️ Javítva (Fixed) +- **SQLAlchemy Async Fix:** `joinedload` alkalmazása a User-Person kapcsolathoz (MissingGreenlet hiba elhárítva). +- **JSON Serialization:** Pydantic `model_dump(mode='json')` használata a JSONB mezőkhöz (dátum-konverziós hiba javítva). +- **Postgres Schema:** `data.organizations` tábla bővítve hiányzó oszlopokkal (`is_verified`, `updated_at`, stb.). +- **Auth Endpoint:** `/complete-kyc` végpont hozzáadva és JWT védelemmel ellátva. + +### ⚙️ Adatbázis Változások (Database) +- Új Enum típus: `data.orgtype` ('individual', 'company'). +- `data.persons` bővítve: `phone`, `birth_place`, `birth_date`, `mothers_name`, `identity_docs`, `ice_contact`. +- `data.organizations` bővítve: `is_verified`, `is_transferable`, `verification_expires_at`, `updated_at`. + +## [0.2.0] - 2026-02-07 +### ✅ Step 2 KYC & Activation Complete +- **Funkció:** Teljes körű személyazonosság-kezelés és fiókaktiválás. +- **Automatizálás:** Regisztrációkor automatikusan létrejön a "Privát Flotta" (Organization) és a digitális pénztárca (Wallet). +- **Adatvédelem:** Elkészült a "Digitális Széf" logika az okmányok és vészhelyzeti adatok biztonságos tárolására. +- **Technikai fix:** SQLAlchemy `joinedload` integráció az aszinkron adatkezeléshez és JSON-safe dátumkezelés. \ No newline at end of file diff --git a/docs/V01_gemini/18_ASSET_AND_FLEET_SPECIFICATION.md b/docs/V01_gemini/18_ASSET_AND_FLEET_SPECIFICATION.md new file mode 100644 index 0000000..ef9d519 --- /dev/null +++ b/docs/V01_gemini/18_ASSET_AND_FLEET_SPECIFICATION.md @@ -0,0 +1,69 @@ +# 🏎️ Asset és Flotta Specifikáció: A Járművek DNS-e + +Ez a dokumentum írja le a rendszer magját képező "széf" logikát, ahol minden közlekedési eszköz (Asset) egyedi életutat és digitális lenyomatot kap. + +## 1. Az Alapelv: "Mindenki Flottatulajdonos" +A rendszerben nincs különbség egy magánszemély és egy cég között a technikai rétegben. +- **Privát Flotta:** A regisztráció (Step 2) során automatikusan létrejövő szervezet (Organization). +- **Széf (Safe Deposit):** A flotta része, ahol az eszközök (járművek) és azok bizalmas okmányai laknak. + +## 2. Eszköz Típusok és Speciális Azonosítók +Minden eszköz rendelkezik egy **Univerzális Állandó Azonosítóval (UAI)**, ami az életútja során soha nem változik. + +| Típus | Elsődleges Azonosító (UAI) | Speciális Adatpontok | +| :--- | :--- | :--- | +| **Közúti** | VIN (Alvázszám) | Rendszám, Motorkód, Sebességváltó kód | +| **Vízi** | HIN (Hull ID / Testszám) | MMSI kód, IMO szám, Név | +| **Légi** | Serial Number (Gyári szám) | Lajstromjel (Registration), Típusjelzés | +| **Egyéb** | Egyedi sorozatszám | Gyártó, Teljesítmény | + +### Kiegészítő mérőszámok: +- **Futásteljesítmény (Odometer):** Közúti járműveknél (km/mérföld). +- **Üzemóra (Operating Hours):** Hajók, repülők, munkagépek és versenytechnika esetén kritikus. + +## 3. A Jármű DNS (Deep Data Structure) +Az adatbázisnak ismernie kell a járművet "gyári" állapotában és annak minden módosítását. + +### A) Gyári Konfiguráció (Factory Specs) +- **Trim Level:** Felszereltségi csomag (pl. S-Line, AMG Pack, Comfortline). +- **Technikai paraméterek:** Motorválaszték, kW/LE, nyomaték, gyári felni- és gumiméret (ET számmal), folyadékmennyiségek. +- **Szervizintervallumok:** Gyártó által előírt periodikus karbantartások (idő vagy távolság alapú). + +### B) Aktuális Állapot és Módosítások (Modifications) +- **Gyári extrák:** Mi az, ami benne maradt? (pl. bőrbelső, napfénytető). +- **Utólagos (Aftermarket):** Mi került bele? (pl. vonóhorog, gázszett). +- **Hiányzó:** Mi került ki belőle? (pl. kiszerelt gyári hifi). + +## 4. Digitális Szervizkönyv (Digital Service Book) +Nem csak egy lista, hanem egy **Eseményalapú Idővonal (Timeline)**. Minden bejegyzés megváltoztathatatlan (immutable-szerű) logként rögzül. +- **Típusok:** Karbantartás, Javítás, Műszaki Vizsga, Baleset, Tulajdonosváltás. +- **Csatolmányok:** Fotók az alkatrészekről, számlák PDF-ben, munkalapok. + +## 5. Jármű Minősítés és Értékelés +A jármű két különálló, de egymást kiegészítő minősítést kap: + +### A) Technikai Minősítés (AI Health Score) +- **Algoritmus alapú:** A szerviztörténet, az üzemóra/futás aránya és a gyári specifikációk betartása alapján kalkulált pontszám. + +### B) Emocionális és Közösségi Értékelés (Driver Rating) +A járművet használó sofőrök értékelhetik az eszközt szubjektív szempontok alapján: +- **Komfort:** Mennyire kényelmes hosszú távon? +- **Vezetési élmény:** "Lelke van", vagy csak egy gép? +- **Praktikum:** Mennyire használható a mindennapokban? +- **Megbízhatóság érzet:** Mennyire érzi magát benne biztonságban a sofőr? + +Ez a kettős mérőszám adja meg a jármű valós "piaci és használati értékét". + +## 6. Az Adat-Gondnok (Harvester Robot) +A rendszer integritásáért és az adatok pontosságáért egy automata Robot felel. + +### Funkciók: +1. **Initial Load:** A legnépszerűbb 1000 európai járműtípus alapértelmezett feltöltése. +2. **On-Demand Fetch:** Ha egy felhasználó ismeretlen típust keres, a Robot prioritással kutatja fel és rögzíti azt. +3. **Deep Data Scrape:** A Robot nemcsak a típust, hanem a gyári specifikációkat (olajmennyiség, guminyomás, szervizintervallum) is gyűjti. +4. **Maintenance:** Negyedévente frissíti a meglévő adatokat (új modellévek, módosított gyári előírások). + +### Adatforrások hierarchiája: +1. Hivatalos gyártói API-k (ahol elérhető). +2. Nyilvános műszaki adatbázisok (Auto-Data, UltimateSpecs). +3. VIN/HIN dekóder algoritmusok. \ No newline at end of file