from sqlalchemy.orm import DeclarativeMeta from app.modules.business import models from app.modules.business.constants import BusinessDomain DOMAIN_MODELS: dict[BusinessDomain, type[DeclarativeMeta]] = { BusinessDomain.PROJECTS: models.Project, BusinessDomain.TASKS: models.WorkTask, BusinessDomain.PROCUREMENTS: models.Procurement, BusinessDomain.EXPENSES: models.Expense, BusinessDomain.FUND_ACCOUNTS: models.FundAccount, BusinessDomain.POLICIES: models.Policy, BusinessDomain.STANDARDS: models.Standard, BusinessDomain.PERFORMANCE_METRICS: models.PerformanceMetric, BusinessDomain.SUPPLIERS: models.Supplier, BusinessDomain.ATTENDANCE_RECORDS: models.AttendanceRecord, BusinessDomain.WORK_REPORTS: models.WorkReport, BusinessDomain.RISK_EVENTS: models.RiskEvent, BusinessDomain.LEGACY_SYNC_RUNS: models.LegacySyncRun, } DOMAIN_WRITABLE_FIELDS: dict[BusinessDomain, frozenset[str]] = { BusinessDomain.PROJECTS: frozenset( { "code", "name", "owner", "status", "priority", "progress_percent", "risk_level", "budget_amount", "actual_amount", "start_date", "due_date", "description", } ), BusinessDomain.TASKS: frozenset( { "code", "title", "project_code", "owner", "status", "priority", "due_date", "completed_at", "blocker", "description", } ), BusinessDomain.PROCUREMENTS: frozenset( { "code", "name", "applicant", "project_code", "supplier_name", "budget_subject", "expected_amount", "actual_amount", "approval_status", "delivery_status", "payment_status", "comparison_summary", } ), BusinessDomain.EXPENSES: frozenset( { "code", "expense_type", "amount", "applicant", "department", "project_code", "budget_subject", "payment_account", "invoice_status", "approval_status", "payment_status", } ), BusinessDomain.FUND_ACCOUNTS: frozenset( { "code", "name", "account_type", "current_balance", "expected_receivable", "expected_payable", "safety_line", "risk_level", "note", } ), BusinessDomain.POLICIES: frozenset( { "code", "title", "policy_type", "owner_department", "version", "status", "effective_date", "feishu_doc_url", "summary", } ), BusinessDomain.STANDARDS: frozenset( { "code", "title", "standard_type", "applies_to", "status", "check_items", "remediation", "policy_code", } ), BusinessDomain.PERFORMANCE_METRICS: frozenset( { "code", "name", "applies_to_role", "formula", "weight", "data_source", "auto_score", "confirmed_score", "status", } ), BusinessDomain.SUPPLIERS: frozenset( { "code", "name", "category", "contact", "quality_score", "delivery_score", "price_score", "risk_level", "blacklist_status", } ), BusinessDomain.ATTENDANCE_RECORDS: frozenset( { "code", "employee_name", "employee_id", "department", "project_code", "work_date", "check_in_at", "check_out_at", "status", "location", "note", } ), BusinessDomain.WORK_REPORTS: frozenset( { "code", "report_type", "title", "reporter", "department", "project_code", "period_start", "period_end", "content", "metrics", "risk_summary", "status", } ), BusinessDomain.RISK_EVENTS: frozenset( { "code", "title", "risk_type", "risk_level", "status", "source_domain", "source_record_id", "project_code", "owner", "due_date", "assigned_to", "closed_reason", "review_summary", "description", "mitigation", "evidence", } ), BusinessDomain.LEGACY_SYNC_RUNS: frozenset( { "code", "domain", "source_table", "status", "created_count", "updated_count", "skipped_count", "error_message", "note", } ), } HIGH_RISK_DOMAINS = frozenset(DOMAIN_MODELS) LOW_RISK_DOMAINS = frozenset(set(DOMAIN_MODELS) - HIGH_RISK_DOMAINS) def normalize_domain(domain: str | BusinessDomain) -> BusinessDomain: try: return BusinessDomain(domain) except ValueError as exc: supported = ", ".join(sorted(item.value for item in DOMAIN_MODELS)) raise KeyError(f"Unsupported domain '{domain}'. Supported: {supported}") from exc def supported_domain_values() -> list[str]: return sorted(item.value for item in DOMAIN_MODELS) def is_high_risk_domain(domain: str | BusinessDomain) -> bool: return normalize_domain(domain) in HIGH_RISK_DOMAINS def get_domain_model(domain: str | BusinessDomain) -> type[DeclarativeMeta]: return DOMAIN_MODELS[normalize_domain(domain)] def get_writable_fields(domain: str | BusinessDomain) -> frozenset[str]: return DOMAIN_WRITABLE_FIELDS[normalize_domain(domain)]