NeighbourAid / app /services /verification.py
Parth Kansal
commit
49e9f9d
"""Multi-source verification scoring.
An alert's `verified_score` (0-100) combines independent signals:
β€’ witnesses β€” distinct users who say they also see the incident
β€’ corroboration β€” other alerts of the same category posted nearby recently
β€’ weather_match β€” external weather data consistent with the alert category
"""
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from bson import ObjectId
CORROBORATE_RADIUS_M = 500
CORROBORATE_WINDOW_MIN = 30
WITNESS_RADIUS_M = 2000 # users within 2 km can add a witness vote
def compute_verified_score(
witnesses: int,
corroborating_alerts: int,
weather_match: bool,
) -> int:
"""Composite 0-100 score. Each independent source adds capped weight."""
score = 0
score += min(40, witnesses * 8) # up to 40 pts from community witnesses
score += min(40, corroborating_alerts * 15) # up to 40 pts from nearby same-category alerts
if weather_match:
score += 20 # 20 pts from external weather confirmation
return min(100, score)
async def find_corroborating_alerts(db, category: str, coordinates: list[float]):
"""Return open alerts of the same category within the corroboration
radius/window β€” excluding resolved ones. Caller filters out the alert
being scored if needed."""
since = datetime.now(timezone.utc) - timedelta(minutes=CORROBORATE_WINDOW_MIN)
cursor = db.alerts.find(
{
"category": category,
"status": {"$ne": "resolved"},
"created_at": {"$gte": since},
"location": {
"$nearSphere": {
"$geometry": {"type": "Point", "coordinates": coordinates},
"$maxDistance": CORROBORATE_RADIUS_M,
}
},
}
)
return [doc async for doc in cursor]
async def bump_witness(db, alert_id: ObjectId, user_id: str) -> dict | None:
"""Idempotently add a witness β€” one user can only confirm once."""
return await db.alerts.find_one_and_update(
{"_id": alert_id, "witnessed_by": {"$ne": user_id}},
{
"$addToSet": {"witnessed_by": user_id},
"$inc": {"witnesses": 1},
},
return_document=True,
)