Files
vector/backend/routers/stats.py
T
root 8b3a2cbf7e B2: tolerant JSON extraction from Claude responses + graceful fallback
_extract_json_payload handles a json/JSON/bare fence, raw JSON and JSON
embedded in prose; any unparseable response or contract violation now
degrades to the deterministic scoring_service instead of raising.
Also guards the response envelope itself (content[0].text).

B3: single home for recommendation scoring

services/recommendation_service.py holds the rules; routers/stats.py and
backend/services/stats_service.py both delegate to it. Unified rules are the
union of the two old copies: same weights/threshold, substring matching
(superset of the old exact match), tolerant key aliases, health_flags rule
kept. Endpoint response contract unchanged.

Plus: Overpass circuit breaker and concurrent zone queries in geo_service -
128 sequential calls per analysis no longer each burn a connect timeout when
the host has no outbound network.

Tests: 152 -> 194 passed.
2026-07-25 13:01:02 +00:00

33 lines
891 B
Python

from fastapi import APIRouter
from backend.database import db
from backend.schemas import DashboardResponse, RecommendationRequest
from services.recommendation_service import score_recommendation
router = APIRouter(prefix='/api/v1/stats', tags=['stats'])
@router.get('/summary', response_model=DashboardResponse)
def stats_summary() -> dict:
return db.stats()
@router.get('/heatmap')
def stats_heatmap() -> dict:
return {'items': db.heatmap()}
@router.post('/recommendation')
def stats_recommendation(payload: RecommendationRequest) -> dict:
result = score_recommendation(
age=payload.age,
elapsed_hours=payload.elapsed_hours,
terrain=payload.terrain_primary,
weather=payload.weather,
health_flags=payload.health_flags,
)
return {
'recommendation': result['recommendation'],
'score': result['score'],
}