feat: implement pivot-currency model, rbac smart tokens & fix circular imports
This commit is contained in:
Binary file not shown.
BIN
backend/app/services/__pycache__/cost_service.cpython-312.pyc
Normal file
BIN
backend/app/services/__pycache__/cost_service.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
35
backend/app/services/asset_service.py
Normal file
35
backend/app/services/asset_service.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.models.asset import Asset, AssetTelemetry, AssetFinancials
|
||||
from app.models.gamification import UserStats, PointRule
|
||||
import uuid
|
||||
|
||||
async def create_new_vehicle(db: AsyncSession, user_id: int, vin: str, license_plate: str):
|
||||
# 1. Alap Asset létrehozása
|
||||
new_asset = Asset(
|
||||
vin=vin,
|
||||
license_plate=license_plate,
|
||||
name=f"Teszt Autó ({license_plate})"
|
||||
)
|
||||
db.add(new_asset)
|
||||
await db.flush() # Hogy legyen ID-ja
|
||||
|
||||
# 2. Modulok inicializálása (Digital Twin alapozás)
|
||||
db.add(AssetTelemetry(asset_id=new_asset.id, current_mileage=0))
|
||||
db.add(AssetFinancials(asset_id=new_asset.id))
|
||||
|
||||
# 3. GAMIFICATION: Pontszerzés (ASSET_REGISTER = 100 XP)
|
||||
# Megkeressük a szabályt
|
||||
rule_stmt = select(PointRule).where(PointRule.action_key == "ASSET_REGISTER")
|
||||
rule = (await db.execute(rule_stmt)).scalar_one_or_none()
|
||||
|
||||
if rule:
|
||||
# Frissítjük a felhasználó XP-jét
|
||||
stats_stmt = select(UserStats).where(UserStats.user_id == user_id)
|
||||
stats = (await db.execute(stats_stmt)).scalar_one_or_none()
|
||||
if stats:
|
||||
stats.total_xp += rule.points
|
||||
# Itt később jöhet a szintlépés ellenőrzése is!
|
||||
|
||||
await db.commit()
|
||||
return new_asset
|
||||
@@ -10,7 +10,7 @@ from sqlalchemy.orm import joinedload
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
from app.models.identity import User, Person, UserRole, VerificationToken, Wallet
|
||||
from app.models.gamification import UserStats # <--- Innen importáljuk mostantól!
|
||||
from app.models.gamification import UserStats
|
||||
from app.models.organization import Organization, OrganizationMember, OrgType
|
||||
from app.schemas.auth import UserLiteRegister, UserKYCComplete
|
||||
from app.core.security import get_password_hash, verify_password
|
||||
@@ -26,7 +26,7 @@ class AuthService:
|
||||
async def register_lite(db: AsyncSession, user_in: UserLiteRegister):
|
||||
"""
|
||||
Step 1: Lite Regisztráció (Master Book 1.1)
|
||||
Új User és ideiglenes Person rekord létrehozása.
|
||||
Új User és ideiglenes Person rekord létrehozása nyelvi és időzóna adatokkal.
|
||||
"""
|
||||
try:
|
||||
# Ideiglenes Person rekord a KYC-ig
|
||||
@@ -45,7 +45,10 @@ class AuthService:
|
||||
role=UserRole.user,
|
||||
is_active=False,
|
||||
is_deleted=False,
|
||||
region_code=user_in.region_code
|
||||
region_code=user_in.region_code,
|
||||
# --- NYELVI ÉS ADMIN BEÁLLÍTÁSOK MENTÉSE ---
|
||||
preferred_language=user_in.lang,
|
||||
timezone=user_in.timezone
|
||||
)
|
||||
db.add(new_user)
|
||||
await db.flush()
|
||||
@@ -60,12 +63,14 @@ class AuthService:
|
||||
expires_at=datetime.now(timezone.utc) + timedelta(hours=int(reg_hours))
|
||||
))
|
||||
|
||||
# Email küldés (Master Book 3.2: Nincs manuális subject)
|
||||
# --- EMAIL KÜLDÉSE A VÁLASZTOTT NYELVEN ---
|
||||
# Master Book 3.2: Nincs manuális subject, a nyelvi kulcs alapján töltődik be
|
||||
verification_link = f"{settings.FRONTEND_BASE_URL}/verify?token={token_val}"
|
||||
await email_manager.send_email(
|
||||
recipient=user_in.email,
|
||||
template_key="registration",
|
||||
variables={"first_name": user_in.first_name, "link": verification_link}
|
||||
template_key="reg", # hu.json: email.reg_subject, reg_greeting stb.
|
||||
variables={"first_name": user_in.first_name, "link": verification_link},
|
||||
lang=user_in.lang # Dinamikus nyelvválasztás
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
@@ -81,6 +86,7 @@ class AuthService:
|
||||
"""
|
||||
1.3. Fázis: Atomi Tranzakció & Shadow Identity
|
||||
Felismeri a visszatérő Person-t, de új User-ként, izolált flottával indít.
|
||||
Frissíti a nyelvi és pénzügyi beállításokat.
|
||||
"""
|
||||
try:
|
||||
# 1. Aktuális technikai User lekérése
|
||||
@@ -89,8 +95,11 @@ class AuthService:
|
||||
user = res.scalar_one_or_none()
|
||||
if not user: return None
|
||||
|
||||
# 2. Shadow Identity Ellenőrzése (Anyja neve + Születési hely + Idő alapján)
|
||||
# Globális keresés, régiótól függetlenül
|
||||
# --- PÉNZNEM PREFERENCIA FRISSÍTÉSE ---
|
||||
if hasattr(kyc_in, 'preferred_currency') and kyc_in.preferred_currency:
|
||||
user.preferred_currency = kyc_in.preferred_currency
|
||||
|
||||
# 2. Shadow Identity Ellenőrzése
|
||||
identity_stmt = select(Person).where(and_(
|
||||
Person.mothers_last_name == kyc_in.mothers_last_name,
|
||||
Person.mothers_first_name == kyc_in.mothers_first_name,
|
||||
@@ -100,7 +109,6 @@ class AuthService:
|
||||
existing_person = (await db.execute(identity_stmt)).scalar_one_or_none()
|
||||
|
||||
if existing_person:
|
||||
# Visszatérő identitás: A User-t a régi Person-hoz kötjük
|
||||
user.person_id = existing_person.id
|
||||
active_person = existing_person
|
||||
logger.info(f"Shadow Identity linked: User {user_id} -> Person {existing_person.id}")
|
||||
@@ -118,7 +126,7 @@ class AuthService:
|
||||
parcel_id=kyc_in.address_hrsz
|
||||
)
|
||||
|
||||
# 4. Person adatok frissítése (mindig a legfrissebbet tároljuk)
|
||||
# 4. Person adatok frissítése
|
||||
active_person.mothers_last_name = kyc_in.mothers_last_name
|
||||
active_person.mothers_first_name = kyc_in.mothers_first_name
|
||||
active_person.birth_place = kyc_in.birth_place
|
||||
@@ -129,7 +137,7 @@ class AuthService:
|
||||
active_person.ice_contact = jsonable_encoder(kyc_in.ice_contact)
|
||||
active_person.is_active = True
|
||||
|
||||
# 5. Új, izolált INDIVIDUAL szervezet (4.2.3)
|
||||
# 5. Új, izolált INDIVIDUAL szervezet (4.2.3) i18n beállításokkal
|
||||
new_org = Organization(
|
||||
full_name=f"{active_person.last_name} {active_person.first_name} Egyéni Flotta",
|
||||
name=f"{active_person.last_name} Flotta",
|
||||
@@ -137,7 +145,11 @@ class AuthService:
|
||||
owner_id=user.id,
|
||||
is_transferable=False,
|
||||
is_active=True,
|
||||
status="verified"
|
||||
status="verified",
|
||||
# Megörökölt adminisztrációs adatok
|
||||
language=user.preferred_language,
|
||||
default_currency=user.preferred_currency,
|
||||
country_code=user.region_code
|
||||
)
|
||||
db.add(new_org)
|
||||
await db.flush()
|
||||
@@ -150,8 +162,13 @@ class AuthService:
|
||||
permissions={"can_add_asset": True, "can_view_costs": True, "is_admin": True}
|
||||
))
|
||||
|
||||
# 7. Wallet & Stats (Friss kezdés 0 ponttal)
|
||||
db.add(Wallet(user_id=user.id, coin_balance=0, credit_balance=0))
|
||||
# 7. Wallet & Stats
|
||||
db.add(Wallet(
|
||||
user_id=user.id,
|
||||
coin_balance=0,
|
||||
credit_balance=0,
|
||||
currency=user.preferred_currency
|
||||
))
|
||||
db.add(UserStats(user_id=user.id, total_xp=0, current_level=1))
|
||||
|
||||
# 8. Aktiválás
|
||||
@@ -197,7 +214,6 @@ class AuthService:
|
||||
|
||||
@staticmethod
|
||||
async def initiate_password_reset(db: AsyncSession, email: str):
|
||||
# Csak aktív (nem törölt) felhasználónak engedünk jelszót resetelni
|
||||
stmt = select(User).where(and_(User.email == email, User.is_deleted == False))
|
||||
user = (await db.execute(stmt)).scalar_one_or_none()
|
||||
|
||||
@@ -211,11 +227,13 @@ class AuthService:
|
||||
expires_at=datetime.now(timezone.utc) + timedelta(hours=int(reset_hours))
|
||||
))
|
||||
|
||||
# --- EMAIL KÜLDÉSE A FELHASZNÁLÓ SAJÁT NYELVÉN ---
|
||||
reset_link = f"{settings.FRONTEND_BASE_URL}/reset-password?token={token_val}"
|
||||
await email_manager.send_email(
|
||||
recipient=email,
|
||||
template_key="password_reset",
|
||||
variables={"link": reset_link}
|
||||
template_key="pwd_reset", # hu.json: email.pwd_reset_subject stb.
|
||||
variables={"link": reset_link},
|
||||
lang=user.preferred_language # Adatbázisból kinyert nyelv
|
||||
)
|
||||
await db.commit()
|
||||
return "success"
|
||||
|
||||
97
backend/app/services/cost_service.py
Normal file
97
backend/app/services/cost_service.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, desc
|
||||
from app.models.asset import AssetCost, AssetTelemetry, ExchangeRate
|
||||
from app.models.gamification import UserStats
|
||||
from app.models.system_config import SystemParameter
|
||||
from app.schemas.asset_cost import AssetCostCreate
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CostService:
|
||||
@staticmethod
|
||||
async def get_param(db: AsyncSession, key: str, default: any) -> any:
|
||||
"""Rendszerparaméter lekérése (pl. XP szorzó)."""
|
||||
stmt = select(SystemParameter).where(SystemParameter.key == key)
|
||||
res = await db.execute(stmt)
|
||||
param = res.scalar_one_or_none()
|
||||
return param.value if param else default
|
||||
|
||||
async def record_cost(self, db: AsyncSession, cost_in: AssetCostCreate, user_id: int):
|
||||
"""
|
||||
Költség rögzítése: EUR konverzió + Telemetria + XP.
|
||||
"""
|
||||
try:
|
||||
# 1. Árfolyam lekérése (EUR alapú pivot)
|
||||
# Megkeressük a legfrissebb rögzített árfolyamot a megadott devizához
|
||||
rate_stmt = select(ExchangeRate).where(
|
||||
ExchangeRate.target_currency == cost_in.currency_local
|
||||
).order_by(desc(ExchangeRate.updated_at)).limit(1)
|
||||
|
||||
rate_res = await db.execute(rate_stmt)
|
||||
rate_obj = rate_res.scalar_one_or_none()
|
||||
|
||||
# Ha nincs rögzített árfolyam, 1.0-val számolunk (vagy hibát dobhatunk a konfigurációtól függően)
|
||||
exchange_rate = rate_obj.rate if rate_obj else Decimal("1.0")
|
||||
|
||||
# EUR kalkuláció: Helyi összeg / Árfolyam (Pl. 40000 HUF / 400 = 100 EUR)
|
||||
amt_eur = Decimal(str(cost_in.amount_local)) / exchange_rate if exchange_rate > 0 else Decimal("0")
|
||||
|
||||
# 2. Költség rekord létrehozása
|
||||
new_cost = AssetCost(
|
||||
asset_id=cost_in.asset_id,
|
||||
organization_id=cost_in.organization_id,
|
||||
driver_id=user_id,
|
||||
cost_type=cost_in.cost_type,
|
||||
amount_local=cost_in.amount_local,
|
||||
currency_local=cost_in.currency_local,
|
||||
amount_eur=amt_eur,
|
||||
net_amount_local=cost_in.net_amount_local,
|
||||
vat_rate=cost_in.vat_rate,
|
||||
exchange_rate_used=exchange_rate,
|
||||
mileage_at_cost=cost_in.mileage_at_cost,
|
||||
date=cost_in.date or datetime.now(),
|
||||
data=cost_in.data or {}
|
||||
)
|
||||
db.add(new_cost)
|
||||
|
||||
# 3. Telemetria frissítése (Ha érkezett kilométeróra állás)
|
||||
if cost_in.mileage_at_cost:
|
||||
tel_stmt = select(AssetTelemetry).where(AssetTelemetry.asset_id == cost_in.asset_id)
|
||||
res = await db.execute(tel_stmt)
|
||||
telemetry = res.scalar_one_or_none()
|
||||
|
||||
if telemetry:
|
||||
# Megakadályozzuk a "visszatekerést"
|
||||
if cost_in.mileage_at_cost > (telemetry.current_mileage or 0):
|
||||
telemetry.current_mileage = cost_in.mileage_at_cost
|
||||
else:
|
||||
# Ha még nem volt telemetria adat, létrehozzuk
|
||||
new_telemetry = AssetTelemetry(
|
||||
asset_id=cost_in.asset_id,
|
||||
current_mileage=cost_in.mileage_at_cost
|
||||
)
|
||||
db.add(new_telemetry)
|
||||
|
||||
# 4. Gamification XP jóváírás
|
||||
xp_reward = await self.get_param(db, "XP_PER_COST_LOG", 50)
|
||||
stats_stmt = select(UserStats).where(UserStats.user_id == user_id)
|
||||
stats_res = await db.execute(stats_stmt)
|
||||
user_stats = stats_res.scalar_one_or_none()
|
||||
|
||||
if user_stats:
|
||||
user_stats.total_xp += int(xp_reward)
|
||||
logger.info(f"User {user_id} earned {xp_reward} XP for cost logging.")
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(new_cost)
|
||||
return new_cost
|
||||
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Error in record_cost: {str(e)}")
|
||||
raise e
|
||||
|
||||
cost_service = CostService()
|
||||
@@ -17,6 +17,9 @@ class EmailManager:
|
||||
button_text = locale_manager.get(f"email.{template_key}_button", lang=lang)
|
||||
footer = locale_manager.get(f"email.{template_key}_footer", lang=lang)
|
||||
|
||||
# ÚJ: A link fallback szöveg is a nyelvi fájlból jön
|
||||
link_fallback_text = locale_manager.get("email.link_fallback", lang=lang)
|
||||
|
||||
return f"""
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; color: #333; line-height: 1.6;">
|
||||
@@ -30,8 +33,8 @@ class EmailManager:
|
||||
</a>
|
||||
</div>
|
||||
<p style="font-size: 0.85em; color: #777; word-break: break-all;">
|
||||
Ha a gomb nem működik, másolja be ezt a linket a böngészőjébe:<br>
|
||||
{variables.get('link')}
|
||||
{link_fallback_text}<br>
|
||||
<a href="{variables.get('link')}" style="color: #3498db;">{variables.get('link')}</a>
|
||||
</p>
|
||||
<hr style="border: 0; border-top: 1px solid #eee; margin: 30px 0;">
|
||||
<p style="font-size: 0.8em; color: #999; text-align: center;">{footer}</p>
|
||||
@@ -66,18 +69,11 @@ class EmailManager:
|
||||
response = sg.send(message)
|
||||
|
||||
logger.info(f"SendGrid Status: {response.status_code} for {recipient}")
|
||||
if response.status_code >= 400:
|
||||
logger.error(f"SendGrid Hibaüzenet: {response.body}")
|
||||
|
||||
return {"status": "success", "provider": "sendgrid", "code": response.status_code}
|
||||
except Exception as e:
|
||||
logger.error(f"SendGrid Kritikus Hiba: {str(e)}")
|
||||
|
||||
# 2. SMTP Fallback
|
||||
if not settings.SMTP_HOST or not settings.SMTP_USER or not settings.SMTP_PASSWORD:
|
||||
logger.warning("SMTP nincs konfigurálva a fallback-hez.")
|
||||
return {"status": "error", "message": "Nincs elérhető szolgáltató."}
|
||||
|
||||
try:
|
||||
msg = MIMEMultipart()
|
||||
msg["From"] = f"{settings.EMAILS_FROM_NAME} <{settings.EMAILS_FROM_EMAIL}>"
|
||||
|
||||
@@ -1,26 +1,47 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, text
|
||||
from sqlalchemy import select
|
||||
from app.models.gamification import UserStats, PointsLedger
|
||||
from app.models.identity import User
|
||||
import math
|
||||
|
||||
class GamificationService:
|
||||
@staticmethod
|
||||
async def award_points(db: AsyncSession, user_id: int, points: int, reason: str):
|
||||
"""Pontok jóváírása (SQL szinkronizált points mezővel)."""
|
||||
new_entry = PointsLedger(
|
||||
user_id=user_id,
|
||||
points=points, # Javítva: points_change helyett points
|
||||
reason=reason
|
||||
)
|
||||
db.add(new_entry)
|
||||
|
||||
result = await db.execute(select(UserStats).where(UserStats.user_id == user_id))
|
||||
stats = result.scalar_one_or_none()
|
||||
async def process_activity(db: AsyncSession, user_id: int, xp_amount: int, social_amount: int, reason: str):
|
||||
"""
|
||||
XP növelés, Szintlépés csekk és Automata Kredit váltás.
|
||||
"""
|
||||
# 1. User statisztika lekérése
|
||||
stmt = select(UserStats).where(UserStats.user_id == user_id)
|
||||
stats = (await db.execute(stmt)).scalar_one_or_none()
|
||||
|
||||
if not stats:
|
||||
stats = UserStats(user_id=user_id, total_points=0, current_level=1)
|
||||
stats = UserStats(user_id=user_id, total_xp=0, social_points=0, current_level=1, credits=0)
|
||||
db.add(stats)
|
||||
|
||||
# 2. Részletes Logolás (PointsLedger) - A visszakövethetőség miatt
|
||||
db.add(PointsLedger(
|
||||
user_id=user_id,
|
||||
xp_gain=xp_amount,
|
||||
social_gain=social_amount,
|
||||
reason=reason
|
||||
))
|
||||
|
||||
# 3. XP és Szintlépés (Nehezedő görbe)
|
||||
stats.total_xp += xp_amount
|
||||
# Képlet: Level = (XP / 500)^(1/1.5)
|
||||
new_level = int((stats.total_xp / 500) ** (1/1.5)) + 1
|
||||
if new_level > stats.current_level:
|
||||
stats.current_level = new_level
|
||||
|
||||
# 4. Automata Kredit váltás
|
||||
# Példa: Minden 100 Social pont automatikusan 1 Kredit lesz
|
||||
stats.social_points += social_amount
|
||||
if stats.social_points >= 100:
|
||||
new_credits = stats.social_points // 100
|
||||
stats.credits += new_credits
|
||||
stats.social_points %= 100 # A maradék megmarad a következő váltáshoz
|
||||
|
||||
stats.total_points += points
|
||||
await db.flush()
|
||||
return stats.total_points
|
||||
# Külön log a váltásról
|
||||
db.add(PointsLedger(user_id=user_id, reason=f"Auto-conversion: {new_credits} Credits", credits_change=new_credits))
|
||||
|
||||
await db.commit()
|
||||
return stats
|
||||
@@ -1,34 +1,45 @@
|
||||
# /app/services/harvester_base.py
|
||||
import httpx
|
||||
import logging
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.models.vehicle import VehicleCatalog
|
||||
from app.models.asset import AssetCatalog
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class BaseHarvester:
|
||||
def __init__(self, category: str):
|
||||
self.category = category
|
||||
self.category = category # car, bike, truck
|
||||
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
|
||||
async def check_exists(self, db: AsyncSession, brand: str, model: str, gen: str = None):
|
||||
"""Ellenőrzi a katalógusban való létezést."""
|
||||
stmt = select(AssetCatalog).where(
|
||||
AssetCatalog.make == brand,
|
||||
AssetCatalog.model == model,
|
||||
AssetCatalog.vehicle_class == self.category
|
||||
)
|
||||
if gen:
|
||||
stmt = stmt.where(AssetCatalog.generation == gen)
|
||||
|
||||
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)
|
||||
async def log_entry(self, db: AsyncSession, brand: str, model: str, specs: dict):
|
||||
"""Létrehoz vagy frissít egy bejegyzést az AssetCatalog-ban."""
|
||||
existing = await self.check_exists(db, brand, model, specs.get("generation"))
|
||||
if not existing:
|
||||
new_v = VehicleCatalog(
|
||||
brand=brand,
|
||||
new_v = AssetCatalog(
|
||||
make=brand,
|
||||
model=model,
|
||||
category=self.category,
|
||||
factory_specs=specs or {},
|
||||
verification_status="incomplete" if not specs else "verified"
|
||||
generation=specs.get("generation"),
|
||||
year_from=specs.get("year_from"),
|
||||
year_to=specs.get("year_to"),
|
||||
vehicle_class=self.category,
|
||||
fuel_type=specs.get("fuel_type"),
|
||||
engine_code=specs.get("engine_code")
|
||||
)
|
||||
db.add(new_v)
|
||||
logger.info(f"🆕 Új katalógus elem: {brand} {model}")
|
||||
return True
|
||||
return False
|
||||
51
backend/app/services/recon_bot.py
Normal file
51
backend/app/services/recon_bot.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.models.asset import Asset, AssetCatalog, AssetTelemetry
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def run_vehicle_recon(db: AsyncSession, asset_id: str):
|
||||
"""
|
||||
VIN alapján megkeresi a mélységi adatokat és frissíti a Digitális Ikert.
|
||||
"""
|
||||
# 1. Lekérjük a járművet és a katalógusát
|
||||
stmt = select(Asset).where(Asset.id == asset_id)
|
||||
result = await db.execute(stmt)
|
||||
asset = result.scalar_one_or_none()
|
||||
|
||||
if not asset or not asset.catalog_id:
|
||||
return False
|
||||
|
||||
logger.info(f"🤖 Robot indul: {asset.vin} felderítése...")
|
||||
|
||||
# 2. SZIMULÁLT ADATGYŰJTÉS (Itt hívnánk meg az API-kat: NHTSA, autodna stb.)
|
||||
await asyncio.sleep(2) # Időigényes keresés szimulálása
|
||||
|
||||
deep_data = {
|
||||
"assembly_plant": "Fremont, California",
|
||||
"drive_unit": "Dual Motor - Raven type",
|
||||
"onboard_charger": "11 kW",
|
||||
"supercharging_max": "250 kW",
|
||||
"safety_rating": "5-star EuroNCAP"
|
||||
}
|
||||
|
||||
# 3. Katalógus frissítése
|
||||
catalog_stmt = select(AssetCatalog).where(AssetCatalog.id == asset.catalog_id)
|
||||
catalog = (await db.execute(catalog_stmt)).scalar_one_or_none()
|
||||
|
||||
if catalog:
|
||||
current_data = catalog.factory_data or {}
|
||||
current_data.update(deep_data)
|
||||
catalog.factory_data = current_data
|
||||
|
||||
# 4. Telemetria frissítése (A robot talált egy visszahívást, VQI csökken kicsit)
|
||||
telemetry_stmt = select(AssetTelemetry).where(AssetTelemetry.asset_id == asset_id)
|
||||
telemetry = (await db.execute(telemetry_stmt)).scalar_one_or_none()
|
||||
if telemetry:
|
||||
telemetry.vqi_score = 99.2 # Robot frissített állapota
|
||||
|
||||
await db.commit()
|
||||
logger.info(f"✨ Robot végzett: {asset.license_plate} felokosítva.")
|
||||
return True
|
||||
@@ -1,40 +1,37 @@
|
||||
# /app/services/robot_manager.py
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime
|
||||
# Frissített importok az új fájlnevekhez:
|
||||
from .harvester_cars import CarHarvester
|
||||
from .harvester_bikes import BikeHarvester
|
||||
from .harvester_trucks import TruckHarvester
|
||||
# Megjegyzés: Ellenőrizd, hogy a harvester_bikes/trucks fájlokban is BaseHarvester az alap!
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class RobotManager:
|
||||
@staticmethod
|
||||
async def run_full_sync(db):
|
||||
"""Sorban lefuttatja az összes robotot."""
|
||||
print(f"🕒 Szinkronizáció indítva: {datetime.now()}")
|
||||
"""Sorban lefuttatja a robotokat az új AssetCatalog struktúrához."""
|
||||
logger.info(f"🕒 Teljes szinkronizáció indítva: {datetime.now()}")
|
||||
|
||||
robots = [
|
||||
CarHarvester(),
|
||||
BikeHarvester(),
|
||||
TruckHarvester()
|
||||
# BikeHarvester(),
|
||||
# TruckHarvester()
|
||||
]
|
||||
|
||||
for robot in robots:
|
||||
try:
|
||||
# Itt a robot lekéri az API-tól az ÖSSZES márkát és frissít
|
||||
await robot.run(db)
|
||||
logger.info(f"✅ {robot.category} robot sikeresen lefutott.")
|
||||
await asyncio.sleep(5)
|
||||
except Exception as e:
|
||||
print(f"❌ Hiba a {robot.category} robotnál: {e}")
|
||||
logger.error(f"❌ Kritikus hiba a {robot.category} robotnál: {e}")
|
||||
|
||||
@staticmethod
|
||||
async def schedule_nightly_run(db):
|
||||
"""
|
||||
Egyszerű ciklus, ami figyeli az időt.
|
||||
Ha éjjel 2 óra van, elindítja a teljes szinkront.
|
||||
"""
|
||||
while True:
|
||||
now = datetime.now()
|
||||
# Ha hajnali 2 és 2:01 között vagyunk, indítás
|
||||
if now.hour == 2 and now.minute == 0:
|
||||
await RobotManager.run_full_sync(db)
|
||||
await asyncio.sleep(70) # Várunk, hogy ne induljon el többször ugyanabban a percben
|
||||
await asyncio.sleep(30) # 30 másodpercenként ellenőrizzük az időt
|
||||
await asyncio.sleep(70)
|
||||
await asyncio.sleep(30)
|
||||
Reference in New Issue
Block a user