123 lines
4.2 KiB
Python
123 lines
4.2 KiB
Python
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, _visible_clients
|
|
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
|
|
_clients, scope = _visible_clients(db, request, user)
|
|
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
|
|
_clients, scope = _visible_clients(db, request, user)
|
|
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
|
|
_clients, scope = _visible_clients(db, request, user)
|
|
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
|
|
_clients, scope = _visible_clients(db, request, user)
|
|
deactivate_models(db, tenant_id=scope.tenant_id)
|
|
return _redirect(message="All internal accounting models returned to shadow mode.")
|
|
finally:
|
|
db.close()
|