- Change file mode from 644 to 755 for all project files - Add chain_id parameter to get_balance RPC endpoint with default "ait-devnet" - Rename Miner.extra_meta_data to extra_metadata for consistency
82 lines
2.5 KiB
Python
Executable File
82 lines
2.5 KiB
Python
Executable File
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
import json
|
|
from aitbc.logging import get_logger
|
|
from typing import Iterable, List, Optional
|
|
from uuid import UUID
|
|
|
|
from redis.asyncio import Redis
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from ..models import Feedback
|
|
from ..storage.redis_keys import RedisKeys
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class FeedbackRepository:
|
|
"""Persists coordinator feedback and emits Redis notifications."""
|
|
|
|
def __init__(self, session: AsyncSession, redis: Redis) -> None:
|
|
self._session = session
|
|
self._redis = redis
|
|
|
|
async def add_feedback(
|
|
self,
|
|
*,
|
|
job_id: str,
|
|
miner_id: str,
|
|
outcome: str,
|
|
latency_ms: Optional[int] = None,
|
|
fail_code: Optional[str] = None,
|
|
tokens_spent: Optional[float] = None,
|
|
) -> Feedback:
|
|
feedback = Feedback(
|
|
job_id=job_id,
|
|
miner_id=miner_id,
|
|
outcome=outcome,
|
|
latency_ms=latency_ms,
|
|
fail_code=fail_code,
|
|
tokens_spent=tokens_spent,
|
|
created_at=dt.datetime.utcnow(),
|
|
)
|
|
self._session.add(feedback)
|
|
await self._session.flush()
|
|
|
|
payload = {
|
|
"job_id": job_id,
|
|
"miner_id": miner_id,
|
|
"outcome": outcome,
|
|
"latency_ms": latency_ms,
|
|
"fail_code": fail_code,
|
|
"tokens_spent": tokens_spent,
|
|
"created_at": feedback.created_at.isoformat() if feedback.created_at else None,
|
|
}
|
|
try:
|
|
await self._redis.publish(RedisKeys.feedback_channel(), json.dumps(payload))
|
|
except Exception as exc: # pragma: no cover - defensive
|
|
logger.warning("Failed to publish feedback event for job %s: %s", job_id, exc)
|
|
return feedback
|
|
|
|
async def list_feedback_for_miner(self, miner_id: str, limit: int = 50) -> List[Feedback]:
|
|
stmt = (
|
|
select(Feedback)
|
|
.where(Feedback.miner_id == miner_id)
|
|
.order_by(Feedback.created_at.desc())
|
|
.limit(limit)
|
|
)
|
|
result = await self._session.execute(stmt)
|
|
return list(result.scalars().all())
|
|
|
|
async def list_feedback_for_job(self, job_id: str, limit: int = 50) -> List[Feedback]:
|
|
stmt = (
|
|
select(Feedback)
|
|
.where(Feedback.job_id == job_id)
|
|
.order_by(Feedback.created_at.desc())
|
|
.limit(limit)
|
|
)
|
|
result = await self._session.execute(stmt)
|
|
return list(result.scalars().all())
|