Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/backend/app/routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .categories import bp as categories_bp
from .docs import bp as docs_bp
from .dashboard import bp as dashboard_bp
from .integrity import bp as integrity_bp


def register_routes(app: Flask):
Expand All @@ -18,3 +19,4 @@ def register_routes(app: Flask):
app.register_blueprint(categories_bp, url_prefix="/categories")
app.register_blueprint(docs_bp, url_prefix="/docs")
app.register_blueprint(dashboard_bp, url_prefix="/dashboard")
app.register_blueprint(integrity_bp, url_prefix="/integrity")
81 changes: 81 additions & 0 deletions packages/backend/app/routes/integrity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""
Financial Data Integrity & Reconciliation routes (Issue #96).

Endpoints:
GET /integrity/check → full integrity report
GET /integrity/check/summary → lightweight status + counts only
"""

from __future__ import annotations

import logging

from flask import Blueprint, jsonify, request
from flask_jwt_extended import get_jwt_identity, jwt_required

from ..services.integrity import run_integrity_check

bp = Blueprint("integrity", __name__)
logger = logging.getLogger("finmind.integrity")


@bp.get("/check")
@jwt_required()
def integrity_check():
"""
Run a full financial data integrity check for the authenticated user.

Query params:
months (int, 1-12, default 3) — look-back window for balance mismatch check

Response keys:
overall_status, total_issues, balance_mismatches, orphaned_expenses,
orphaned_recurring, future_dated_expenses, duplicate_expenses,
negative_amounts, stale_recurring, bills_missing_reminders,
analysis_months, generated_at
"""
uid = int(get_jwt_identity())
months_raw = request.args.get("months", "3")
try:
months = int(months_raw)
if not (1 <= months <= 12):
raise ValueError
except (ValueError, TypeError):
return jsonify(error="months must be an integer between 1 and 12"), 400

report = run_integrity_check(uid, months=months)
return jsonify(report)


@bp.get("/check/summary")
@jwt_required()
def integrity_summary():
"""
Return a lightweight integrity summary: overall status and per-check counts.
Faster than the full report — useful for dashboard badges.
"""
uid = int(get_jwt_identity())
months_raw = request.args.get("months", "3")
try:
months = int(months_raw)
if not (1 <= months <= 12):
raise ValueError
except (ValueError, TypeError):
return jsonify(error="months must be an integer between 1 and 12"), 400

full = run_integrity_check(uid, months=months)
return jsonify({
"overall_status": full["overall_status"],
"total_issues": full["total_issues"],
"counts": {
"balance_mismatches": len(full["balance_mismatches"]),
"orphaned_expenses": len(full["orphaned_expenses"]),
"orphaned_recurring": len(full["orphaned_recurring"]),
"future_dated_expenses": len(full["future_dated_expenses"]),
"duplicate_expenses": len(full["duplicate_expenses"]),
"negative_amounts": len(full["negative_amounts"]),
"stale_recurring": len(full["stale_recurring"]),
"bills_missing_reminders": len(full["bills_missing_reminders"]),
},
"generated_at": full["generated_at"],
})
Loading