From 845784011a86b780f3af0c2cc05d9e22761e3407 Mon Sep 17 00:00:00 2001 From: A R R R Associates Date: Tue, 7 Jul 2026 14:29:47 +0530 Subject: [PATCH] Add AQMM dashboard for assurance engagement quality monitoring --- app/modules/aqmm_dashboard/__init__.py | 0 app/modules/aqmm_dashboard/service.py | 353 ++++++++++++++++++ .../templates/aqmm_dashboard/dashboard.html | 143 +++++++ app/modules/aqmm_dashboard/ui.py | 113 ++++++ app/ui/app.py | 4 +- 5 files changed, 612 insertions(+), 1 deletion(-) create mode 100644 app/modules/aqmm_dashboard/__init__.py create mode 100644 app/modules/aqmm_dashboard/service.py create mode 100644 app/modules/aqmm_dashboard/templates/aqmm_dashboard/dashboard.html create mode 100644 app/modules/aqmm_dashboard/ui.py diff --git a/app/modules/aqmm_dashboard/__init__.py b/app/modules/aqmm_dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/modules/aqmm_dashboard/service.py b/app/modules/aqmm_dashboard/service.py new file mode 100644 index 0000000..ef230d9 --- /dev/null +++ b/app/modules/aqmm_dashboard/service.py @@ -0,0 +1,353 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from sqlalchemy import and_, func, or_, select +from sqlalchemy.orm import joinedload + +from app.modules.clients.models import Client +from app.modules.core.rbac.deps import get_user_permissions, get_user_roles +from app.modules.services.models import ( + ClientServiceSubscription, + ClientServiceTaskInstance, + EngagementClosureChecklist, + ServiceCatalogue, +) + + +ASSURANCE_TYPE = "assurance" +APPROVED_STATUSES = {"approved", "completed", "complete"} +REVIEWED_STATUSES = {"reviewed", "approved", "completed", "complete", "not_required"} +EVIDENCE_OK_STATUSES = {"accepted", "approved", "reviewed", "uploaded", "not_required"} +CLOSURE_CLOSED_STATUSES = {"closed", "completed"} + + +@dataclass(frozen=True) +class AqmmScope: + tenant_id: int | None + branch_id: int | None + own_user_id: int | None + + +def _clean(value: Any) -> str: + return str(value or "").strip() + + +def _active_tenant_id(request, user) -> int | None: + value = ( + request.session.get("active_tenant_id") + or request.session.get("selected_tenant_id") + or request.session.get("tenant_id") + or getattr(user, "tenant_id", None) + ) + return int(value) if value not in (None, "", 0, "0") else None + + +def _active_branch_id(request, user, permissions: set[str]) -> int | None: + value = request.session.get("active_branch_id") + if value in (None, "", 0, "0"): + if "clients.cross_branch" in permissions or "services.cross_branch" in permissions: + return None + branch_id = getattr(user, "branch_id", None) + return int(branch_id) if branch_id not in (None, "", 0, "0") else None + return int(value) + + +def build_scope(db, request, user) -> AqmmScope: + roles = set(get_user_roles(db, user.id)) + permissions = set(get_user_permissions(db, user.id)) + own_user_id = None + if "clients.view.own_only" in permissions and "Firm Admin" not in roles: + own_user_id = int(user.id) + return AqmmScope( + tenant_id=_active_tenant_id(request, user), + branch_id=_active_branch_id(request, user, permissions), + own_user_id=own_user_id, + ) + + +def can_view_aqmm_dashboard(db, user) -> bool: + roles = set(get_user_roles(db, user.id)) + permissions = set(get_user_permissions(db, user.id)) + if roles.intersection({"System Admin", "Firm Admin", "Partner", "Manager", "Branch Manager"}): + return True + return bool(permissions.intersection({"services.view", "clients.view", "audit.view"})) + + +def _subscription_filters(scope: AqmmScope, financial_year: str = "", q: str = ""): + filters = [ClientServiceSubscription.engagement_type == ASSURANCE_TYPE] + if scope.tenant_id: + filters.append(ClientServiceSubscription.tenant_id == scope.tenant_id) + if scope.branch_id: + filters.append(ClientServiceSubscription.branch_id == scope.branch_id) + if financial_year: + filters.append(ClientServiceSubscription.financial_year == financial_year) + if scope.own_user_id: + filters.append( + or_( + ClientServiceSubscription.assigned_partner_user_id == scope.own_user_id, + ClientServiceSubscription.assigned_manager_user_id == scope.own_user_id, + ClientServiceSubscription.assigned_staff_user_id == scope.own_user_id, + ClientServiceSubscription.review_partner_user_id == scope.own_user_id, + Client.partner_id == scope.own_user_id, + ) + ) + if q: + like = f"%{q.lower()}%" + filters.append( + or_( + func.lower(Client.client_name).like(like), + func.lower(Client.client_code).like(like), + func.lower(ServiceCatalogue.service_name).like(like), + func.lower(ServiceCatalogue.service_code).like(like), + ) + ) + return filters + + +def _count(db, stmt) -> int: + return int(db.execute(stmt).scalar_one() or 0) + + +def _subscription_id_subquery(scope: AqmmScope, financial_year: str = "", q: str = ""): + return ( + select(ClientServiceSubscription.id) + .join(Client, Client.id == ClientServiceSubscription.client_id) + .join(ServiceCatalogue, ServiceCatalogue.id == ClientServiceSubscription.service_catalogue_id) + .where(and_(*_subscription_filters(scope, financial_year, q))) + .subquery() + ) + + +def _task_count(db, subq, *extra_filters) -> int: + return _count( + db, + select(func.count(ClientServiceTaskInstance.id)).where( + ClientServiceTaskInstance.subscription_id.in_(select(subq.c.id)), + ClientServiceTaskInstance.is_aqmm_task.is_(True), + *extra_filters, + ), + ) + + +def _subscription_task_counts(db, subscription_id: int) -> dict[str, int]: + base = [ClientServiceTaskInstance.subscription_id == subscription_id, ClientServiceTaskInstance.is_aqmm_task.is_(True)] + total = _count(db, select(func.count(ClientServiceTaskInstance.id)).where(*base)) + mandatory = _count(db, select(func.count(ClientServiceTaskInstance.id)).where(*base, ClientServiceTaskInstance.aqmm_mandatory.is_(True))) + evidence_pending = _count( + db, + select(func.count(ClientServiceTaskInstance.id)).where( + *base, + ClientServiceTaskInstance.aqmm_evidence_required.is_(True), + ClientServiceTaskInstance.evidence_status.notin_(list(EVIDENCE_OK_STATUSES)), + ), + ) + manager_pending = _count( + db, + select(func.count(ClientServiceTaskInstance.id)).where( + *base, + ClientServiceTaskInstance.aqmm_manager_review_required.is_(True), + ClientServiceTaskInstance.manager_review_status.notin_(list(REVIEWED_STATUSES)), + ), + ) + partner_pending = _count( + db, + select(func.count(ClientServiceTaskInstance.id)).where( + *base, + ClientServiceTaskInstance.aqmm_partner_review_required.is_(True), + ClientServiceTaskInstance.partner_review_status.notin_(list(REVIEWED_STATUSES)), + ), + ) + review_partner_pending = _count( + db, + select(func.count(ClientServiceTaskInstance.id)).where( + *base, + ClientServiceTaskInstance.aqmm_review_partner_required.is_(True), + ClientServiceTaskInstance.review_partner_review_status.notin_(list(REVIEWED_STATUSES)), + ), + ) + completed = _count( + db, + select(func.count(ClientServiceTaskInstance.id)).where( + *base, + or_( + ClientServiceTaskInstance.aqmm_status.in_(["completed", "complete", "approved"]), + ClientServiceTaskInstance.status.in_(["completed", "complete", "done"]), + ), + ), + ) + return { + "total": total, + "mandatory": mandatory, + "completed": completed, + "evidence_pending": evidence_pending, + "manager_pending": manager_pending, + "partner_pending": partner_pending, + "review_partner_pending": review_partner_pending, + } + + +def build_aqmm_dashboard_payload(db, request, user, *, financial_year: str = "", q: str = "") -> dict[str, Any]: + financial_year = _clean(financial_year) + q = _clean(q) + scope = build_scope(db, request, user) + subq = _subscription_id_subquery(scope, financial_year, q) + + total_assurance = _count(db, select(func.count()).select_from(subq)) + aqmm_approved = _count( + db, + select(func.count(ClientServiceSubscription.id)).where( + ClientServiceSubscription.id.in_(select(subq.c.id)), + ClientServiceSubscription.quality_workflow_status.in_(list(APPROVED_STATUSES)), + ), + ) + aqmm_pending = max(total_assurance - aqmm_approved, 0) + + kyc_pending = _count( + db, + select(func.count(ClientServiceSubscription.id)).where( + ClientServiceSubscription.id.in_(select(subq.c.id)), + ClientServiceSubscription.quality_kyc_status.notin_(list(APPROVED_STATUSES) + ["not_required"]), + ), + ) + declaration_pending = _count( + db, + select(func.count(ClientServiceSubscription.id)).where( + ClientServiceSubscription.id.in_(select(subq.c.id)), + or_( + ClientServiceSubscription.quality_independence_status.notin_(list(APPROVED_STATUSES) + ["not_required"]), + ClientServiceSubscription.quality_conflict_status.notin_(list(APPROVED_STATUSES) + ["not_required"]), + ), + ), + ) + letter_pending = _count( + db, + select(func.count(ClientServiceSubscription.id)).where( + ClientServiceSubscription.id.in_(select(subq.c.id)), + ClientServiceSubscription.quality_engagement_letter_status.notin_(list(APPROVED_STATUSES) + ["not_required"]), + ), + ) + + aqmm_tasks_total = _task_count(db, subq) + evidence_pending = _task_count( + db, + subq, + ClientServiceTaskInstance.aqmm_evidence_required.is_(True), + ClientServiceTaskInstance.evidence_status.notin_(list(EVIDENCE_OK_STATUSES)), + ) + manager_review_pending = _task_count( + db, + subq, + ClientServiceTaskInstance.aqmm_manager_review_required.is_(True), + ClientServiceTaskInstance.manager_review_status.notin_(list(REVIEWED_STATUSES)), + ) + partner_review_pending = _task_count( + db, + subq, + ClientServiceTaskInstance.aqmm_partner_review_required.is_(True), + ClientServiceTaskInstance.partner_review_status.notin_(list(REVIEWED_STATUSES)), + ) + review_partner_pending = _task_count( + db, + subq, + ClientServiceTaskInstance.aqmm_review_partner_required.is_(True), + ClientServiceTaskInstance.review_partner_review_status.notin_(list(REVIEWED_STATUSES)), + ) + rework_pending = _task_count( + db, + subq, + ClientServiceTaskInstance.rework_status.in_(["requested", "pending", "rework_required"]), + ) + + closure_closed = _count( + db, + select(func.count(EngagementClosureChecklist.id)).where( + EngagementClosureChecklist.subscription_id.in_(select(subq.c.id)), + EngagementClosureChecklist.closure_status.in_(list(CLOSURE_CLOSED_STATUSES)), + ), + ) + closure_pending = max(total_assurance - closure_closed, 0) + + engagement_rows = db.execute( + select(ClientServiceSubscription, EngagementClosureChecklist) + .join(Client, Client.id == ClientServiceSubscription.client_id) + .join(ServiceCatalogue, ServiceCatalogue.id == ClientServiceSubscription.service_catalogue_id) + .outerjoin(EngagementClosureChecklist, EngagementClosureChecklist.subscription_id == ClientServiceSubscription.id) + .options( + joinedload(ClientServiceSubscription.client), + joinedload(ClientServiceSubscription.catalogue), + joinedload(ClientServiceSubscription.assigned_partner), + joinedload(ClientServiceSubscription.assigned_manager), + joinedload(ClientServiceSubscription.assigned_staff), + joinedload(ClientServiceSubscription.review_partner), + ) + .where(and_(*_subscription_filters(scope, financial_year, q))) + .order_by(ClientServiceSubscription.updated_at_utc.desc(), ClientServiceSubscription.id.desc()) + .limit(50) + ).all() + + engagements = [] + for subscription, closure in engagement_rows: + counts = _subscription_task_counts(db, subscription.id) + blockers = [] + if subscription.quality_workflow_status not in APPROVED_STATUSES: + blockers.append("AQMM acceptance pending") + if counts["evidence_pending"]: + blockers.append(f"Evidence pending: {counts['evidence_pending']}") + if counts["manager_pending"]: + blockers.append(f"Manager review pending: {counts['manager_pending']}") + if counts["partner_pending"]: + blockers.append(f"Partner review pending: {counts['partner_pending']}") + if counts["review_partner_pending"]: + blockers.append(f"Review partner review pending: {counts['review_partner_pending']}") + if closure and closure.closure_status not in CLOSURE_CLOSED_STATUSES: + blockers.append("Closure pending") + engagements.append({ + "row": subscription, + "closure": closure, + "counts": counts, + "blockers": blockers, + }) + + pending_tasks = db.execute( + select(ClientServiceTaskInstance) + .options(joinedload(ClientServiceTaskInstance.subscription).joinedload(ClientServiceSubscription.client), joinedload(ClientServiceTaskInstance.subscription).joinedload(ClientServiceSubscription.catalogue)) + .where( + ClientServiceTaskInstance.subscription_id.in_(select(subq.c.id)), + ClientServiceTaskInstance.is_aqmm_task.is_(True), + or_( + and_(ClientServiceTaskInstance.aqmm_evidence_required.is_(True), ClientServiceTaskInstance.evidence_status.notin_(list(EVIDENCE_OK_STATUSES))), + and_(ClientServiceTaskInstance.aqmm_manager_review_required.is_(True), ClientServiceTaskInstance.manager_review_status.notin_(list(REVIEWED_STATUSES))), + and_(ClientServiceTaskInstance.aqmm_partner_review_required.is_(True), ClientServiceTaskInstance.partner_review_status.notin_(list(REVIEWED_STATUSES))), + and_(ClientServiceTaskInstance.aqmm_review_partner_required.is_(True), ClientServiceTaskInstance.review_partner_review_status.notin_(list(REVIEWED_STATUSES))), + ClientServiceTaskInstance.rework_status.in_(["requested", "pending", "rework_required"]), + ), + ) + .order_by(ClientServiceTaskInstance.internal_target_date.asc().nulls_last(), ClientServiceTaskInstance.sequence_no.asc()) + .limit(50) + ).scalars().all() + + return { + "financial_year": financial_year, + "q": q, + "scope": scope, + "kpis": { + "total_assurance": total_assurance, + "aqmm_approved": aqmm_approved, + "aqmm_pending": aqmm_pending, + "declaration_pending": declaration_pending, + "kyc_pending": kyc_pending, + "engagement_letter_pending": letter_pending, + "aqmm_tasks_total": aqmm_tasks_total, + "evidence_pending": evidence_pending, + "manager_review_pending": manager_review_pending, + "partner_review_pending": partner_review_pending, + "review_partner_pending": review_partner_pending, + "rework_pending": rework_pending, + "closure_pending": closure_pending, + "closure_closed": closure_closed, + }, + "engagements": engagements, + "pending_tasks": pending_tasks, + } diff --git a/app/modules/aqmm_dashboard/templates/aqmm_dashboard/dashboard.html b/app/modules/aqmm_dashboard/templates/aqmm_dashboard/dashboard.html new file mode 100644 index 0000000..6fec2ce --- /dev/null +++ b/app/modules/aqmm_dashboard/templates/aqmm_dashboard/dashboard.html @@ -0,0 +1,143 @@ +{% extends "ui/templates/base/layout.html" %} +{% block content %} +
+
+
+

AQMM Dashboard

+

Assurance engagement quality status, AQMM tasks, evidence, reviews and closure readiness.

+
+ +
+ +
+
+ + +
+
+ + +
+ +
+ +
+

Assurance Engagements

{{ kpis.total_assurance }}

Within selected scope

+

AQMM Approved

{{ kpis.aqmm_approved }}

Pending: {{ kpis.aqmm_pending }}

+

Evidence Pending

{{ kpis.evidence_pending }}

AQMM-tagged task evidence

+

Closure Pending

{{ kpis.closure_pending }}

Closed: {{ kpis.closure_closed }}

+
+ +
+

Declarations Pending

{{ kpis.declaration_pending }}

+

KYC Pending

{{ kpis.kyc_pending }}

+

Engagement Letter Pending

{{ kpis.engagement_letter_pending }}

+

Rework Pending

{{ kpis.rework_pending }}

+
+ +
+

AQMM Tasks

{{ kpis.aqmm_tasks_total }}

+

Manager Review Pending

{{ kpis.manager_review_pending }}

+

Partner Review Pending

{{ kpis.partner_review_pending }}

+

Review Partner Pending

{{ kpis.review_partner_pending }}

+
+ +
+
+

Assurance Engagement Quality Status

+

Shows only assurance engagements in the selected tenant, branch and financial year.

+
+
+ + + + + + + + + + + + + + {% for item in engagements %} + {% set row = item.row %} + {% set closure = item.closure %} + {% set counts = item.counts %} + + + + + + + + + + {% else %} + + {% endfor %} + +
Client / ServiceAQMM AcceptanceChecklist TasksPending ReviewsClosureBlockersAction
+
{{ row.client.client_name if row.client else '-' }}
+
{{ row.catalogue.service_name if row.catalogue else '-' }} · FY {{ row.financial_year or '-' }}
+
Partner: {{ row.assigned_partner.full_name if row.assigned_partner and row.assigned_partner.full_name else (row.assigned_partner.email if row.assigned_partner else '-') }}
+
+
Status: {{ (row.quality_workflow_status or 'not_required')|replace('_',' ')|title }}
+
KYC: {{ (row.quality_kyc_status or '-')|replace('_',' ')|title }}
+
Independence: {{ (row.quality_independence_status or '-')|replace('_',' ')|title }}
+
Conflict: {{ (row.quality_conflict_status or '-')|replace('_',' ')|title }}
+
Letter: {{ (row.quality_engagement_letter_status or '-')|replace('_',' ')|title }}
+
+
Total: {{ counts.total }}
+
Mandatory: {{ counts.mandatory }}
+
Completed: {{ counts.completed }}
+
Evidence pending: {{ counts.evidence_pending }}
+
+
Manager: {{ counts.manager_pending }}
+
Partner: {{ counts.partner_pending }}
+
Review Partner: {{ counts.review_partner_pending }}
+
+ {{ (closure.closure_status if closure else 'not_started')|replace('_',' ')|title }} + + {% if item.blockers %} +
    + {% for blocker in item.blockers[:4] %}
  • {{ blocker }}
  • {% endfor %} + {% if item.blockers|length > 4 %}
  • More blockers...
  • {% endif %} +
+ {% else %} + No major blocker + {% endif %} +
Open
No assurance engagements found.
+
+
+ +
+
+

AQMM Tasks Needing Evidence / Review

+

Tasks shown here are already part of your existing service checklist and are tagged as AQMM tasks.

+
+
+ + + + {% for task in pending_tasks %} + + + + + + + + {% else %} + + {% endfor %} + +
TaskClient / EngagementEvidenceReview StatusAction
{{ task.task_name }}
Seq {{ task.sequence_no }}{% if task.aqmm_reference %} · {{ task.aqmm_reference }}{% endif %}
{{ task.subscription.client.client_name if task.subscription and task.subscription.client else '-' }}
{{ task.subscription.catalogue.service_name if task.subscription and task.subscription.catalogue else '-' }}
Required: {{ 'Yes' if task.aqmm_evidence_required else 'No' }}
Status: {{ (task.evidence_status or '-')|replace('_',' ')|title }}
Manager: {{ (task.manager_review_status or '-')|replace('_',' ')|title }}
Partner: {{ (task.partner_review_status or '-')|replace('_',' ')|title }}
Review Partner: {{ (task.review_partner_review_status or '-')|replace('_',' ')|title }}
{% if task.rework_status not in ['none','not_required',''] %}
Rework: {{ task.rework_status|replace('_',' ')|title }}
{% endif %}
Evidence
No pending AQMM task evidence or reviews.
+
+
+
+{% endblock %} diff --git a/app/modules/aqmm_dashboard/ui.py b/app/modules/aqmm_dashboard/ui.py new file mode 100644 index 0000000..4477c87 --- /dev/null +++ b/app/modules/aqmm_dashboard/ui.py @@ -0,0 +1,113 @@ +from __future__ import annotations + +import csv +import io + +from fastapi import APIRouter, Request +from fastapi.responses import RedirectResponse, StreamingResponse + +from app.core.db.common import CommonSessionLocal +from app.core.http_responses import ui_access_denied +from app.core.security.csrf import get_or_create_csrf_token +from app.core.security.session_auth import get_current_user +from app.core.templating import templates +from app.modules.aqmm_dashboard.service import build_aqmm_dashboard_payload, can_view_aqmm_dashboard +from app.modules.core.rbac.deps import get_user_permissions, get_user_roles + +router = APIRouter(prefix="/aqmm", tags=["aqmm-dashboard-ui"]) + + +def _ctx(request: Request, db, user, **extra): + base = { + "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": "AQMM Dashboard", + } + base.update(extra) + return base + + +@router.get("/dashboard") +def dashboard(request: Request, financial_year: str = "", q: str = ""): + db = CommonSessionLocal() + try: + user = get_current_user(request, db=db) + if not user: + return RedirectResponse(url="/login", status_code=303) + if not can_view_aqmm_dashboard(db, user): + return ui_access_denied() + payload = build_aqmm_dashboard_payload(db, request, user, financial_year=financial_year, q=q) + return templates.TemplateResponse( + "modules/aqmm_dashboard/templates/aqmm_dashboard/dashboard.html", + _ctx(request, db, user, **payload), + ) + finally: + db.close() + + +@router.get("/dashboard/export.csv") +def dashboard_export_csv(request: Request, financial_year: str = "", q: str = ""): + db = CommonSessionLocal() + try: + user = get_current_user(request, db=db) + if not user: + return RedirectResponse(url="/login", status_code=303) + if not can_view_aqmm_dashboard(db, user): + return ui_access_denied() + payload = build_aqmm_dashboard_payload(db, request, user, financial_year=financial_year, q=q) + + buf = io.StringIO() + writer = csv.writer(buf) + writer.writerow([ + "Client", + "Service", + "FY", + "Status", + "AQMM Status", + "KYC", + "Independence", + "Conflict", + "Engagement Letter", + "AQMM Tasks", + "Evidence Pending", + "Manager Review Pending", + "Partner Review Pending", + "Review Partner Pending", + "Closure Status", + "Blockers", + ]) + for item in payload["engagements"]: + row = item["row"] + closure = item.get("closure") + counts = item["counts"] + writer.writerow([ + row.client.client_name if row.client else "", + row.catalogue.service_name if row.catalogue else "", + row.financial_year or "", + row.status or "", + row.quality_workflow_status or "", + row.quality_kyc_status or "", + row.quality_independence_status or "", + row.quality_conflict_status or "", + row.quality_engagement_letter_status or "", + counts.get("total", 0), + counts.get("evidence_pending", 0), + counts.get("manager_pending", 0), + counts.get("partner_pending", 0), + counts.get("review_partner_pending", 0), + closure.closure_status if closure else "not_started", + "; ".join(item.get("blockers") or []), + ]) + + data = buf.getvalue().encode("utf-8-sig") + filename = f"aqmm_dashboard_{financial_year or 'all'}.csv" + return StreamingResponse( + iter([data]), + media_type="text/csv", + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, + ) + finally: + db.close() diff --git a/app/ui/app.py b/app/ui/app.py index 93a77f5..330d12d 100644 --- a/app/ui/app.py +++ b/app/ui/app.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI +from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from app.modules.clients.ui import router as clients_ui_router, portal_router as client_portal_router @@ -29,6 +29,7 @@ from app.modules.system_admin_dashboard.ui import router as system_admin_dashboa from app.ui.routes.auth import router as auth_router from app.modules.workspace_navigation.ui import router as workspace_navigation_router from app.modules.firm_admin_dashboard.ui import router as firm_admin_dashboard_router +from app.modules.aqmm_dashboard.ui import router as aqmm_dashboard_router def mount_ui(app: FastAPI) -> None: @@ -44,6 +45,7 @@ def mount_ui(app: FastAPI) -> None: app.include_router(rbac_ui_router) app.include_router(audit_ui_router) app.include_router(services_ui_router) + app.include_router(aqmm_dashboard_router) app.include_router(work_tracker_ui_router) app.include_router(billing_ui_router) app.include_router(platform_billing_ui_router)