From 8746068cd48ed27ce72cb6d6a76e6fc8479c9f92 Mon Sep 17 00:00:00 2001 From: SmartUp Developer Date: Thu, 2 Jul 2026 20:23:20 +0800 Subject: [PATCH] feat: implement priority_weighted_plus_percent pricing algorithm --- backend/app/models/website.py | 2 +- backend/app/routers/websites.py | 2 +- backend/app/schemas/website.py | 2 +- backend/app/services/website_client.py | 22 +++++++++++++++++++++- backend/test_website_client.py | 16 ++++++++++++++++ frontend/src/views/Websites.vue | 5 +++-- 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/backend/app/models/website.py b/backend/app/models/website.py index 24d5cf3..fc5fe56 100644 --- a/backend/app/models/website.py +++ b/backend/app/models/website.py @@ -40,7 +40,7 @@ class WebsiteGroupBinding(Base): target_group_name: Mapped[str] = mapped_column(String(255), default="") source_groups_json: Mapped[str] = mapped_column(Text, default="[]") percent: Mapped[str] = mapped_column(String(32), default="0") - algorithm: Mapped[str] = mapped_column(String(64), default="max_plus_percent") + algorithm: Mapped[str] = mapped_column(String(64), default="priority_weighted_plus_percent") enabled: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) updated_at: Mapped[datetime] = mapped_column( diff --git a/backend/app/routers/websites.py b/backend/app/routers/websites.py index 4ea08c5..3de0026 100644 --- a/backend/app/routers/websites.py +++ b/backend/app/routers/websites.py @@ -73,7 +73,7 @@ SENSITIVE_CREDENTIAL_KEYS = { "aws_secret_access_key", "aws_session_token", "service_account_json", "service_account", "private_key", } -ALGORITHMS = {"max_plus_percent", "average_plus_percent", "min_plus_percent"} +ALGORITHMS = {"max_plus_percent", "average_plus_percent", "min_plus_percent", "priority_weighted_plus_percent"} def _mask(cfg: dict) -> dict: diff --git a/backend/app/schemas/website.py b/backend/app/schemas/website.py index 9a02145..e3bc786 100644 --- a/backend/app/schemas/website.py +++ b/backend/app/schemas/website.py @@ -91,7 +91,7 @@ class BindingCreate(BaseModel): target_group_name: str = "" source_groups: list[BindingSourceGroup] = Field(default_factory=list) percent: float = Field(default=0, ge=0) - algorithm: str = "max_plus_percent" + algorithm: str = "priority_weighted_plus_percent" enabled: bool = True diff --git a/backend/app/services/website_client.py b/backend/app/services/website_client.py index 6745329..f61568e 100644 --- a/backend/app/services/website_client.py +++ b/backend/app/services/website_client.py @@ -65,12 +65,32 @@ def calculate_target_rate(values: list[Any], percent: Any = 0, algorithm: str = base = min(rates) elif algorithm == "max_plus_percent": base = max(rates) + elif algorithm == "priority_weighted_plus_percent": + rates_sorted = sorted(rates) + N = len(rates_sorted) + if N == 1: + base = rates_sorted[0] + else: + base = Decimal("0") + remaining = Decimal("1.0") + for i in range(N): + if i == N - 1: + weight = remaining + else: + weight = remaining * Decimal("0.85") + remaining -= weight + base += rates_sorted[i] * weight else: raise WebsiteError(f"不支持的算法:{algorithm}") pct = Decimal(str(percent or 0)) if pct < 0: raise WebsiteError("百分比不能为负数") - return (base * (Decimal("1") + pct / Decimal("100"))).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) + target_rate = (base * (Decimal("1") + pct / Decimal("100"))).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) + if algorithm == "priority_weighted_plus_percent": + min_rate = min(rates) + if target_rate < min_rate: + target_rate = min_rate + return target_rate def _unwrap_data(value: Any) -> Any: diff --git a/backend/test_website_client.py b/backend/test_website_client.py index ad39628..6f6eeae 100644 --- a/backend/test_website_client.py +++ b/backend/test_website_client.py @@ -399,3 +399,19 @@ def test_list_accounts_pagination_returns_none_when_any_page_fails(): assert len(requests_called) == 2 finally: client.close() + + +def test_calculate_target_rate_priority_weighted(): + # 0.18、0.25 + 50% 应得到约 0.29 + assert calculate_target_rate(["0.18", "0.25"], 50, "priority_weighted_plus_percent") == Decimal("0.29") + # 单来源时等同于该来源倍率 + 百分比 + assert calculate_target_rate(["0.18"], 50, "priority_weighted_plus_percent") == Decimal("0.27") + # 多来源时权重按低倍率优先,最高倍率不会主导价格 + assert calculate_target_rate(["0.18", "0.25", "0.38"], 50, "priority_weighted_plus_percent") == Decimal("0.29") + + # 无可用正数倍率时报错 + with pytest.raises(WebsiteError, match="没有可用的正数上游倍率"): + calculate_target_rate([], 50, "priority_weighted_plus_percent") + + # 旧算法结果不变 + assert calculate_target_rate(["0.18", "0.25"], 50, "max_plus_percent") == Decimal("0.38") diff --git a/frontend/src/views/Websites.vue b/frontend/src/views/Websites.vue index 0918ae0..3c8ad58 100644 --- a/frontend/src/views/Websites.vue +++ b/frontend/src/views/Websites.vue @@ -287,6 +287,7 @@
+ @@ -1091,7 +1092,7 @@ const syncingImportStatus = ref(false) const reorderingPriorities = ref(false) const statusLabel = (s: string) => ({ healthy: '健康', unhealthy: '异常', unknown: '未知' }[s] || s) -const algorithmLabel = (s: string) => ({ max_plus_percent: '最高倍率', average_plus_percent: '平均倍率', min_plus_percent: '最低倍率' }[s] || s) +const algorithmLabel = (s: string) => ({ max_plus_percent: '最高倍率', average_plus_percent: '平均倍率', min_plus_percent: '最低倍率', priority_weighted_plus_percent: '优先级期望成本' }[s] || s) const toUTC = (t: string) => /[Z+\-]\d*$/.test(t.trim()) ? t : t + 'Z' const fmtTime = (t: string) => dayjs(toUTC(t)).format('MM-DD HH:mm:ss') const sourceGroupId = (group: any) => String(group?.group_id || group?.id || group?.name || '') @@ -1130,7 +1131,7 @@ function defaultBindingForm(): GroupBindingForm { target_group_name: '', source_groups: [], percent: 0, - algorithm: 'max_plus_percent', + algorithm: 'priority_weighted_plus_percent', enabled: true, } }