90 lines
3.0 KiB
Python
90 lines
3.0 KiB
Python
|
|
"""
|
||
|
|
Dispute Scheduler for automatic dispute resolution after 24 hours.
|
||
|
|
"""
|
||
|
|
import asyncio
|
||
|
|
from datetime import datetime, timedelta
|
||
|
|
from sqlalchemy import select
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
from sqlalchemy.orm import selectinload
|
||
|
|
|
||
|
|
from app.models import Dispute, DisputeStatus, Assignment, AssignmentStatus
|
||
|
|
from app.services.disputes import dispute_service
|
||
|
|
|
||
|
|
|
||
|
|
# Configuration
|
||
|
|
CHECK_INTERVAL_SECONDS = 300 # Check every 5 minutes
|
||
|
|
DISPUTE_WINDOW_HOURS = 24 # Disputes auto-resolve after 24 hours
|
||
|
|
|
||
|
|
|
||
|
|
class DisputeScheduler:
|
||
|
|
"""Background scheduler for automatic dispute resolution."""
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
self._running = False
|
||
|
|
self._task: asyncio.Task | None = None
|
||
|
|
|
||
|
|
async def start(self, session_factory) -> None:
|
||
|
|
"""Start the scheduler background task."""
|
||
|
|
if self._running:
|
||
|
|
return
|
||
|
|
|
||
|
|
self._running = True
|
||
|
|
self._task = asyncio.create_task(self._run_loop(session_factory))
|
||
|
|
print("[DisputeScheduler] Started")
|
||
|
|
|
||
|
|
async def stop(self) -> None:
|
||
|
|
"""Stop the scheduler."""
|
||
|
|
self._running = False
|
||
|
|
if self._task:
|
||
|
|
self._task.cancel()
|
||
|
|
try:
|
||
|
|
await self._task
|
||
|
|
except asyncio.CancelledError:
|
||
|
|
pass
|
||
|
|
print("[DisputeScheduler] Stopped")
|
||
|
|
|
||
|
|
async def _run_loop(self, session_factory) -> None:
|
||
|
|
"""Main scheduler loop."""
|
||
|
|
while self._running:
|
||
|
|
try:
|
||
|
|
async with session_factory() as db:
|
||
|
|
await self._process_expired_disputes(db)
|
||
|
|
except Exception as e:
|
||
|
|
print(f"[DisputeScheduler] Error in loop: {e}")
|
||
|
|
|
||
|
|
await asyncio.sleep(CHECK_INTERVAL_SECONDS)
|
||
|
|
|
||
|
|
async def _process_expired_disputes(self, db: AsyncSession) -> None:
|
||
|
|
"""Process and resolve expired disputes."""
|
||
|
|
cutoff_time = datetime.utcnow() - timedelta(hours=DISPUTE_WINDOW_HOURS)
|
||
|
|
|
||
|
|
# Find all open disputes that have expired
|
||
|
|
result = await db.execute(
|
||
|
|
select(Dispute)
|
||
|
|
.options(
|
||
|
|
selectinload(Dispute.votes),
|
||
|
|
selectinload(Dispute.assignment).selectinload(Assignment.participant),
|
||
|
|
)
|
||
|
|
.where(
|
||
|
|
Dispute.status == DisputeStatus.OPEN.value,
|
||
|
|
Dispute.created_at < cutoff_time,
|
||
|
|
)
|
||
|
|
)
|
||
|
|
expired_disputes = result.scalars().all()
|
||
|
|
|
||
|
|
for dispute in expired_disputes:
|
||
|
|
try:
|
||
|
|
result_status, votes_valid, votes_invalid = await dispute_service.resolve_dispute(
|
||
|
|
db, dispute.id
|
||
|
|
)
|
||
|
|
print(
|
||
|
|
f"[DisputeScheduler] Auto-resolved dispute {dispute.id}: "
|
||
|
|
f"{result_status} (valid: {votes_valid}, invalid: {votes_invalid})"
|
||
|
|
)
|
||
|
|
except Exception as e:
|
||
|
|
print(f"[DisputeScheduler] Failed to resolve dispute {dispute.id}: {e}")
|
||
|
|
|
||
|
|
|
||
|
|
# Global scheduler instance
|
||
|
|
dispute_scheduler = DisputeScheduler()
|