from __future__ import annotations from fastapi import APIRouter, Form, Request from fastapi.responses import RedirectResponse from app.core.db.common import CommonSessionLocal from app.core.security.csrf import get_or_create_csrf_token, validate_csrf from app.core.templating import templates from app.modules.accounting.internal_model_service import ( activate_model, deactivate_models, model_dashboard, recent_predictions, train_model, ) from app.modules.accounting.ui import _require_partner from app.modules.core.rbac.deps import get_user_permissions, get_user_roles router = APIRouter(prefix="/tools/accounting/internal-model", tags=["accounting-internal-model-ui"]) def _redirect(message="", error=""): from urllib.parse import urlencode q = {} if message: q["message"] = message[:300] if error: q["error"] = error[:300] return RedirectResponse( "/tools/accounting/internal-model" + ("?" + urlencode(q) if q else ""), status_code=303, ) @router.get("") def page(request: Request, message: str = "", error: str = ""): db = CommonSessionLocal() try: user, denied = _require_partner(request, db, "accounting.learning.view") if denied: return denied scope = request.state.workspace_scope summary = model_dashboard(db, tenant_id=scope.tenant_id) predictions = recent_predictions(db, tenant_id=scope.tenant_id, limit=100) return templates.TemplateResponse( "modules/accounting/templates/accounting/internal_model.html", { "request": request, "current_user": user, "current_user_roles": get_user_roles(db, user.id), "current_user_permissions": get_user_permissions(db, user.id), "csrf_token": get_or_create_csrf_token(request), "title": "Internal Accounting Model", "summary": summary, "predictions": predictions, "message": message, "error": error, }, ) finally: db.close() @router.post("/train") def train(request: Request, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, denied = _require_partner(request, db, "accounting.learning.manage") if denied: return denied scope = request.state.workspace_scope model = train_model(db, tenant_id=scope.tenant_id, user_id=user.id) return _redirect( message=( f"Internal model {model.version_label} trained. " f"Validation accuracy {model.validation_accuracy * 100:.1f}%." ) ) except Exception as exc: db.rollback() return _redirect(error=str(exc)) finally: db.close() @router.post("/{model_id}/activate") def activate(request: Request, model_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, denied = _require_partner(request, db, "accounting.learning.manage") if denied: return denied scope = request.state.workspace_scope model = activate_model(db, tenant_id=scope.tenant_id, model_id=model_id) return _redirect( message=( f"{model.version_label} marked active. " "It still remains shadow-only unless ACCOUNTING_INTERNAL_MODEL_ENABLED=true on the server." ) ) except Exception as exc: db.rollback() return _redirect(error=str(exc)) finally: db.close() @router.post("/deactivate") def deactivate(request: Request, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, denied = _require_partner(request, db, "accounting.learning.manage") if denied: return denied scope = request.state.workspace_scope deactivate_models(db, tenant_id=scope.tenant_id) return _redirect(message="All internal accounting models returned to shadow mode.") finally: db.close()