Files
AIPortPilot/backend/tests/test_evaluation_engine.py

227 lines
8.5 KiB
Python

"""评价权重计算引擎测试。"""
import pytest
from app.services.evaluation_engine import (
BASE_WEIGHTS,
DIMENSION_KEY_MAP,
calculate_weighted_score,
compute_weights,
get_dimension_score_key,
)
class TestComputeWeights:
"""权重计算引擎测试。"""
def test_basic_computation(self):
"""基本权重计算应返回有效结果。"""
result = compute_weights(
fund_type="early_vc",
fund_lifecycle="investment",
company_stage="a",
industry="ai",
strategy="growth",
)
assert "weights" in result
assert "enabled_dimensions" in result
assert "disabled_dimensions" in result
assert "custom_metrics" in result
def test_weights_sum_to_100(self):
"""归一化后权重总和应等于 100。"""
result = compute_weights(
fund_type="early_vc",
fund_lifecycle="growth",
company_stage="b",
industry="saas",
strategy="value",
)
total = sum(result["weights"].values())
assert abs(total - 100.0) < 0.5, f"权重总和应为 100,实际为 {total}"
def test_disabled_dimensions_have_zero_weight(self):
"""禁用的维度不应出现在权重中。"""
result = compute_weights(
fund_type="pe",
fund_lifecycle="investment",
company_stage="c",
industry="hardware",
strategy="value",
)
for dim in result["disabled_dimensions"]:
assert dim not in result["weights"] or result["weights"][dim] == 0
def test_ai_industry_enables_all_dimensions(self):
"""AI 赛道应启用全部 14 维度。"""
result = compute_weights(
fund_type="early_vc",
fund_lifecycle="investment",
company_stage="a",
industry="ai",
)
assert len(result["enabled_dimensions"]) == 14
assert len(result["disabled_dimensions"]) == 0
def test_hardware_disables_ai_dimensions(self):
"""硬科技赛道应禁用 AI 相关维度。"""
result = compute_weights(
fund_type="early_vc",
fund_lifecycle="investment",
company_stage="a",
industry="hardware",
)
assert "ai_commercial" in result["disabled_dimensions"]
assert "ai_cost" in result["disabled_dimensions"]
assert "ai_model_product" in result["disabled_dimensions"]
def test_biotech_disables_market_and_customer(self):
"""生物医药赛道应禁用市场竞争和客户成功。"""
result = compute_weights(
fund_type="angel",
fund_lifecycle="investment",
company_stage="seed",
industry="biotech",
)
assert "market_compete" in result["disabled_dimensions"]
assert "customer_success" in result["disabled_dimensions"]
def test_seed_stage_emphasizes_product_and_team(self):
"""种子期应提高产品技术和组织人才权重。"""
result = compute_weights(
fund_type="angel",
fund_lifecycle="investment",
company_stage="seed",
industry="ai",
)
weights = result["weights"]
# 产品技术权重应高于财务
assert weights.get("product_tech", 0) > weights.get("financial", 0)
# 组织人才权重应高于治理
assert weights.get("org_talent", 0) > weights.get("governance", 0)
def test_pre_ipo_stage_emphasizes_financial_and_governance(self):
"""Pre-IPO 阶段应提高财务和治理权重。"""
result = compute_weights(
fund_type="growth_vc",
fund_lifecycle="exit_preparation",
company_stage="pre_ipo",
industry="ai",
)
weights = result["weights"]
# 财务权重应高于产品技术
assert weights.get("financial", 0) > weights.get("product_tech", 0)
# Pre-IPO 的治理权重应高于种子期
seed_result = compute_weights(
fund_type="growth_vc",
fund_lifecycle="exit_preparation",
company_stage="seed",
industry="ai",
)
assert weights.get("governance", 0) > seed_result["weights"].get("governance", 0)
def test_pe_fund_type_emphasizes_financial(self):
"""PE 基金应大幅提高财务权重。"""
result = compute_weights(
fund_type="pe",
fund_lifecycle="growth",
company_stage="b",
industry="saas",
)
weights = result["weights"]
# PE 的财务权重应高于早期 VC
vc_result = compute_weights(
fund_type="early_vc",
fund_lifecycle="investment",
company_stage="b",
industry="saas",
)
assert weights.get("financial", 0) > vc_result["weights"].get("financial", 0)
def test_custom_metrics_present(self):
"""各赛道应有专属指标。"""
for industry in ["ai", "saas", "hardware", "biotech", "consumer", "fintech"]:
result = compute_weights(
fund_type="early_vc",
fund_lifecycle="investment",
company_stage="a",
industry=industry,
)
assert len(result["custom_metrics"]) >= 3, f"{industry} 赛道专属指标不足"
def test_strategy_adjustment_effect(self):
"""投资策略微调应影响权重。"""
base = compute_weights(
fund_type="early_vc",
fund_lifecycle="growth",
company_stage="b",
industry="ai",
strategy="growth",
)
value = compute_weights(
fund_type="early_vc",
fund_lifecycle="growth",
company_stage="b",
industry="ai",
strategy="value",
)
# 成长型策略市场权重应高于价值型
assert base["weights"].get("market_compete", 0) > value["weights"].get("market_compete", 0)
# 价值型策略财务权重应高于成长型
assert value["weights"].get("financial", 0) > base["weights"].get("financial", 0)
def test_all_weights_non_negative(self):
"""所有权重应为非负数。"""
for fund_type in ["angel", "early_vc", "growth_vc", "pe", "cvc", "distress", "esg"]:
for lifecycle in ["investment", "growth", "exit_preparation", "liquidation"]:
for stage in ["seed", "a", "b", "c", "pre_ipo"]:
for industry in ["ai", "saas", "hardware", "biotech", "consumer", "fintech"]:
result = compute_weights(
fund_type=fund_type,
fund_lifecycle=lifecycle,
company_stage=stage,
industry=industry,
)
for dim, weight in result["weights"].items():
assert weight >= 0, f"{fund_type}/{lifecycle}/{stage}/{industry}{dim} 权重为负: {weight}"
class TestCalculateWeightedScore:
"""加权评分计算测试。"""
def test_basic_weighted_score(self):
"""基本加权评分计算。"""
scores = {"financial": 80, "operational": 70, "product_tech": 90}
weights = {"financial": 30, "operational": 30, "product_tech": 40}
result = calculate_weighted_score(scores, weights)
expected = (80 * 30 + 70 * 30 + 90 * 40) / 100
assert abs(result - expected) < 0.1
def test_missing_dimension_ignored(self):
"""缺失维度的评分应被忽略。"""
scores = {"financial": 80}
weights = {"financial": 50, "operational": 50}
result = calculate_weighted_score(scores, weights)
assert abs(result - 80.0) < 0.1
def test_empty_scores(self):
"""空评分应返回 0。"""
result = calculate_weighted_score({}, {"financial": 100})
assert result == 0.0
class TestDimensionKeyMap:
"""维度 key 映射测试。"""
def test_key_mapping(self):
"""权重 key 应正确映射到评分字段 key。"""
assert get_dimension_score_key("financial") == "financial_score"
assert get_dimension_score_key("ai_commercial") == "ai_commercial_score"
assert get_dimension_score_key("customer_success") == "customer_success_score"
def test_all_dimensions_mapped(self):
"""所有 14 维度都应有映射。"""
assert len(DIMENSION_KEY_MAP) == 14
for key in BASE_WEIGHTS:
assert key in DIMENSION_KEY_MAP, f"维度 {key} 缺少映射"