feat(platform): close AI expense value loop

Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
caoxiaozhu
2026-07-17 14:14:08 +08:00
parent 242d68c36f
commit 787bc3a481
507 changed files with 82072 additions and 6344 deletions

View File

@@ -0,0 +1,319 @@
from __future__ import annotations
import uuid
from datetime import UTC, date, datetime, timedelta
from decimal import Decimal
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool
import app.models # noqa: F401 - 注册完整 metadata
from app.api.deps import CurrentUserContext
from app.db.base_class import Base
from app.models.financial_record import ExpenseClaim, ExpenseClaimItem
from app.schemas.cfo_value import CfoValueFiltersRead
from app.schemas.savings import (
SavingsEvidenceCreate,
SavingsRealizationActionCreate,
SavingsRealizationCreate,
)
from app.services.cfo_value_analytics import CfoValueAnalyticsService
from app.services.savings_access_policy import SavingsPermissionError
from app.services.savings_discovery import SavingsDiscoveryService
from app.services.savings_realization import SavingsRealizationService
@pytest.fixture()
def db() -> Session:
engine = create_engine(
"sqlite+pysqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine, expire_on_commit=False)
with factory() as session:
yield session
Base.metadata.drop_all(engine)
engine.dispose()
def test_cfo_value_counts_only_confirmed_canonical_and_replays_as_of(db: Session) -> None:
operator = _user("finance-operator", roles=["finance"])
confirmer = _user("finance-confirmer", roles=["finance"])
cny_opportunity = _discover(db, operator, currency="CNY", saving=Decimal("200"))
usd_opportunity = _discover(db, operator, currency="USD", saving=Decimal("50"))
db.commit()
cny_realization = (
SavingsRealizationService(db)
.record(
cny_opportunity,
_record_payload("record-cny-001", Decimal("200"), "CNY"),
operator,
)
.response.realization
)
usd_realization = (
SavingsRealizationService(db)
.record(
usd_opportunity,
_record_payload("record-usd-001", Decimal("50"), "USD"),
operator,
)
.response.realization
)
start = datetime.now(UTC) - timedelta(days=2)
end = datetime.now(UTC) + timedelta(days=2)
pending_dashboard = CfoValueAnalyticsService(db).build_dashboard(
confirmer,
start=start,
end=end,
as_of=datetime.now(UTC),
filters=CfoValueFiltersRead(),
)
assert pending_dashboard.kpis.verified_cash.status == "empty"
assert pending_dashboard.data_quality.pending_confirmation_count == 2
assert _stage(pending_dashboard, "actual_pending").count == 2
confirmed_cny = (
SavingsRealizationService(db)
.execute_action(
cny_realization.id,
SavingsRealizationActionCreate(
action="confirm",
request_id="confirm-cny-001",
expected_version=1,
comment="独立复核 CNY 结果与归因",
),
confirmer,
)
.response
)
SavingsRealizationService(db).execute_action(
usd_realization.id,
SavingsRealizationActionCreate(
action="confirm",
request_id="confirm-usd-001",
expected_version=1,
comment="独立复核 USD 结果与归因",
),
confirmer,
)
confirmed_as_of = confirmed_cny.event.occurred_at
current = CfoValueAnalyticsService(db).build_dashboard(
confirmer,
start=start,
end=end,
as_of=datetime.now(UTC),
filters=CfoValueFiltersRead(),
)
assert _money(current.kpis.verified_cash.values) == {
"CNY": Decimal("200.0000"),
"USD": Decimal("50.0000"),
}
assert current.kpis.releasable_labor.status == "collecting"
assert current.kpis.safe_straight_through.status == "collecting"
assert (
next(
item for item in current.guardrails if item.key == "confirmed_high_risk_exposure"
).status
== "unavailable"
)
SavingsRealizationService(db).execute_action(
cny_realization.id,
SavingsRealizationActionCreate(
action="reverse",
request_id="reverse-cny-001",
expected_version=2,
comment="补付后全额冲回 CNY 节省",
reversal_amount=Decimal("200"),
),
confirmer,
)
latest = CfoValueAnalyticsService(db).build_dashboard(
confirmer,
start=start,
end=end,
as_of=datetime.now(UTC),
filters=CfoValueFiltersRead(),
)
assert _money(latest.kpis.verified_cash.values) == {
"CNY": Decimal("0.0000"),
"USD": Decimal("50.0000"),
}
assert _stage(latest, "reversed").count == 1
historical = CfoValueAnalyticsService(db).build_dashboard(
confirmer,
start=start,
end=end,
as_of=confirmed_as_of,
filters=CfoValueFiltersRead(),
)
assert _money(historical.kpis.verified_cash.values)["CNY"] == Decimal("200.0000")
with pytest.raises(SavingsPermissionError):
CfoValueAnalyticsService(db).build_dashboard(
_user("employee"),
start=start,
end=end,
as_of=datetime.now(UTC),
filters=CfoValueFiltersRead(),
)
def test_cfo_value_budget_monitor_is_limited_to_own_department(db: Session) -> None:
operator = _user("finance-operator", roles=["finance"])
_discover(
db,
operator,
currency="CNY",
saving=Decimal("100"),
department_name="销售部",
)
_discover(
db,
operator,
currency="CNY",
saving=Decimal("300"),
department_name="研发部",
)
db.commit()
dashboard = CfoValueAnalyticsService(db).build_dashboard(
_user("budget-sales", roles=["budget_monitor"], department_name="销售部"),
start=datetime.now(UTC) - timedelta(days=1),
end=datetime.now(UTC) + timedelta(days=1),
as_of=datetime.now(UTC),
filters=CfoValueFiltersRead(),
)
assert dashboard.source.opportunity_count == 1
assert _money(_stage(dashboard, "estimated").values) == {
"CNY": Decimal("100.0000")
}
def _discover(
db: Session,
current_user: CurrentUserContext,
*,
currency: str,
saving: Decimal,
department_name: str = "销售部",
) -> str:
original = Decimal("1000")
target = original - saving
claim = ExpenseClaim(
id=str(uuid.uuid4()),
claim_no=f"BX-{uuid.uuid4().hex[:12]}",
employee_name="测试员工",
department_name=department_name,
project_code="PROJECT-CFO",
expense_type="hotel",
reason="客户现场差旅",
location="上海",
amount=original,
currency=currency,
invoice_count=1,
occurred_at=datetime.now(UTC),
status="draft",
risk_flags_json=[],
)
item = ExpenseClaimItem(
id=str(uuid.uuid4()),
claim=claim,
item_date=date.today(),
item_type="hotel",
item_reason="住宿",
item_location="上海",
item_note="",
item_amount=original,
)
db.add(claim)
db.flush()
return (
SavingsDiscoveryService(db)
.discover_standard_adjustments(
claim=claim,
items_by_id={item.id: item},
adjustment_flags=[
{
"item_id": item.id,
"message": "CFO 聚合测试政策差额",
"original_amount": str(original),
"reimbursable_amount": str(target),
"employee_absorbed_amount": str(saving),
"policy_rule_version": f"cfo-{currency.lower()}-v1",
"policy_grade": "P6",
"policy_matched_city": "上海",
"calculation_fingerprint": "sha256:" + uuid.uuid4().hex * 2,
}
],
current_user=current_user,
request_id=f"discover-{currency.lower()}-{uuid.uuid4().hex[:8]}",
)[0]
.id
)
def _record_payload(
request_id: str,
amount: Decimal,
currency: str,
) -> SavingsRealizationCreate:
return SavingsRealizationCreate(
request_id=request_id,
expected_version=1,
comment="登记待独立确认的实际结果",
actual_gross=amount,
incremental_cost=Decimal("0"),
currency=currency,
realized_at=datetime.now(UTC),
attribution_method="server_policy_counterfactual",
attribution_ratio=Decimal("1"),
evidence_level="business_state",
evidence=[
SavingsEvidenceCreate(
evidence_key=f"evidence-{request_id}",
evidence_role="payment_business_state",
resource_type="business_event",
resource_id=f"payment-{request_id}",
source_system="x-financial",
external_event_id=f"payment-{request_id}",
content_hash="b" * 64,
occurred_at=datetime.now(UTC),
verification_status="unverified",
metadata_json={"source": "cfo-test"},
)
],
)
def _user(
username: str,
*,
roles: list[str] | None = None,
department_name: str = "",
) -> CurrentUserContext:
return CurrentUserContext(
username=username,
name=username,
role_codes=list(roles or []),
is_admin=False,
tenant_id="default",
employee_id=username,
department_name=department_name,
)
def _money(items) -> dict[str, Decimal]:
return {item.currency: item.amount for item in items}
def _stage(dashboard, key: str):
return next(item for item in dashboard.funnel.stages if item.key == key)