Add Phase 6 self improving ledger selection engine
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from urllib.parse import urlencode
|
||||
|
||||
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.historical_learning_service import active_natures, ledger_mappings
|
||||
from app.modules.accounting.ledger_learning_service import (
|
||||
available_tally_guids,
|
||||
learning_summary,
|
||||
rank_suggestions,
|
||||
recent_events,
|
||||
record_review,
|
||||
)
|
||||
from app.modules.accounting.ui import _find_visible_client, _require_partner, _visible_clients
|
||||
from app.modules.clients.business_profile_service import get_profile
|
||||
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
|
||||
|
||||
router = APIRouter(prefix="/tools/accounting/ledger-learning", tags=["accounting-ledger-learning-ui"])
|
||||
|
||||
|
||||
def _float_or_none(value):
|
||||
try:
|
||||
return float(value) if str(value or "").strip() else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
@router.get("")
|
||||
def ledger_learning(
|
||||
request: Request,
|
||||
client_id: int | None = None,
|
||||
tally_guid: str = "",
|
||||
supplier_name: str = "",
|
||||
supplier_gstin: str = "",
|
||||
hsn_code: str = "",
|
||||
description: str = "",
|
||||
amount: str = "",
|
||||
reviewed: int = 0,
|
||||
error: str = "",
|
||||
):
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user, response = _require_partner(request, db, "accounting.learning.view")
|
||||
if response:
|
||||
return response
|
||||
|
||||
clients, scope = _visible_clients(db, request, user)
|
||||
selected = next((c for c in clients if client_id and int(c.id) == int(client_id)), None)
|
||||
suggestions = []
|
||||
mappings = []
|
||||
tally_companies = []
|
||||
profile = None
|
||||
summary = []
|
||||
events = []
|
||||
|
||||
if selected:
|
||||
profile = get_profile(db, selected.id)
|
||||
mappings = ledger_mappings(db, scope.tenant_id, selected.id, tally_guid)
|
||||
tally_companies = available_tally_guids(db, scope.tenant_id, selected.id)
|
||||
summary = learning_summary(db, scope.tenant_id, selected.id)
|
||||
events = recent_events(db, scope.tenant_id, selected.id, 20)
|
||||
if any([supplier_name.strip(), supplier_gstin.strip(), hsn_code.strip(), description.strip()]):
|
||||
suggestions = rank_suggestions(
|
||||
db,
|
||||
tenant_id=scope.tenant_id,
|
||||
client_id=selected.id,
|
||||
tally_guid=tally_guid.strip(),
|
||||
supplier_name=supplier_name,
|
||||
supplier_gstin=supplier_gstin,
|
||||
hsn_code=hsn_code,
|
||||
description=description,
|
||||
amount=_float_or_none(amount),
|
||||
)
|
||||
|
||||
return templates.TemplateResponse("modules/accounting/templates/accounting/ledger_learning.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": "Self-improving Ledger Selection",
|
||||
"clients": clients,
|
||||
"selected_client": selected,
|
||||
"profile": profile,
|
||||
"tally_companies": tally_companies,
|
||||
"mappings": mappings,
|
||||
"natures": active_natures(db, scope.tenant_id),
|
||||
"suggestions": suggestions,
|
||||
"learning_summary": summary,
|
||||
"events": events,
|
||||
"query": {
|
||||
"tally_guid": tally_guid,
|
||||
"supplier_name": supplier_name,
|
||||
"supplier_gstin": supplier_gstin,
|
||||
"hsn_code": hsn_code,
|
||||
"description": description,
|
||||
"amount": amount,
|
||||
},
|
||||
"reviewed": bool(reviewed),
|
||||
"error": error,
|
||||
})
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/review")
|
||||
def review(
|
||||
request: Request,
|
||||
client_id: int = Form(...),
|
||||
tally_guid: str = Form(""),
|
||||
supplier_name: str = Form(""),
|
||||
supplier_gstin: str = Form(""),
|
||||
hsn_code: str = Form(""),
|
||||
description: str = Form(""),
|
||||
amount: str = Form(""),
|
||||
suggested_nature_id: str = Form(""),
|
||||
suggested_ledger_name: str = Form(""),
|
||||
suggested_confidence: int = Form(0),
|
||||
final_nature_id: int = Form(...),
|
||||
final_ledger_name: str = Form(""),
|
||||
explanation: str = Form(""),
|
||||
csrf_token: str = Form(...),
|
||||
):
|
||||
validate_csrf(request, csrf_token)
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user, response = _require_partner(request, db, "accounting.learning.manage")
|
||||
if response:
|
||||
return response
|
||||
client, _, scope = _find_visible_client(db, request, user, client_id)
|
||||
if not client:
|
||||
from app.core.http_responses import ui_access_denied
|
||||
return ui_access_denied()
|
||||
|
||||
suggested_id = int(suggested_nature_id) if str(suggested_nature_id or "").isdigit() else None
|
||||
explanations = [x.strip() for x in str(explanation or "").split("||") if x.strip()]
|
||||
record_review(
|
||||
db,
|
||||
tenant_id=scope.tenant_id,
|
||||
client_id=client.id,
|
||||
tally_guid=tally_guid.strip(),
|
||||
supplier_name=supplier_name,
|
||||
supplier_gstin=supplier_gstin,
|
||||
hsn_code=hsn_code,
|
||||
description=description,
|
||||
amount=_float_or_none(amount),
|
||||
suggested_nature_id=suggested_id,
|
||||
suggested_ledger_name=suggested_ledger_name,
|
||||
suggested_confidence=suggested_confidence,
|
||||
final_nature_id=final_nature_id,
|
||||
final_ledger_name=final_ledger_name,
|
||||
user_id=user.id,
|
||||
explanation=explanations,
|
||||
)
|
||||
|
||||
params = {
|
||||
"client_id": client_id,
|
||||
"tally_guid": tally_guid,
|
||||
"supplier_name": supplier_name,
|
||||
"supplier_gstin": supplier_gstin,
|
||||
"hsn_code": hsn_code,
|
||||
"description": description,
|
||||
"amount": amount,
|
||||
"reviewed": 1,
|
||||
}
|
||||
return RedirectResponse(url="/tools/accounting/ledger-learning?" + urlencode(params), status_code=303)
|
||||
except Exception as exc:
|
||||
db.rollback()
|
||||
params = {"client_id": client_id, "error": str(exc)[:120]}
|
||||
return RedirectResponse(url="/tools/accounting/ledger-learning?" + urlencode(params), status_code=303)
|
||||
finally:
|
||||
db.close()
|
||||
Reference in New Issue
Block a user