feat: complete advisor product suitability gates
This commit is contained in:
@@ -80,6 +80,7 @@ async def test_authoritative_candidates_fail_closed_and_keep_r1_without_scale_li
|
||||
],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
])
|
||||
result = await AdvisorProductRepository(session).authoritative_tradable_products(
|
||||
now, sales_institution="南方基金管理股份有限公司直销",
|
||||
@@ -95,10 +96,52 @@ async def test_authoritative_candidates_fail_closed_and_keep_r1_without_scale_li
|
||||
async def test_pending_governance_and_missing_evidence_are_excluded() -> None:
|
||||
now = datetime(2026, 9, 11, 10, 0)
|
||||
session = FakeSession([
|
||||
[product(1)], [suitability(1)], [contract(1)], [], [1], [],
|
||||
[product(1)], [suitability(1)], [contract(1)], [], [], [1], [],
|
||||
])
|
||||
result = await AdvisorProductRepository(session).authoritative_tradable_products(
|
||||
now, sales_institution="南方基金管理股份有限公司直销",
|
||||
)
|
||||
assert result == []
|
||||
assert "exchange_code IN" in str(session.statements[0])
|
||||
|
||||
|
||||
def test_hard_suitability_filter_returns_exclusion_reasons() -> None:
|
||||
from app.repository.advisor_product_repository import (
|
||||
AdvisorProductRepository,
|
||||
AuthoritativeProductCandidate,
|
||||
ProductContractEvidence,
|
||||
ProductSuitabilityEvidence,
|
||||
)
|
||||
|
||||
def candidate(code: str, risk: str) -> AuthoritativeProductCandidate:
|
||||
item = product(1)
|
||||
item.product_code = code
|
||||
return AuthoritativeProductCandidate(
|
||||
product=item,
|
||||
suitability=ProductSuitabilityEvidence(
|
||||
risk_level=risk, sales_institution="南方基金管理股份有限公司直销",
|
||||
source_url="https://example.test/risk.pdf", document_title="风险等级披露",
|
||||
document_sha256="a" * 64, effective_from=date(2026, 1, 1),
|
||||
),
|
||||
contract=ProductContractEvidence(
|
||||
fund_type="股票型ETF", investment_scope="场内基金", performance_benchmark=None,
|
||||
risk_return_characteristics="净值波动", custodian_name=None,
|
||||
management_fee_rate_pct=None, custodian_fee_rate_pct=None, inception_date=None,
|
||||
source_url="https://example.test/contract.pdf", document_title="基金合同",
|
||||
document_sha256="b" * 64, document_published_at=None,
|
||||
),
|
||||
)
|
||||
|
||||
selected, excluded = AdvisorProductRepository.hard_suitability_filter(
|
||||
[candidate("R1", "R1"), candidate("R4", "R4")], 2
|
||||
)
|
||||
assert [item.product.product_code for item in selected] == ["R1"]
|
||||
assert excluded[0]["reason_code"] == "RISK_LEVEL_MISMATCH"
|
||||
|
||||
|
||||
def test_liquidity_requirement_is_failed_closed_without_metrics() -> None:
|
||||
from app.repository.advisor_product_repository import AdvisorProductRepository
|
||||
|
||||
evidence = AdvisorProductRepository._liquidity_evidence(None, None, "within_7_days")
|
||||
assert evidence is not None
|
||||
assert evidence.status == "insufficient"
|
||||
|
||||
Reference in New Issue
Block a user