fix: round website rates to two decimals
This commit is contained in:
@@ -45,6 +45,7 @@ from app.services.website_sync import (
|
||||
sync_account_priorities_for_upstream,
|
||||
)
|
||||
from app.utils.auth import get_current_user
|
||||
from app.utils.number import fixed_decimal_number
|
||||
|
||||
router = APIRouter(tags=["websites"])
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -339,7 +340,7 @@ def import_groups_from_upstream(
|
||||
"name": target_name,
|
||||
"description": group.get("description") or f"Imported from {upstream.name} / {source_name}",
|
||||
"platform": group.get("platform") or "openai",
|
||||
"rate_multiplier": _source_group_rate(group),
|
||||
"rate_multiplier": fixed_decimal_number(_source_group_rate(group), 2),
|
||||
}
|
||||
if group.get("rpm_limit") is not None:
|
||||
create_body["rpm_limit"] = group.get("rpm_limit")
|
||||
|
||||
@@ -7,7 +7,7 @@ from urllib.parse import quote
|
||||
|
||||
import httpx
|
||||
|
||||
from app.utils.number import decimal_string
|
||||
from app.utils.number import fixed_decimal_number, fixed_decimal_string
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -67,7 +67,7 @@ def calculate_target_rate(values: list[Any], percent: Any = 0, algorithm: str =
|
||||
pct = Decimal(str(percent or 0))
|
||||
if pct < 0:
|
||||
raise WebsiteError("百分比不能为负数")
|
||||
return (base * (Decimal("1") + pct / Decimal("100"))).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)
|
||||
return (base * (Decimal("1") + pct / Decimal("100"))).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
||||
|
||||
|
||||
def _unwrap_data(value: Any) -> Any:
|
||||
@@ -122,7 +122,7 @@ def normalize_groups(value: Any) -> list[dict[str, Any]]:
|
||||
groups.append({
|
||||
"id": str(gid),
|
||||
"name": str(name),
|
||||
"rate_multiplier": decimal_string(rate) if rate is not None else None,
|
||||
"rate_multiplier": fixed_decimal_string(rate, 2) if rate is not None else None,
|
||||
"raw": item,
|
||||
})
|
||||
return groups
|
||||
@@ -209,9 +209,9 @@ class Sub2ApiWebsiteClient:
|
||||
msg = str(last_error) if last_error else "拉取分组失败"
|
||||
raise WebsiteError(f"{msg}(尝试接口:{'、'.join(tried_paths)})")
|
||||
|
||||
def update_group_rate(self, endpoint_template: str, group_id: str, rate: Decimal) -> Any:
|
||||
def update_group_rate(self, endpoint_template: str, group_id: str, rate: Any) -> Any:
|
||||
path = endpoint_template.replace("{id}", quote(group_id, safe=""))
|
||||
return self._request("PUT", path, {"rate_multiplier": float(rate)})
|
||||
return self._request("PUT", path, {"rate_multiplier": fixed_decimal_number(rate, 2)})
|
||||
|
||||
def create_group(self, body: dict[str, Any], endpoint: str = "/groups") -> dict[str, Any]:
|
||||
resp = self._request("POST", endpoint, body)
|
||||
|
||||
@@ -11,9 +11,10 @@ from app.models.snapshot import UpstreamRateSnapshot
|
||||
from app.models.upstream import Upstream
|
||||
from app.models.upstream_key import UpstreamGeneratedKey
|
||||
from app.models.website import Website, WebsiteGroupBinding, WebsiteSyncLog
|
||||
from app.services.website_client import Sub2ApiWebsiteClient, WebsiteError, _extract_id, calculate_target_rate, decimal_string
|
||||
from app.services.website_client import Sub2ApiWebsiteClient, WebsiteError, _extract_id, calculate_target_rate
|
||||
from app.services.upstream_client import UpstreamClient
|
||||
from app.services import webhook_service
|
||||
from app.utils.number import fixed_decimal_string
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -91,8 +92,8 @@ def _log(
|
||||
algorithm=binding.algorithm,
|
||||
percent=binding.percent,
|
||||
source_rates_json=json.dumps(source_rates, ensure_ascii=False),
|
||||
old_rate=decimal_string(old_rate) if old_rate not in (None, "") else None,
|
||||
new_rate=decimal_string(new_rate) if new_rate not in (None, "") else None,
|
||||
old_rate=fixed_decimal_string(old_rate, 2) if old_rate not in (None, "") else None,
|
||||
new_rate=fixed_decimal_string(new_rate, 2) if new_rate not in (None, "") else None,
|
||||
status=status,
|
||||
message=message,
|
||||
)
|
||||
@@ -147,7 +148,11 @@ def sync_binding(db: Session, binding: WebsiteGroupBinding, write: bool = True)
|
||||
groups = client.get_groups(website.groups_endpoint)
|
||||
target = next((item for item in groups if item.get("id") == binding.target_group_id), None)
|
||||
old_rate = target.get("rate_multiplier") if target else None
|
||||
client.update_group_rate(website.group_update_endpoint, binding.target_group_id, target_rate)
|
||||
client.update_group_rate(
|
||||
website.group_update_endpoint,
|
||||
binding.target_group_id,
|
||||
target_rate,
|
||||
)
|
||||
website.last_status = "healthy"
|
||||
website.last_error = None
|
||||
except Exception as exc:
|
||||
@@ -157,8 +162,8 @@ def sync_binding(db: Session, binding: WebsiteGroupBinding, write: bool = True)
|
||||
return _log(db, binding, website, source_rates, "failed", f"写回失败:{exc}", old_rate, target_rate)
|
||||
db.commit()
|
||||
log = _log(db, binding, website, source_rates, "success", "同步成功", old_rate, target_rate)
|
||||
old_rate_str = decimal_string(old_rate) if old_rate not in (None, "") else None
|
||||
new_rate_str = decimal_string(target_rate)
|
||||
old_rate_str = fixed_decimal_string(old_rate, 2) if old_rate not in (None, "") else None
|
||||
new_rate_str = fixed_decimal_string(target_rate, 2)
|
||||
if old_rate_str != new_rate_str:
|
||||
webhook_service.send_website_rate_changed(
|
||||
db,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Shared numeric formatting utilities."""
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
|
||||
from typing import Any
|
||||
|
||||
|
||||
@@ -23,3 +23,20 @@ def decimal_string(value: Any) -> str:
|
||||
if n == n.to_integral():
|
||||
return str(n.quantize(Decimal("1")))
|
||||
return format(n, "f")
|
||||
|
||||
|
||||
def fixed_decimal_string(value: Any, places: int = 2) -> str:
|
||||
"""Format a numeric value with a fixed number of decimal places."""
|
||||
if value is None or value == "":
|
||||
return ""
|
||||
try:
|
||||
d = Decimal(str(value))
|
||||
except (InvalidOperation, ValueError):
|
||||
return str(value)
|
||||
quantum = Decimal("1").scaleb(-places)
|
||||
return format(d.quantize(quantum, rounding=ROUND_HALF_UP), f".{places}f")
|
||||
|
||||
|
||||
def fixed_decimal_number(value: Any, places: int = 2) -> float:
|
||||
"""Round a numeric value to a fixed precision for JSON number payloads."""
|
||||
return float(fixed_decimal_string(value, places))
|
||||
|
||||
Reference in New Issue
Block a user