átlagos kiegészítséek jó sok

This commit is contained in:
Roo
2026-03-22 11:02:05 +00:00
parent f53e0b53df
commit 5d44339f21
249 changed files with 20922 additions and 2253 deletions

View File

@@ -1,4 +1,4 @@
# /opt/docker/dev/service_finder/backend/app/workers/service_hunter.py
# /opt/docker/dev/service_finder/backend/app/workers/service/service_robot_0_hunter.py
import asyncio
import httpx
import logging
@@ -8,7 +8,7 @@ from datetime import datetime, timezone
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, text, update
from app.db.session import AsyncSessionLocal
from app.models.staged_data import ServiceStaging, DiscoveryParameter
from app.models.marketplace.staged_data import ServiceStaging, DiscoveryParameter
# Naplózás beállítása a Sentinel monitorozáshoz
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s')
@@ -119,7 +119,8 @@ class ServiceHunter:
@classmethod
async def run_grid_search(cls, db: AsyncSession, task: DiscoveryParameter):
""" A város koordináta-alapú bejárása. """
bbox = await cls._get_city_bounds(task.city, task.country_code or 'HU')
# DiscoveryParameter modellnek nincs country_code mezője, ezért alapértelmezett 'HU'-t használunk
bbox = await cls._get_city_bounds(task.city, 'HU')
if not bbox:
return

View File

@@ -6,7 +6,7 @@ import httpx
from urllib.parse import quote
from sqlalchemy import select, text
from app.database import AsyncSessionLocal
from app.models.service import ServiceStaging # JAVÍTOTT IMPORT ÚTVONAL!
from app.models.marketplace.service import ServiceStaging # JAVÍTOTT IMPORT ÚTVONAL!
import re
# Logolás MB 2.0 szabvány szerint

View File

@@ -3,7 +3,7 @@ import logging
import warnings
from sqlalchemy import text, update
from app.database import AsyncSessionLocal
from app.models.service import ServiceStaging
from app.models.marketplace.service import ServiceStaging
warnings.filterwarnings("ignore", category=RuntimeWarning, module='duckduckgo_search')
from duckduckgo_search import DDGS
@@ -23,8 +23,8 @@ class ServiceResearcher:
try:
def search():
with DDGS() as ddgs:
results = ddgs.text(query, max_results=3)
return [f"- {r.get('body', '')}" for r in results] if results else []
results = ddgs.search(query, max_results=3)
return [f"- {r.get('body', r.get('snippet', ''))}" for r in results] if results else []
results = await asyncio.wait_for(asyncio.to_thread(search), timeout=self.search_timeout)
if not results: return ""

View File

@@ -1,63 +1,46 @@
import asyncio
import logging
import json
from sqlalchemy import select, text, update, func
from app.database import AsyncSessionLocal # JAVÍTVA
from app.models.service import ServiceProfile, ExpertiseTag, ServiceExpertise, ServiceStaging
from sqlalchemy import select, text
from app.database import AsyncSessionLocal
from app.models.marketplace.service import ExpertiseTag
# Logolás MB 2.0 szabvány
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s')
logger = logging.getLogger("Service-Robot-3-Enricher")
class ServiceEnricher:
"""
Service Robot 3: Professional Classifier (Atomi Zárolással)
"""
""" Service Robot 3: Professional Classifier (Bíró-Kompatibilis Verzió) """
@staticmethod
async def match_expertise_to_service(db, service_profile_id: int, scraped_text: str):
""" Kulcsszó-alapú elemző motor az ExpertiseTag tábla alapján. """
if not scraped_text: return
async def match_expertise_and_score(db, scraped_text: str, current_trust_score: int) -> int:
""" Keresi a szakmákat és bónusz pontokat ad értük a Staging adatnak. """
if not scraped_text: return current_trust_score
tags_query = await db.execute(select(ExpertiseTag).where(ExpertiseTag.is_official == True))
all_tags = tags_query.scalars().all()
found_any = False
match_count = 0
for tag in all_tags:
match_count = 0
for kw in (tag.search_keywords or []):
if kw.lower() in scraped_text.lower():
match_count += 1
if match_count > 0:
existing_check = await db.execute(
select(ServiceExpertise).where(
ServiceExpertise.service_id == service_profile_id,
ServiceExpertise.expertise_id == tag.id
)
)
if not existing_check.scalar():
new_link = ServiceExpertise(
service_id=service_profile_id,
expertise_id=tag.id,
confidence_level=min(match_count, 2)
)
db.add(new_link)
found_any = True
logger.info(f"{tag.key} szakma azonosítva a szerviznél.")
break # Egy tag elég, ha egyszer megvan
if found_any:
await db.commit()
# +5 pont minden megtalált szakmáért, max 30 bónusz pont
bonus = min(match_count * 5, 30)
new_score = min(current_trust_score + bonus, 100)
if bonus > 0:
logger.info(f"{match_count} szakma azonosítva. Bónusz: +{bonus} pont.")
return new_score
@classmethod
async def run_worker(cls):
logger.info("🧠 Service Enricher ONLINE - Szakmai elemzés indítása (Atomi Zárolás)")
logger.info("🧠 Service Enricher ONLINE - Adatdúsítás (Nem publikál, csak pontoz!)")
while True:
try:
async with AsyncSessionLocal() as db:
# 1. Zárolunk egy "enrich_ready" szervizt a Staging táblából
query = text("""
UPDATE marketplace.service_staging
SET status = 'enriching'
@@ -67,41 +50,34 @@ class ServiceEnricher:
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING id, name, city, full_address, fingerprint, raw_data;
RETURNING id, name, trust_score, raw_data;
""")
result = await db.execute(query)
task = result.fetchone()
await db.commit()
if task:
s_id, name, city, address, fprint, raw_data = task
s_id, name, t_score, raw_data = task
web_context = raw_data.get('web_context', '') if isinstance(raw_data, dict) else ''
async with AsyncSessionLocal() as process_db:
try:
# 2. Áttesszük a végleges ServiceProfile táblába (mert már van elég adatunk a webről)
profile_stmt = text("""
INSERT INTO marketplace.service_profiles
(fingerprint, status, trust_score, location, is_verified, bio)
VALUES (:fp, 'active', 40, ST_SetSRID(ST_MakePoint(19.04, 47.49), 4326), false, :bio)
ON CONFLICT (fingerprint) DO UPDATE SET bio = EXCLUDED.bio
RETURNING id;
""") # Megjegyzés: A GPS koordinátát (19.04, 47.49) majd a Validator (Robot-4) pontosítja!
# 1. Kiszámoljuk az új pontszámot a webes adatok (kulcsszavak) alapján
new_score = await cls.match_expertise_and_score(process_db, web_context, t_score)
p_result = await process_db.execute(profile_stmt, {"fp": fprint, "bio": name + " - " + city})
profile_id = p_result.scalar()
await process_db.commit()
# 3. Futtatjuk a kulcsszó-elemzést
await cls.match_expertise_to_service(process_db, profile_id, web_context)
# 4. Lezárjuk a Staging feladatot
await process_db.execute(text("UPDATE marketplace.service_staging SET status = 'processed' WHERE id = :id"), {"id": s_id})
# 2. Visszaírjuk a Staging táblába, és átadjuk az Auditor-nak (Gamification 2.0: auditor_ready státusz)
upd_query = text("""
UPDATE marketplace.service_staging
SET status = 'auditor_ready', trust_score = :ns
WHERE id = :id
""")
await process_db.execute(upd_query, {"ns": new_score, "id": s_id})
await process_db.commit()
logger.info(f"✅ Dúsítás kész: {name} (Pont: {t_score} -> {new_score}). Átadva az Auditor-nak (auditor_ready).")
except Exception as e:
await process_db.rollback()
logger.error(f"Hiba a dúsítás során ({s_id}): {e}")
logger.error(f"Hiba a dúsítás során ({s_id}): {e}")
await process_db.execute(text("UPDATE marketplace.service_staging SET status = 'error' WHERE id = :id"), {"id": s_id})
await process_db.commit()
else:

View File

@@ -1,3 +1,4 @@
# /opt/docker/dev/service_finder/backend/app/workers/service/service_robot_4_validator_google.py
import asyncio
import httpx
import logging
@@ -7,7 +8,7 @@ import json
from datetime import datetime
from sqlalchemy import text, update, func
from app.database import AsyncSessionLocal
from app.models.service import ServiceProfile
from app.models.marketplace.service import ServiceProfile
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] Robot-4-Validator: %(message)s', stream=sys.stdout)
logger = logging.getLogger("Service-Robot-4-Google-Validator")

View File

@@ -0,0 +1,368 @@
# /opt/docker/dev/service_finder/backend/app/workers/service/service_robot_5_auditor.py
import asyncio
import logging
import json
import random
from datetime import datetime
from sqlalchemy import select, text, update, insert
from sqlalchemy.dialects.postgresql import insert as pg_insert
from app.database import AsyncSessionLocal
# MB 2.0: Közvetlen és teljes importok a hiánytalan működéshez
from app.models.marketplace.service import ServiceStaging, ServiceProfile, ExpertiseTag, ServiceExpertise
from app.models.marketplace.organization import Organization
from app.models.identity.identity import User, Person
from app.models.gamification.gamification import UserContribution, PointsLedger, UserStats
from app.models.system.system import SystemParameter
from app.core.config import settings
# --- NAPLÓZÁS KONFIGURÁCIÓ ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] Service-Robot-5-Auditor: %(message)s')
logger = logging.getLogger("Service-Robot-5-Auditor")
class ServiceAuditor:
"""
Service Robot 5: Auditor és Publikáló (Staging → Production)
Verzió: 1.3 - Tömörítetlen, teljes adatstruktúra szinkronnal és ADAPTÍV időzítéssel.
"""
@staticmethod
async def get_trust_threshold(db) -> int:
""" Lekéri a SystemParameter-ből a trust score küszöböt a validáláshoz. """
try:
query = select(SystemParameter).where(
SystemParameter.key == "service_trust_threshold"
)
result = await db.execute(query)
param = result.scalar_one_or_none()
if param and param.value:
logger.info(f"🔍 Rendszer trust küszöb értéke: {param.value}")
return int(param.value)
except Exception as e:
logger.warning(f"⚠️ Trust threshold lekérdezés hiba, alapértelmezett 70 használata: {e}")
return 70
@staticmethod
async def create_digital_twin(db, staging_data: dict) -> int:
"""
Létrehoz vagy megkeres egy Digital Twin (Organization) entitást.
A 'fleet' sémában lévő organizations táblát kezeli.
"""
try:
tax_no = staging_data.get("tax_number")
org_name = staging_data.get("name", "").strip()
existing_org = None
# 1. Ellenőrzés adószám alapján (ha rendelkezésre áll)
if tax_no:
logger.info(f"🔎 Digital Twin keresése adószám alapján: {tax_no}")
tax_query = select(Organization).where(
Organization.tax_number == tax_no.strip(),
Organization.is_deleted == False
)
tax_result = await db.execute(tax_query)
existing_org = tax_result.scalar_one_or_none()
# 2. Ellenőrzés név alapján (ha az adószám nem talált egyezést)
if not existing_org and org_name:
logger.info(f"🔎 Digital Twin keresése név alapján: {org_name}")
org_query = select(Organization).where(
Organization.name == org_name,
Organization.is_deleted == False
)
name_result = await db.execute(org_query)
existing_org = name_result.scalar_one_or_none()
if existing_org:
logger.info(f"✅ Meglévő Digital Twin azonosítva: {existing_org.name} (ID: {existing_org.id})")
return existing_org.id
# 3. Új Organization (Digital Twin) létrehozása
new_org = Organization(
name=org_name,
full_name=staging_data.get("full_name") or org_name,
tax_number=tax_no,
reg_number=staging_data.get("registration_number"),
contact_email=staging_data.get("contact_email"),
contact_phone=staging_data.get("contact_phone"),
website=staging_data.get("website"),
address_zip=staging_data.get("postal_code"),
address_city=staging_data.get("city"),
address_street_name=staging_data.get("address_line1"),
country_code=staging_data.get("country_code", "HU"),
is_active=True,
status="active",
created_at=datetime.utcnow(),
updated_at=datetime.utcnow()
)
db.add(new_org)
await db.flush()
await db.refresh(new_org)
logger.info(f"✨ Új Digital Twin (Organization) létrehozva: {new_org.name}")
return new_org.id
except Exception as e:
logger.error(f"❌ Digital Twin hiba: {e}")
raise
@staticmethod
async def create_service_profile(db, staging_data: dict, org_id: int) -> int:
""" Létrehozza az éles ServiceProfile rekordot a marketplace sémában. """
try:
new_service = ServiceProfile(
organization_id=org_id,
name=staging_data.get("name", "").strip(),
description=staging_data.get("description") or "",
contact_email=staging_data.get("contact_email"),
contact_phone=staging_data.get("contact_phone"),
website=staging_data.get("website"),
address_line1=staging_data.get("address_line1"),
address_line2=staging_data.get("address_line2"),
city=staging_data.get("city"),
postal_code=staging_data.get("postal_code"),
country_code=staging_data.get("country_code", "HU"),
latitude=staging_data.get("latitude"),
longitude=staging_data.get("longitude"),
trust_score=staging_data.get("trust_score", 0),
status="active",
external_id=staging_data.get("external_id"),
metadata=staging_data.get("metadata") or {},
created_at=datetime.utcnow(),
updated_at=datetime.utcnow()
)
db.add(new_service)
await db.flush()
await db.refresh(new_service)
logger.info(f"✅ Éles ServiceProfile rögzítve: {new_service.name} (ID: {new_service.id})")
return new_service.id
except Exception as e:
logger.error(f"❌ ServiceProfile rögzítési hiba: {e}")
raise
@staticmethod
async def award_user_contribution(db, user_id: int, service_id: int, staging_id: int):
""" XP és pontok kiosztása a felhasználónak. """
try:
# 1. Aktuális aktív szezon keresése
season_query = text("""
SELECT id FROM system.seasons
WHERE is_active = true
AND start_date <= CURRENT_DATE
AND end_date >= CURRENT_DATE
LIMIT 1
""")
result = await db.execute(season_query)
season_row = result.fetchone()
season_id = season_row[0] if season_row else None
# 2. UserContribution rekord létrehozása
contribution = UserContribution(
user_id=user_id,
season_id=season_id,
contribution_type="service_submission",
entity_type="service",
entity_id=service_id,
points_awarded=50,
xp_awarded=100,
status="approved",
metadata={
"staging_id": staging_id,
"awarded_at": datetime.utcnow().isoformat(),
"reason": "Auditor publication approval"
},
created_at=datetime.utcnow()
)
db.add(contribution)
# 3. UserStats (globális statisztika) frissítése
stats_query = select(UserStats).where(UserStats.user_id == user_id)
stats_result = await db.execute(stats_query)
user_stats = stats_result.scalar_one_or_none()
if user_stats:
user_stats.total_points += 50
user_stats.total_xp += 100
user_stats.services_submitted += 1
user_stats.updated_at = datetime.utcnow()
else:
new_stats = UserStats(
user_id=user_id, total_points=50, total_xp=100,
services_submitted=1, created_at=datetime.utcnow()
)
db.add(new_stats)
# 4. PointsLedger bejegyzés
ledger = PointsLedger(
user_id=user_id, points=50, xp=100,
source_type="service_submission",
source_id=service_id,
description="Reward for verified service publication",
created_at=datetime.utcnow()
)
db.add(ledger)
logger.info(f"🏆 Jutalmazás elvégezve: User {user_id} (+50 PT, +100 XP)")
except Exception as e:
logger.error(f"⚠️ Hiba a jutalmazási folyamatban: {e}")
@classmethod
async def process_staging_record(cls, db, staging_id: int):
""" Egyetlen staging rekord teljes körű feldolgozása tranzakcióban. """
try:
# 1. Rekord lekérése
query = select(ServiceStaging).where(
ServiceStaging.id == staging_id,
ServiceStaging.status == 'auditing'
)
result = await db.execute(query)
staging = result.scalar_one_or_none()
if not staging:
logger.error(f"❌ Staging rekord nem található vagy rossz státuszban van: {staging_id}")
return False
# 2. Trust Score ellenőrzés
trust_threshold = await cls.get_trust_threshold(db)
if staging.trust_score < trust_threshold:
logger.warning(f"🚫 Trust Score elégtelen: {staging.trust_score} < {trust_threshold}")
staging.status = 'rejected'
staging.rejection_reason = f'Low trust score ({staging.trust_score})'
staging.updated_at = datetime.utcnow()
await db.commit()
return False
# 3. Adatok kigyűjtése explicit módon
staging_data = {
"name": staging.name,
"description": staging.description,
"contact_email": staging.contact_email,
"contact_phone": staging.contact_phone,
"website": staging.website,
"address_line1": staging.address_line1,
"address_line2": staging.address_line2,
"city": staging.city,
"postal_code": staging.postal_code,
"country_code": staging.country_code,
"latitude": staging.latitude,
"longitude": staging.longitude,
"trust_score": staging.trust_score,
"external_id": staging.external_id,
"metadata": staging.metadata or {},
"tax_number": staging.metadata.get("tax_number") if staging.metadata else None,
"registration_number": staging.metadata.get("registration_number") if staging.metadata else None
}
# 4. Digital Twin (Cég) fázis
org_id = await cls.create_digital_twin(db, staging_data)
# 5. Production (Szolgáltatás) fázis
service_id = await cls.create_service_profile(db, staging_data, org_id)
# 6. Gamification fázis
if staging.submitted_by:
await cls.award_user_contribution(db, staging.submitted_by, service_id, staging_id)
# 7. Lezárás és Audit Trail mentése
staging.status = 'published'
staging.published_at = datetime.utcnow()
staging.service_profile_id = service_id
staging.organization_id = org_id
staging.updated_at = datetime.utcnow()
staging.audit_trail = {
"audited_by": "robot_5",
"audited_at": datetime.utcnow().isoformat(),
"trust_threshold_used": trust_threshold,
"final_trust_score": staging.trust_score,
"organization_id": org_id,
"service_profile_id": service_id,
"version": "1.3"
}
await db.commit()
logger.info(f"✅ SIKER: Staging {staging_id} -> Production {service_id}")
return True
except Exception as e:
logger.error(f"❌ Kritikus feldolgozási hiba (Staging ID: {staging_id}): {e}")
await db.rollback()
return False
@classmethod
async def run_worker(cls):
"""
Az Auditor fő folyamata:
Adaptív ciklus: 20mp ha van adat, 5 perc ha 5x üres.
"""
logger.info("🚀 Service Auditor v1.3 ONLINE - Adaptív üzemmód")
empty_counter = 0
while True:
try:
async with AsyncSessionLocal() as db:
# 1. Következő rekord lefoglalása atomi módon
query = text("""
UPDATE marketplace.service_staging
SET status = 'auditing'
WHERE id = (
SELECT id FROM marketplace.service_staging
WHERE status = 'auditor_ready'
AND trust_score >= (
SELECT COALESCE(
(SELECT value::integer FROM system.system_parameters
WHERE key = 'service_trust_threshold'),
70
)
)
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING id
""")
result = await db.execute(query)
row = result.fetchone()
if not row:
empty_counter += 1
if empty_counter >= 5:
sleep_time = 600 # 5 perc várakozás
logger.info(f"💤 Nincs adat (5x üres). Lassítás {sleep_time} másodpercre (5 perc)...")
else:
sleep_time = 20 # 20 másodperc várakozás
logger.info(f"⏳ Várólista üres, következő próba {sleep_time} mp múlva. (Próba: {empty_counter}/5)")
await db.commit()
await asyncio.sleep(sleep_time)
continue
# Ha találtunk adatot:
empty_counter = 0
staging_id = row[0]
await db.commit() # Elengedjük a zárolást a hosszas feldolgozáshoz
logger.info(f"🎯 Auditor feldolgozás indítása: staging_id={staging_id}")
# 2. Rekord tényleges feldolgozása egy friss session-ben
async with AsyncSessionLocal() as process_db:
await cls.process_staging_record(process_db, staging_id)
# 3. Sikeres feldolgozás utáni pihenő az utasítás szerint (20 mp)
await asyncio.sleep(20)
except Exception as e:
logger.error(f"❌ Auditor fő ciklus hiba: {e}")
await asyncio.sleep(10)
async def main():
""" Belépési pont a konténer számára. """
auditor = ServiceAuditor()
await auditor.run_worker()
if __name__ == "__main__":
asyncio.run(main())