227 lines
8.5 KiB
Python
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} 缺少映射"
|