From 5e30020d60c690bf09d43522f41287ec2364843c Mon Sep 17 00:00:00 2001 From: Repinoid Date: Sat, 31 Oct 2026 10:14:29 +0300 Subject: [PATCH] feat: expose dossier timeline and exceptions API --- site/routes/api_bp.py | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/site/routes/api_bp.py b/site/routes/api_bp.py index c99a21a..68c3415 100644 --- a/site/routes/api_bp.py +++ b/site/routes/api_bp.py @@ -1,6 +1,11 @@ """API blueprint — groups, documents, supplements, sync, cleanup, spec-current, chat.""" +import json +from datetime import date from flask import Blueprint, request, jsonify from db import documents, supplements, spec_current +from db import dossiers as db_dossiers +from db import exceptions as db_exceptions +from db import timeline as db_timeline from db.connection import execute, query, transaction from services.grouping import group_documents, apply_groups from config import LLM_URL, LLM_KEY, LLM_MODEL @@ -9,6 +14,56 @@ import httpx api_bp = Blueprint("api", __name__) +# ── Contract timeline and exceptions ──────────────────────────── + +@api_bp.route("/api/dossiers//timeline") +def api_dossier_timeline(dossier_id): + """Вернуть полную историю и активный срез обязательств dossier.""" + dossier = db_dossiers.get_dossier(dossier_id) + if not dossier: + return jsonify(ok=False, error="dossier not found"), 404 + + as_of = request.args.get("as_of") or date.today().isoformat() + try: + as_of = date.fromisoformat(as_of).isoformat() + except ValueError: + return jsonify(ok=False, error="as_of must be YYYY-MM-DD"), 400 + + history = db_timeline.list_timeline(dossier_id) + active = db_timeline.list_timeline(dossier_id, as_of) + for interval in history + active: + try: + interval["config_params"] = json.loads(interval.get("config_params_json") or "{}") + except (TypeError, json.JSONDecodeError): + interval["config_params"] = {} + return jsonify( + ok=True, + dossier=dossier, + as_of=as_of, + timeline=history, + active=active, + exceptions=db_exceptions.list_exceptions(dossier_id=dossier_id, status="OPEN"), + ) + + +@api_bp.route("/api/exceptions") +def api_exceptions(): + """Вернуть исключения только по явно заданной области batch или dossier.""" + batch_id = request.args.get("batch_id") + dossier_id = request.args.get("dossier_id") + status = request.args.get("status") + if not batch_id and not dossier_id: + return jsonify(ok=False, error="batch_id or dossier_id required"), 400 + if status and status not in {"OPEN", "RESOLVED"}: + return jsonify(ok=False, error="status must be OPEN or RESOLVED"), 400 + return jsonify( + ok=True, + exceptions=db_exceptions.list_exceptions( + batch_id=batch_id, dossier_id=dossier_id, status=status, + ), + ) + + # ── Supplements ────────────────────────────────────────────────── @api_bp.route("/api/supplements")