From d3ce60d69be1247d0c87c4c361c5bdde0e11f3c2 Mon Sep 17 00:00:00 2001 From: Kincses Date: Sat, 7 Feb 2026 02:20:04 +0000 Subject: [PATCH] feat: multi-robot architecture, car-robot rename, and credit-based OCR logic spec --- .../api/v1/__pycache__/api.cpython-312.pyc | Bin 400 -> 540 bytes backend/app/api/v1/api.py | 9 +- .../__pycache__/catalog.cpython-312.pyc | Bin 0 -> 2057 bytes backend/app/api/v1/endpoints/catalog.py | 37 +++++ backend/app/core/config.py | 1 + .../__pycache__/vehicle.cpython-312.pyc | Bin 5835 -> 5920 bytes backend/app/models/vehicle.py | 9 +- backend/app/services/auth_service_old.py | 130 ---------------- backend/app/services/auth_service_old2.py | 145 ------------------ backend/app/services/auth_service_old3.py | 129 ---------------- backend/app/services/harvester_base.py | 34 ++++ backend/app/services/harvester_bikes.py | 12 ++ .../{harvester_robot.py => harvester_cars.py} | 0 backend/app/services/harvester_trucks.py | 8 + backend/app/services/robot_manager.py | 22 +++ docs/V01_gemini/15_Changelog.md | 14 +- .../18_ASSET_AND_FLEET_SPECIFICATION.md | 85 +++++++++- 17 files changed, 223 insertions(+), 412 deletions(-) create mode 100644 backend/app/api/v1/endpoints/__pycache__/catalog.cpython-312.pyc create mode 100644 backend/app/api/v1/endpoints/catalog.py delete mode 100644 backend/app/services/auth_service_old.py delete mode 100644 backend/app/services/auth_service_old2.py delete mode 100644 backend/app/services/auth_service_old3.py create mode 100644 backend/app/services/harvester_base.py create mode 100644 backend/app/services/harvester_bikes.py rename backend/app/services/{harvester_robot.py => harvester_cars.py} (100%) create mode 100644 backend/app/services/harvester_trucks.py create mode 100644 backend/app/services/robot_manager.py diff --git a/backend/app/api/v1/__pycache__/api.cpython-312.pyc b/backend/app/api/v1/__pycache__/api.cpython-312.pyc index b19346dd1998d61501ee835e261ed812dfa721f6..2742d464d2ada657d81cff1a8e46f2fa76113d0b 100644 GIT binary patch delta 326 zcmbQhJcotvG%qg~0}y!6Zp&T*>a`cPgKe^_cp zW^zudf-^+gPm^Qf4NayZ)`|ZDRRn>iF#>UM0Fd~=%*e=imqGSEgWf}4vF_*wt`BSs wjNFq88O7DFF!+Nx*BK-)GDyx)pRYGlZ-dkY=a36dp%)k=uQG%c@dJ$j0E`|+0RR91 delta 190 zcmbQkGJ%=zG%qg~0}ynJwPvnmn8+vLmI35VXGmd4Va#F3WsG8E1hJWNm~xq;m>C(E z7*ZKmgERm^6iX^g7B_^AOr|nt@k}gGk*j3WWPb^g&}6*D=@{S{lwVqsS_I^yB>Vy1~FT`5vP< M$5jUZB0iuB05nx5WdHyG diff --git a/backend/app/api/v1/api.py b/backend/app/api/v1/api.py index cc1d3ba..06a7569 100755 --- a/backend/app/api/v1/api.py +++ b/backend/app/api/v1/api.py @@ -1,5 +1,10 @@ from fastapi import APIRouter -from app.api.v1.endpoints import auth +from app.api.v1.endpoints import auth, catalog # <--- Hozzáadtuk a catalog-ot api_router = APIRouter() -api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"]) \ No newline at end of file + +# Autentikáció és KYC végpontok +api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"]) + +# Jármű Katalógus és Robot kereső végpontok +api_router.include_router(catalog.router, prefix="/catalog", tags=["Vehicle Catalog"]) \ No newline at end of file diff --git a/backend/app/api/v1/endpoints/__pycache__/catalog.cpython-312.pyc b/backend/app/api/v1/endpoints/__pycache__/catalog.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..476d325715b05f89895ba4e62e0a31e5cb5d562a GIT binary patch literal 2057 zcma)7TW=Fb6rSDn?)tK}6FYIh(8^FQwNPxrMO9IYlr}9wl!%K|wU#PtvpaUy*|p8= zI$)ZpX{jivm1xwefdV2tH7G@@REZyed8pJEELVy<0s-nvd2?_dTAq5w7lN9!>PRzl zE;HwwIdjgLpZ$I>f_CfMv&N_kq2CzOZSD@y{0WFTq$8a(k;SDri%;>EkP&YNz1=k#LHFZcJpet3#f+r)d@4Re2$hw1?_;(xE>O?# zk%PwyB?l8KkKiKC>oygSmM|&DIjRiX<-B$r+qO~2$9XE**uVMJ>f98%43TkF=psC4Ls`B4 z|JhjVoV#mtTA1R<<|$##(?7doF0dMJiko;Dq37)Ix!yHa3AylUsm^!zb>}lk7d`~< zf5XoSXGIZByQW;Z{?$9X5tkeIcipr&C61sAlE!C|#(!`Kp=oJK%B@?y1Eg!b26){C znl*fU8C?}Xvz9lcTy#zQEI$9ybn%Kf%YSj$1*e;qj-yx57iEp-THHx=Q#i@RrP6NL zbf^==F*SCqFjjD4mFp%XLo9<$r*eyR$B9j_{HXkxE!ksgK0&#ykNaRN#C9w$Qr^(1 zCsQ)bbY8VE=>ZL7oTEA=NJ+bhH9H<8EJz89b}F762S~zvLd7wn=5;C>rZIu3Xccs9 zwhhSm6xJZUsc0IO;ZWJuR8u836;#tC5w5hoR!2-LW9fSn_9sN0j{sGI*ZB=F4 zLhr)R!b=NB=TFs?o&Tk4ih{@66huXu8Ch14yQdxuUpRgK^p(U?bf_8~`r*B5^z~YB z|2fygaR1!C*?kxFYq?9gh0x`EEj+m7ADnr+u7oa(o*%6#8`~$SDVt~BS`MMU_06DL zk(L8Mol_e=6z-ebGP|X|@r7##E*-e|>iqD$`d#*VcK-dl>y!0$>#un)dFzpZrO1wI zWJkR}GMAoBH+_=F+fcy5GPBh{Zc%E;D3EyiL<0UVdn-8RlQ(va?B#Cn;YJd!+k3YG zUD+_)GqTflXNL=zJ3B=f<6$aiRCvsyL0F+A@UJ1l${vl_Crs7U#<5jS;8RXQWkud7 z5Q!V`_}zB@Z!bEuRdCqqnuQiy$u28 bhk@RP2y;V1p8h9H(-aUtu*?HR_5kAFn`zmJ literal 0 HcmV?d00001 diff --git a/backend/app/api/v1/endpoints/catalog.py b/backend/app/api/v1/endpoints/catalog.py new file mode 100644 index 0000000..bbe3368 --- /dev/null +++ b/backend/app/api/v1/endpoints/catalog.py @@ -0,0 +1,37 @@ +from fastapi import APIRouter, Depends, Query +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, or_ +from app.db.session import get_db +from app.models.vehicle import VehicleCatalog +from typing import List + +router = APIRouter() + +@router.get("/search") +async def search_catalog( + q: str = Query(..., min_length=2, description="Márka vagy típus keresése"), + category: str = None, + db: AsyncSession = Depends(get_db) +): + """Keresés a Robot által feltöltött katalógusban.""" + stmt = select(VehicleCatalog).where( + or_( + VehicleCatalog.brand.ilike(f"%{q}%"), + VehicleCatalog.model.ilike(f"%{q}%") + ) + ) + if category: + stmt = stmt.where(VehicleCatalog.category == category) + + result = await db.execute(stmt.limit(20)) + items = result.scalars().all() + + return [ + { + "id": i.id, + "full_name": f"{i.brand} {i.model}", + "category": i.category, + "status": i.verification_status, + "specs": i.factory_specs + } for i in items + ] \ No newline at end of file diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 2de8503..d08760b 100755 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -63,4 +63,5 @@ class Settings(BaseSettings): extra="ignore" ) + settings = Settings() \ No newline at end of file diff --git a/backend/app/models/__pycache__/vehicle.cpython-312.pyc b/backend/app/models/__pycache__/vehicle.cpython-312.pyc index d88f26d16eed5b4d4e9070f6e8c4710627bd925b..f5cb6b29da7c7bca886b1cf840b5c0922df08436 100644 GIT binary patch delta 1042 zcma))OK1~O6ozy2XdY%VdAFIQ)rzeqv7w+4(KZd$1~fLU#ex)SI%Fm=(i)gd3<|a` zR8T|;NAQ72MU+AVD%o}AJ{u|BIN(Be*(gXsaiQKbLrV=s8<=lS{`1e7|K2-|zNG}y zU>Jvi{vuCrOTBNIMM75L8To$EXb^3JV})SPU=a4yoGV7Xp7PM1Mf6IBZJKWf&5s(r z0)n~5Y)+#8gO?KU&KmAx07YJoT01ks0Rj3(`?gE1-Nq-FA4_{hY(Rj3|`yABn}Px8i*_lHOG>e;h(-W?+(2lqTZ3u!bID6TOAukeXV zS(M8ONodVFgFZcL`+u^U(Hci+sjXNQDO4*C&r5>J6iRc&lC0#*(C+s%>DdBvfX_lza=ugK<}YWXR(A4Fldc`r%G6Wya6# E50o_5f&c&j delta 1040 zcma))O=uHQ6ouzCNn4XlrjsUVW7-nMsKGX?Xq0M8TWyUtQL7>pB1|)bjnuYGGNNuG z2rgV`|>vy7NiCDd;|KDH12PZ4A+KQD{A~3(47mOc~)2Q zeehy8Jys(z@7mLC|5hsUnzH?>FRoC|k)v0R6bln`F3_^G%-+(V>y|edxu1OM{}AZD zo~csJ)!(?EA8M+!=nYp!8Y>N~lfE}+Qndgt*M0)F1rLcYgtKQ#;Rx+L(tB2~XEP9t1OVf<6vwR4OevWTGT`Oq` z4X%obf-bU{htVG5sKJKqIC6GOBpv=Eoj4>1;Rr`I-nEG=W(NAKmr z4A_t+jT$gO@0HK>FljNN*DWezqf}L6lOmADMb~5z$SNA9$g@5UVfZ}$tZumNKZdzU z?h+8=Zo*2ZY5(|UOpihhoTIP)2W~MNZ1Z>3AU$iX9v|eJVKWRF-l5t_tlBxUab?rS zTzKd-eF~h~_zGC)0FyKnoT^`%MK6N4yDT@FPFLSummH+?% diff --git a/backend/app/models/vehicle.py b/backend/app/models/vehicle.py index aecdbfe..f898bae 100755 --- a/backend/app/models/vehicle.py +++ b/backend/app/models/vehicle.py @@ -16,13 +16,14 @@ class VehicleCatalog(Base): 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 + category = Column(String(50)) + engine_type = Column(String(50)) engine_power_kw = Column(Integer) - # DNS ADATOK: Minden technikai részlet (kerékméret, olajmennyiség, stb.) + # --- EZ A SOR HIÁNYZOTT --- + verification_status = Column(String(20), default="verified") + 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()) diff --git a/backend/app/services/auth_service_old.py b/backend/app/services/auth_service_old.py deleted file mode 100644 index 9409bec..0000000 --- a/backend/app/services/auth_service_old.py +++ /dev/null @@ -1,130 +0,0 @@ -from datetime import datetime, timezone, timedelta -from typing import Optional, Dict, Any -import logging -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, and_, text - -from app.models.identity import User, Person, Wallet, UserRole -from app.models.organization import Organization, OrgType -from app.models.vehicle import OrganizationMember -from app.schemas.auth import UserRegister -from app.core.security import get_password_hash -from app.services.email_manager import email_manager - -logger = logging.getLogger(__name__) - -class AuthService: - @staticmethod - async def get_setting(db: AsyncSession, key: str, default: Any = None) -> Any: - """Kiolvassa a beállítást az adatbázisból (Admin UI kompatibilis).""" - try: - stmt = text("SELECT value FROM data.system_settings WHERE key = :key") - result = await db.execute(stmt, {"key": key}) - val = result.scalar() - return val if val is not None else default - except Exception: - return default - - @staticmethod - async def register_new_user(db: AsyncSession, user_in: UserRegister, ip_address: str): - try: - # 1. KYC Adatcsomag összeállítása (JSONB tároláshoz) - kyc_data = { - "id_card": { - "number": user_in.id_card_number, - "expiry": str(user_in.id_card_expiry) if user_in.id_card_expiry else None - }, - "driver_license": { - "number": user_in.driver_license_number, - "expiry": str(user_in.driver_license_expiry) if user_in.driver_license_expiry else None, - "categories": user_in.driver_license_categories - }, - "special_licenses": { - "boat": user_in.boat_license_number, - "pilot": user_in.pilot_license_number - } - } - - # 2. Person létrehozása - new_person = Person( - first_name=user_in.first_name, - last_name=user_in.last_name, - mothers_name=user_in.mothers_name, - birth_place=user_in.birth_place, - birth_date=user_in.birth_date, - identity_docs=kyc_data - ) - db.add(new_person) - await db.flush() - - # 3. User létrehozása - hashed_pwd = get_password_hash(user_in.password) if user_in.password else None - new_user = User( - email=user_in.email, - hashed_password=hashed_pwd, - social_provider=user_in.social_provider, - social_id=user_in.social_id, - person_id=new_person.id, - role=UserRole.USER, - region_code=user_in.region_code, - is_active=True - ) - db.add(new_user) - await db.flush() - - # 4. Wallet inicializálás - new_wallet = Wallet(user_id=new_user.id, coin_balance=0.00, xp_balance=0) - db.add(new_wallet) - - # 5. Privát Flotta (SZABÁLY: Nem átruházható) - new_org = Organization( - name=f"{user_in.last_name} {user_in.first_name} flottája", - org_type=OrgType.INDIVIDUAL, - owner_id=new_user.id, - is_active=True, - is_transferable=False - ) - db.add(new_org) - await db.flush() - - # 6. Tagság rögzítése - db.add(OrganizationMember(organization_id=new_org.id, user_id=new_user.id, role="owner")) - - # 7. Audit Log - audit_stmt = text(""" - INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address, created_at) - VALUES (:uid, 'USER_REGISTERED_V1.3_FULL_KYC', '/api/v1/auth/register', 'POST', :ip, :now) - """) - await db.execute(audit_stmt, { - "uid": new_user.id, - "ip": ip_address, - "now": datetime.now(timezone.utc) - }) - - # 8. Jutalmazás (Dinamikus) - reward_days = await AuthService.get_setting(db, "auth.reward_days", 14) - - # 9. Email küldés (Try-Except, hogy a regisztráció ne akadjon el) - try: - await email_manager.send_email( - recipient=user_in.email, - template_key="registration_welcome", - variables={"first_name": user_in.first_name, "reward_days": reward_days}, - user_id=new_user.id - ) - except Exception as e: - logger.warning(f"Email delivery failed: {str(e)}") - - await db.commit() - return new_user - - except Exception as e: - await db.rollback() - logger.error(f"Critical error in register_new_user: {str(e)}") - raise e - - @staticmethod - async def check_email_availability(db: AsyncSession, email: str) -> bool: - query = select(User).where(and_(User.email == email, User.is_deleted == False)) - result = await db.execute(query) - return result.scalar_one_or_none() is None \ No newline at end of file diff --git a/backend/app/services/auth_service_old2.py b/backend/app/services/auth_service_old2.py deleted file mode 100644 index a65b70c..0000000 --- a/backend/app/services/auth_service_old2.py +++ /dev/null @@ -1,145 +0,0 @@ -# /opt/docker/dev/service_finder/backend/app/services/auth_service.py -from datetime import datetime, timezone, timedelta -from typing import Optional, Dict, Any -import logging -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, and_, text - -from app.models.identity import User, Person, Wallet, UserRole -from app.models.organization import Organization, OrgType -from app.models.vehicle import OrganizationMember -from app.schemas.auth import UserRegister -from app.core.security import get_password_hash, create_access_token -from app.services.email_manager import email_manager - -logger = logging.getLogger(__name__) - -class AuthService: - @staticmethod - async def get_setting(db: AsyncSession, key: str, default: Any = None) -> Any: - """Admin felületről állítható változók lekérése.""" - try: - stmt = text("SELECT value FROM data.system_settings WHERE key = :key") - result = await db.execute(stmt, {"key": key}) - val = result.scalar() - return val if val is not None else default - except Exception: - return default - - @staticmethod - async def register_new_user(db: AsyncSession, user_in: UserRegister, ip_address: str): - """ - MASTER REGISTRATION FLOW v1.3 (Full Integration) - """ - try: - # 1. KYC ADATOK (Banki szintű nyilvántartás) - kyc_data = { - "id_card": { - "number": user_in.id_card_number, - "expiry": str(user_in.id_card_expiry) if user_in.id_card_expiry else None - }, - "driver_license": { - "number": user_in.driver_license_number, - "expiry": str(user_in.driver_license_expiry) if user_in.driver_license_expiry else None, - "categories": user_in.driver_license_categories - }, - "special_licenses": { - "boat": user_in.boat_license_number, - "pilot": user_in.pilot_license_number - } - } - - # 2. PERSON LÉTREHOZÁSA (Digitális Iker alapja) - new_person = Person( - first_name=user_in.first_name, - last_name=user_in.last_name, - mothers_name=user_in.mothers_name, - birth_place=user_in.birth_place, - birth_date=user_in.birth_date, - identity_docs=kyc_data - ) - db.add(new_person) - await db.flush() - - # 3. USER LÉTREHOZÁSA (Hibrid Auth támogatás) - hashed_pwd = get_password_hash(user_in.password) if user_in.password else None - new_user = User( - email=user_in.email, - hashed_password=hashed_pwd, - social_provider=user_in.social_provider, - social_id=user_in.social_id, - person_id=new_person.id, - role=UserRole.USER, - region_code=user_in.region_code, - is_active=True - ) - db.add(new_user) - await db.flush() - - # 4. ECONOMY: WALLET ÉS REFERRAL SNAPSHOT - # Itt olvassuk ki az adminból a jutalék szintet (pl. 10%) - l1_commission = await AuthService.get_setting(db, "referral.level1", 10) - - db.add(Wallet(user_id=new_user.id, coin_balance=0.00, xp_balance=0)) - - # 5. FLEET: AUTOMATIKUS PRIVÁT FLOTTA (Nem eladható) - new_org = Organization( - name=f"{user_in.last_name} {user_in.first_name} Private Fleet", - org_type=OrgType.INDIVIDUAL, - owner_id=new_user.id, - is_transferable=False - ) - db.add(new_org) - await db.flush() - - # Saját flotta tulajdonjog rögzítése - db.add(OrganizationMember(organization_id=new_org.id, user_id=new_user.id, role="owner")) - - # 6. MEGHÍVÓ FELDOLGOZÁSA (Csatlakozás másik céghez) - if user_in.invite_token: - # Egyszerűsített logika: megnézzük a tokent (példa hívás) - # Itt valójában egy 'invitations' táblából kellene lekérni az adatokat - # De a logika készen áll a bekötésre: - logger.info(f"Processing invite token: {user_in.invite_token}") - # db.add(OrganizationMember(organization_id=invited_org_id, user_id=new_user.id, role=invited_role)) - - # 7. AUDIT LOG (Minden lépés visszakövethető) - audit_stmt = text(""" - INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address, created_at) - VALUES (:uid, 'USER_REGISTERED_COMPLETE_V1.3', '/api/v1/auth/register', 'POST', :ip, :now) - """) - await db.execute(audit_stmt, { - "uid": new_user.id, "ip": ip_address, "now": datetime.now(timezone.utc) - }) - - # 8. JUTALMAZÁS (Admin beállítás alapján) - reward_days = await AuthService.get_setting(db, "auth.reward_days", 14) - - # 9. EMAIL KÜLDÉS - try: - await email_manager.send_email( - recipient=user_in.email, - template_key="registration_welcome", - variables={ - "first_name": user_in.first_name, - "reward_days": reward_days - }, - user_id=new_user.id - ) - except Exception as e: - logger.warning(f"Email delivery skipped during reg: {str(e)}") - - await db.commit() - await db.refresh(new_user) - return new_user - - except Exception as e: - await db.rollback() - logger.error(f"Critical error in register_new_user: {str(e)}") - raise e - - @staticmethod - async def check_email_availability(db: AsyncSession, email: str) -> bool: - query = select(User).where(and_(User.email == email, User.is_deleted == False)) - result = await db.execute(query) - return result.scalar_one_or_none() is None \ No newline at end of file diff --git a/backend/app/services/auth_service_old3.py b/backend/app/services/auth_service_old3.py deleted file mode 100644 index adac093..0000000 --- a/backend/app/services/auth_service_old3.py +++ /dev/null @@ -1,129 +0,0 @@ -# /opt/docker/dev/service_finder/backend/app/services/auth_service.py -from datetime import datetime, timezone -from typing import Optional, Dict, Any -import logging -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, and_, text - -from app.models.identity import User, Person, Wallet, UserRole -from app.models.organization import Organization, OrgType -from app.models.vehicle import OrganizationMember -from app.schemas.auth import UserRegister -from app.core.security import get_password_hash, create_access_token -from app.services.email_manager import email_manager - -logger = logging.getLogger(__name__) - -class AuthService: - @staticmethod - async def get_setting(db: AsyncSession, key: str, default: Any = None) -> Any: - """Kiolvassa az Admin felületről állítható változókat.""" - try: - stmt = text("SELECT value FROM data.system_settings WHERE key = :key") - result = await db.execute(stmt, {"key": key}) - val = result.scalar() - return val if val is not None else default - except Exception: - return default - - @staticmethod - async def register_new_user(db: AsyncSession, user_in: UserRegister, ip_address: str): - """ - MASTER ONBOARDING v1.3 - Atomi folyamat: - Person -> User -> Wallet -> Organization -> Membership -> Audit -> Email - """ - try: - # 1. KYC Adatok struktúrálása - kyc_data = { - "id_card": {"number": user_in.id_card_number, "expiry": str(user_in.id_card_expiry) if user_in.id_card_expiry else None}, - "driver_license": { - "number": user_in.driver_license_number, - "expiry": str(user_in.driver_license_expiry) if user_in.driver_license_expiry else None, - "categories": user_in.driver_license_categories - }, - "special_licenses": {"boat": user_in.boat_license_number, "pilot": user_in.pilot_license_number} - } - - # 2. Person (Identitás) létrehozása - new_person = Person( - first_name=user_in.first_name, - last_name=user_in.last_name, - mothers_name=user_in.mothers_name, - birth_place=user_in.birth_place, - birth_date=user_in.birth_date, - identity_docs=kyc_data - ) - db.add(new_person) - await db.flush() - - # 3. User (Auth) létrehozása - hashed_pwd = get_password_hash(user_in.password) if user_in.password else None - new_user = User( - email=user_in.email, - hashed_password=hashed_pwd, - social_provider=user_in.social_provider, - social_id=user_in.social_id, - person_id=new_person.id, - role=UserRole.USER, - region_code=user_in.region_code, - is_active=True - ) - db.add(new_user) - await db.flush() - - # 4. Economy: Wallet - db.add(Wallet(user_id=new_user.id, coin_balance=0.00, xp_balance=0)) - - # 5. Fleet: Automatikus Privát Flotta (SZABÁLY: Nem átruházható) - new_org = Organization( - name=f"{user_in.last_name} {user_in.first_name} Private Fleet", - org_type=OrgType.INDIVIDUAL, - owner_id=new_user.id, - is_transferable=False - ) - db.add(new_org) - await db.flush() - - # 6. Tagság rögzítése (Privát flotta tulajdonos) - db.add(OrganizationMember(organization_id=new_org.id, user_id=new_user.id, role="owner")) - - # 7. Meghívó kezelése (Ha másik céghez is csatlakozik) - if user_in.invite_token and user_in.invite_token != "string": - logger.info(f"Processing invite token: {user_in.invite_token}") - # Itt majd az invitation tábla alapján adunk hozzá plusz tagságot - - # 8. Audit Log - audit_stmt = text(""" - INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address, created_at) - VALUES (:uid, 'REGISTER_V1.3_KYC_FULL', '/api/v1/auth/register', 'POST', :ip, :now) - """) - await db.execute(audit_stmt, {"uid": new_user.id, "ip": ip_address, "now": datetime.now(timezone.utc)}) - - # 9. Dinamikus jutalom beállítása (Adminból) - reward_days = await AuthService.get_setting(db, "auth.reward_days", 14) - - # 10. Email küldés - try: - await email_manager.send_email( - recipient=user_in.email, - template_key="registration_welcome", - variables={"first_name": user_in.first_name, "reward_days": reward_days}, - user_id=new_user.id - ) - except Exception as e: - logger.warning(f"Email skipped: {str(e)}") - - await db.commit() - await db.refresh(new_user) - return new_user - - except Exception as e: - await db.rollback() - logger.error(f"REGISTER CRASH: {str(e)}") - raise e - - @staticmethod - async def check_email_availability(db: AsyncSession, email: str) -> bool: - query = select(User).where(and_(User.email == email, User.is_deleted == False)) - result = await db.execute(query) - return result.scalar_one_or_none() is None \ No newline at end of file diff --git a/backend/app/services/harvester_base.py b/backend/app/services/harvester_base.py new file mode 100644 index 0000000..ebed6a8 --- /dev/null +++ b/backend/app/services/harvester_base.py @@ -0,0 +1,34 @@ +import httpx +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +from app.models.vehicle import VehicleCatalog + +class BaseHarvester: + def __init__(self, category: str): + self.category = category + self.headers = {"User-Agent": "ServiceFinder-Harvester-Bot/2.0"} + + async def check_exists(self, db: AsyncSession, brand: str, model: str): + """Ellenőrzi, hogy az adott modell létezik-e már.""" + stmt = select(VehicleCatalog).where( + VehicleCatalog.brand == brand, + VehicleCatalog.model == model, + VehicleCatalog.category == self.category + ) + result = await db.execute(stmt) + return result.scalar_one_or_none() + + async def log_entry(self, db: AsyncSession, brand: str, model: str, specs: dict = None): + """Létrehoz vagy frissít egy katalógus bejegyzést.""" + existing = await self.check_exists(db, brand, model) + if not existing: + new_v = VehicleCatalog( + brand=brand, + model=model, + category=self.category, + factory_specs=specs or {}, + verification_status="incomplete" if not specs else "verified" + ) + db.add(new_v) + return True + return False \ No newline at end of file diff --git a/backend/app/services/harvester_bikes.py b/backend/app/services/harvester_bikes.py new file mode 100644 index 0000000..8aef52a --- /dev/null +++ b/backend/app/services/harvester_bikes.py @@ -0,0 +1,12 @@ +from .harvester_base import BaseHarvester + +class BikeHarvester(BaseHarvester): + def __init__(self): + super().__init__(category="motorcycle") + self.api_url = "https://api.example-bikes.com/v1/" # Példa forrás + + async def harvest_all(self, db): + # Ide jön a motor-specifikus API hívás logikája + print("🏍️ Motor Robot: Adatgyűjtés indul...") + # ... fetch és mentés loop ... + await db.commit() \ No newline at end of file diff --git a/backend/app/services/harvester_robot.py b/backend/app/services/harvester_cars.py similarity index 100% rename from backend/app/services/harvester_robot.py rename to backend/app/services/harvester_cars.py diff --git a/backend/app/services/harvester_trucks.py b/backend/app/services/harvester_trucks.py new file mode 100644 index 0000000..9330342 --- /dev/null +++ b/backend/app/services/harvester_trucks.py @@ -0,0 +1,8 @@ +from .harvester_base import BaseHarvester + +class TruckHarvester(BaseHarvester): + def __init__(self): + super().__init__(category="truck") + + async def run(self, db): + print("🚛 Truck Robot: Nehézgépek és teherautók keresése...") \ No newline at end of file diff --git a/backend/app/services/robot_manager.py b/backend/app/services/robot_manager.py new file mode 100644 index 0000000..6afcbcc --- /dev/null +++ b/backend/app/services/robot_manager.py @@ -0,0 +1,22 @@ +import asyncio +from .harvester_robot import CarHarvester # A korábbi CarHarvester-t nevezzük át így +from .harvester_bikes import BikeHarvester +from .harvester_trucks import TruckHarvester + +class RobotManager: + @staticmethod + async def run_full_sync(db): + """Sorban lefuttatja az összes robotot.""" + robots = [ + CarHarvester(), + BikeHarvester(), + TruckHarvester() + ] + + for robot in robots: + try: + await robot.run(db) + # Pihenő a kategóriák között, hogy ne kapjunk IP-tiltást + await asyncio.sleep(5) + except Exception as e: + print(f"❌ Hiba a {robot.category} robotnál: {e}") \ No newline at end of file diff --git a/docs/V01_gemini/15_Changelog.md b/docs/V01_gemini/15_Changelog.md index 756c847..9a9fe0e 100644 --- a/docs/V01_gemini/15_Changelog.md +++ b/docs/V01_gemini/15_Changelog.md @@ -133,4 +133,16 @@ A fejlesztések rendben tartásához javaslom a **`17_DEVELOPER_NOTES_AND_PITFAL ### 🛠️ Refaktor (Refactor) - **Modell Konszolidáció:** A korábbi `Vehicle` és `VehicleBrand` modellek beolvasztva az új `Asset` és `VehicleCatalog` struktúrába. -- **Kapcsolati Térkép:** Az `Organization` és `User` modellek frissítve az új Asset logikához. \ No newline at end of file +- **Kapcsolati Térkép:** Az `Organization` és `User` modellek frissítve az új Asset logikához. + +## [0.3.5] - 2026-02-07 +### ✨ Robot Technológia & Adatbiztonság +- **Multi-Robot Architektúra:** Előkészítve a kategória-specifikus (Car, Bike, Truck) Harvester robotok szétválasztása. +- **Status Tracking:** Bevezetve a `verification_status` a katalógus elemekhez a hiányos adatok kezelésére. +- **SQL Optimalizálás:** Új indexek és kategória alapú szűrések hozzáadva a globális katalógushoz. + +## [0.4.0] - 2026-02-07 +### ✨ Multi-Robot System +- **Kategória Robotok:** Elkészült a Car, Bike és Truck harvester moduláris szerkezete. +- **Robot Manager:** Központi vezérlő az ütemezett és sorrendi adatgyűjtéshez. +- **Katalógus Kereső:** Üzembe helyezve a `/catalog/search` végpont a Swaggerben. \ 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 index 5afa6c5..6ff9653 100644 --- a/docs/V01_gemini/18_ASSET_AND_FLEET_SPECIFICATION.md +++ b/docs/V01_gemini/18_ASSET_AND_FLEET_SPECIFICATION.md @@ -82,4 +82,87 @@ Ha a jármű sehol nem szerepel (pl. épített versenyautó, egyedi yacht): 1. **Manuális nyilatkozat:** A felhasználó rögzíti az adatokat. 2. **Dokumentum alapú validáció:** A forgalmi engedély vagy sportigazolvány fotóját kötelező feltölteni. 3. **AI Verifikáció:** A rendszer OCR-rel (szövegfelismerés) kiolvassa az adatokat a fotóról, és összeveti a manuális bevitelével. -4. **"Unverified Model" jelzés:** A katalógusban egyedi azonosítót kap, amíg egy admin vagy a Robot más forrásból meg nem erősíti. \ No newline at end of file +4. **"Unverified Model" jelzés:** A katalógusban egyedi azonosítót kap, amíg egy admin vagy a Robot más forrásból meg nem erősíti. + +## 8. Multi-Robot Harvester Architektúra + +A rendszer kategóriánként különálló kutató robotokat használ az erőforrások optimalizálása és az adatok pontossága érdekében. + +### A) Működési elv +- **Ütemezett futás:** Minden kategória (Autó, Motor, Teher, Hajó) saját időablakban frissít, elkerülve a szerver túlterhelését. +- **Hiányos adatok kezelése:** A Robot köteles rögzíteni a járművet akkor is, ha csak részleges információt talál (pl. csak márka és típus). +- **Státusz jelölések (`verification_status`):** + - `verified`: Teljes DNS adatsor (Robot által hitelesítve). + - `incomplete`: Alapadatok megvannak, de hiányoznak technikai részletek (pl. guminyomás, olaj). + - `pending`: Felhasználó által felvett, Robot általi ellenőrzésre váró egyedi típus. + +### B) On-Demand prioritás +Amikor a felhasználó olyan típust keres, ami nem szerepel a katalógusban, a rendszer egy "Priority Trigger"-t küld az adott kategória Robotjának, amely soron kívül megkezdi a célzott adatgyűjtést. + +## 9. OCR és Dokumentum Validációs Stratégia + +A rendszer a járműokmányok (forgalmi engedély, hajólevél, lajstrom) feldolgozására hibrid OCR (Optical Character Recognition) technológiát alkalmaz. + +### A) Hibrid Feldolgozási Sorrend (Failover Logic) +A költséghatékonyság és a pontosság érdekében a rendszer az alábbi sorrendben próbálkozik: +1. **Tier 1 (External Free/Limited APIs):** Ingyenes keretű felhő szolgáltatások (pl. Google Vision API, Azure Form Recognizer). A rendszer figyeli a havi limiteket, és azok elérésekor automatikusan vált a következő szolgáltatóra. +2. **Tier 2 (Saját Erőforrás - Fallback):** Ha minden ingyenes külső keret elfogyott, a rendszer a saját szerveren futó PaddleOCR (AI alapú) modult használja. + +### B) Monetizáció és Jogosultságok +A dokumentum alapú automata rögzítés és validáció prémium funkció: +- **Free/Lite:** Manuális rögzítés (limitált mezők). +- **VIP:** Automata rögzítés (OCR) 1-2 eszközre. +- **VIP + / Premium +:** Korlátlan okmányfelismerés, automata lejárati figyelmeztetések és hivatalos adat-összevetés. + +## 10. Multi-Robot Harvester (Moduláris Felépítés) + +A járműkatalógus feltöltését egy bázis-osztályra (`BaseHarvester`) épülő, kategória-specifikus robotcsalád végzi. + +- **Autó Robot:** Közúti gépjárművekre optimalizálva. +- **Motor Robot:** Kétkerekű és hobbi járművekre. +- **Heavy Duty Robot:** Teherautók, kamionok és munkagépek specifikációira. +- **Specialty Robot:** Vízi és légi járművek egyedi azonosítóihoz (MMSI, Lajstrom). + +# 🏎️ Asset és Flotta Specifikáció: A Járművek DNS-e (v1.2) + +## 7. Kivételkezelés: Ismeretlen és Egyedi Járművek +- **On-Demand Harvester:** Ha a katalógus hiányos, a Robot kérésre (Trigger) indítja a mélykeresést. +- **Custom Asset:** Egyedi/Sport eszközök rögzítése dokumentum alapú validációval. + +## 8. Multi-Robot Harvester Architektúra +A rendszer kategória-specifikus (Car, Bike, Truck, Specialty) robotokat használ. +- **Ütemezés:** Éjszakai batch-futás a szerver terhelésének minimalizálására. +- **Státuszok:** `verified` (teljes), `incomplete` (részleges), `pending` (ellenőrzésre vár). + +## 9. VIN (Alvázszám) és Validáció +- **Algoritmus:** Minden közúti járműnél kötelező a VIN Checksum (MOD 11) ellenőrzése a beíráskor. +- **Auto-Fill:** Érvényes alvázszám esetén a rendszer felajánlja a gyártói adatok (gyártási év, üzem, motorverzió) automatikus kitöltését. + +## 10. Dokumentum Kezelés és Tárolás (NAS) +Minden eszközhöz csatolt dokumentum (forgalmi, fotók, számlák) központi NAS tárolón kerül rögzítésre. +- **Elérési út:** `/mnt/nas/app_data/assets/{asset_id}/` +- **Archiválás:** `/mnt/nas/git_vault/` (Adatbázis mentések és konfigurációk). + +## 11. OCR és Üzleti Logika (Tier-based) +A dokumentumfelismerés (OCR) prioritása a felhasználói csomagtól függ: +- **VIP+ / Premium+:** Azonnali (Real-time) OCR feldolgozás. A felhasználó a feltöltés után 3-5 másodperccel már látja az előtöltött adatokat. +- **Alap csomag:** Háttérfolyamat (Background task). A feldolgozás sorban állítás után történik, a felhasználó értesítést kap a befejezésről. +- **Failover:** Külső API-k (Google/Azure) és saját erőforrás (PaddleOCR) hibrid használata a költségkontroll érdekében. + +## 12. OCR Monetizáció és Kreditszabályok (Admin Kontroll) + +A rendszer az OCR alapú adatbeolvasást kvótákhoz és kreditekhez köti. + +### A) Csomag alapú kvóták (Admin beállítás) +Az Admin felületen csomagonként (Lite, VIP, VIP+) meghatározható egy ingyenes havi dokumentum-beolvasási keret: +- **Lite:** 0-1 scan/hó. +- **VIP:** 10 scan/hó. +- **VIP+:** Korlátlan vagy magas limit (pl. 100). + +### B) Kreditalapú túllépés +Ha a felhasználó kimerítette a keretét, minden további beolvasás kreditért vásárolható meg. +- **Egységár:** Admin felületről állítható (pl. 1 beolvasás = 10 kredit). +- **Tranzakció:** A rendszer levonja a kreditet a felhasználó `Wallet`-jéből a sikeres OCR feldolgozás után. + +### C) Egyedi engedélyek (Permissions) +Lehetőség van egyedi felhasználóknak vagy flottáknak "OCR_Override" jogot adni, amivel a csomagtól függetlenül ingyenes vagy kedvezményes beolvasást kapnak (pl. tesztelők vagy stratégiai partnerek). \ No newline at end of file