dikasterion/backend/app/routers/judges.py

205 lines
6.6 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_, func
from sqlalchemy.orm import selectinload
from datetime import datetime, timedelta
from app.database import get_db
from app.models import User, Case, JudgeAssignment
from app.schemas import JudgeVote, JudgeAcceptance
from app.routers.auth import get_current_active_user
from app.utils.notifications import notify_user
router = APIRouter()
@router.get("/pending")
async def get_pending_assignments(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Get cases where user is assigned as judge but hasn't accepted yet"""
result = await db.execute(
select(JudgeAssignment, Case).join(Case).where(
and_(
JudgeAssignment.judge_id == current_user.id,
JudgeAssignment.status == "pending"
)
)
)
assignments = result.all()
return [
{
"assignment_id": ja.id,
"case_id": c.id,
"case_number": c.case_number,
"title": c.title,
"assigned_at": ja.assigned_at
}
for ja, c in assignments
]
@router.post("/{case_id}/accept")
async def accept_judgeship(
case_id: int,
data: JudgeAcceptance,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
result = await db.execute(
select(JudgeAssignment).where(
and_(
JudgeAssignment.case_id == case_id,
JudgeAssignment.judge_id == current_user.id
)
)
)
assignment = result.scalar_one_or_none()
if not assignment:
raise HTTPException(status_code=404, detail="Assignment not found")
if assignment.status != "pending":
raise HTTPException(status_code=400, detail="Already accepted or declined")
if data.accept:
assignment.status = "accepted"
await db.commit()
return {"status": "accepted", "case_id": case_id}
else:
assignment.status = "declined"
await db.commit()
# Find replacement judge
result = await db.execute(select(Case).where(Case.id == case_id))
case = result.scalar_one()
result = await db.execute(
select(User).where(
and_(
User.is_judge == True,
User.is_active == True,
User.id.notin_([
case.plaintiff_id,
case.defendant_id,
current_user.id
])
)
)
)
available = result.scalars().all()
if available:
import random
new_judge = random.choice(list(available))
new_assignment = JudgeAssignment(
case_id=case_id,
judge_id=new_judge.id,
status="pending"
)
db.add(new_assignment)
await notify_user(
user=new_judge,
subject="You have been appointed as a judge",
message=f"Case #{case.case_number}: Replacement needed"
)
await db.commit()
return {"status": "declined", "case_id": case_id, "replacement_found": bool(available)}
@router.post("/{case_id}/vote")
async def submit_vote(
case_id: int,
vote: JudgeVote,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
result = await db.execute(
select(JudgeAssignment).where(
and_(
JudgeAssignment.case_id == case_id,
JudgeAssignment.judge_id == current_user.id
)
)
)
assignment = result.scalar_one_or_none()
if not assignment:
raise HTTPException(status_code=404, detail="You are not assigned to this case")
if assignment.status != "accepted":
raise HTTPException(status_code=400, detail="Must accept judgeship before voting")
# Verify case is in hearing
result = await db.execute(select(Case).where(Case.id == case_id))
case = result.scalar_one()
if case.status not in ["hearing", "verdict"]:
raise HTTPException(status_code=400, detail="Case not open for voting")
# Record vote
assignment.vote = vote.vote
assignment.reasoning = vote.reasoning
assignment.voted_at = datetime.utcnow()
assignment.status = "voted"
# Check if verdict should be rendered
result = await db.execute(
select(JudgeAssignment).where(
and_(
JudgeAssignment.case_id == case_id,
JudgeAssignment.status == "voted"
)
)
)
voted_assignments = result.scalars().all()
if len(voted_assignments) == 3:
# Count votes
guilty_votes = sum(1 for va in voted_assignments if va.vote == "guilty")
innocent_votes = sum(1 for va in voted_assignments if va.vote == "innocent")
if guilty_votes >= 2:
case.verdict = "guilty"
elif innocent_votes >= 2:
case.verdict = "innocent"
else:
case.verdict = "dismissed" # All abstained or tied
case.status = "closed"
case.verdict_deadline = datetime.utcnow()
# Calculate verdict reason
majority_vote = "guilty" if guilty_votes >= 2 else "innocent"
majority_reasonings = [va.reasoning for va in voted_assignments if va.vote == majority_vote]
case.verdict_reason = " | ".join(majority_reasonings)
# Update reputations
if case.verdict == "guilty":
case.defendant.reputation_score = max(0, case.defendant.reputation_score - 10)
elif case.verdict == "innocent":
case.plaintiff.reputation_score = max(0, case.plaintiff.reputation_score - 5)
# Notify parties
background_tasks.add_task(
notify_user,
user=case.plaintiff,
subject=f"Verdict reached: {case.case_number}",
message=f"The verdict is: {case.verdict.upper()}"
)
background_tasks.add_task(
notify_user,
user=case.defendant,
subject=f"Verdict reached: {case.case_number}",
message=f"The verdict is: {case.verdict.upper()}"
)
await db.commit()
return {
"vote_recorded": True,
"vote": vote.vote,
"case_status": case.status,
"verdict": case.verdict
}