fix: 修正 Sub2API 利润币种语义

This commit is contained in:
Codex
2026-07-14 16:06:32 +02:00
parent 1e457e1004
commit be33671161
6 changed files with 40 additions and 33 deletions
@@ -210,9 +210,9 @@ function readProfitConfig(value: unknown): CodexPoolProfitConfig {
if (new Set(allGroups).size !== allGroups.length) {
throw new Error(`${codexPoolConfigPath}.profit.groups entries must be disjoint`);
}
const netRevenueCoefficient = numberValue(value.netRevenueCoefficient);
if (netRevenueCoefficient === null || netRevenueCoefficient < 0) {
throw new Error(`${codexPoolConfigPath}.profit.netRevenueCoefficient must be >= 0`);
const saleRateCnyPerApiUsd = numberValue(value.saleRateCnyPerApiUsd);
if (saleRateCnyPerApiUsd === null || saleRateCnyPerApiUsd < 0) {
throw new Error(`${codexPoolConfigPath}.profit.saleRateCnyPerApiUsd must be >= 0`);
}
const defaultWindow = requiredStringConfigField(value, "defaultWindow", "profit");
if (!/^[1-9][0-9]*(?:s|m|h|d)$/u.test(defaultWindow)) {
@@ -223,14 +223,14 @@ function readProfitConfig(value: unknown): CodexPoolProfitConfig {
throw new Error(`${codexPoolConfigPath}.profit.usageCostField must be actual_cost`);
}
const accountCostSource = requiredStringConfigField(value, "accountCostSource", "profit");
if (accountCostSource !== "account-name-trailing-coefficient") {
throw new Error(`${codexPoolConfigPath}.profit.accountCostSource must be account-name-trailing-coefficient`);
if (accountCostSource !== "account-name-trailing-cny-per-api-usd") {
throw new Error(`${codexPoolConfigPath}.profit.accountCostSource must be account-name-trailing-cny-per-api-usd`);
}
return {
defaultWindow,
usageCostField,
accountCostSource,
netRevenueCoefficient,
saleRateCnyPerApiUsd,
groups: { profitable, selfUse, excluded },
};
}
@@ -91,24 +91,24 @@ function renderProfit(response: Record<string, unknown>): RenderedCliResult {
const lines = [
`SUB2API PROFIT status=${text(report.status)} target=${text(record(response.target).id)} window=${text(window.since)}`,
`WINDOW ${text(window.startAt)} -> ${text(window.endAt)} SOURCE native-admin-usage/${text(record(report.source).usageCostField)}`,
`POLICY net-revenue=${text(policy.netRevenueCoefficient)} account-cost=${text(policy.accountCostSource)}`,
`POLICY sale-rate=${text(policy.saleRateCnyPerApiUsd)}CNY/API_USD account-cost=${text(policy.accountCostSource)}`,
"",
"ACCOUNTS",
];
const accounts = arrayOfRecords(report.accounts);
lines.push(table(
["GROUP", "CLASS", "ACCOUNT", "ID", "REQ", "TOKENS", "API_AMOUNT", "COST_RATE", "REVENUE", "UPSTREAM_COST", "CONTRIBUTION", "STATUS"],
["GROUP", "CLASS", "ACCOUNT", "ID", "REQ", "TOKENS", "API_USD", "COST_CNY/USD", "REVENUE_CNY", "UPSTREAM_CNY", "PROFIT_CNY", "STATUS"],
accounts.map((row) => [
text(row.groupName), text(row.class), text(row.accountName), text(row.accountId), text(row.requestCount), compactTokens(row.tokenCount),
usd(row.apiAmountUsd), text(row.costCoefficient), usd(row.revenueUsd), usd(row.upstreamCostUsd), usd(row.profitContributionUsd), text(row.completeness),
apiUsd(row.apiAmountUsd), text(row.costRateCnyPerApiUsd), cny(row.revenueCny), cny(row.upstreamCostCny), cny(row.profitContributionCny), text(row.completeness),
]),
));
lines.push("", "PROFIT");
lines.push(table(
["SCOPE", "STATUS", "REVENUE", "PROFIT_POOL_COST", "SELF_USE_COST", "PROFIT"],
["SCOPE", "STATUS", "REVENUE_CNY", "PROFIT_POOL_CNY", "SELF_USE_CNY", "PROFIT_CNY"],
[
["不扣自用", text(record(summary.withoutSelfUse).status), usd(summary.revenueUsd), usd(summary.profitPoolCostUsd), "-", usd(record(summary.withoutSelfUse).profitUsd)],
["扣除自用", text(record(summary.withSelfUse).status), usd(summary.revenueUsd), usd(summary.profitPoolCostUsd), usd(summary.selfUseCostUsd), usd(record(summary.withSelfUse).profitUsd)],
["不扣自用", text(record(summary.withoutSelfUse).status), cny(summary.revenueCny), cny(summary.profitPoolCostCny), "-", cny(record(summary.withoutSelfUse).profitCny)],
["扣除自用", text(record(summary.withSelfUse).status), cny(summary.revenueCny), cny(summary.profitPoolCostCny), cny(summary.selfUseCostCny), cny(record(summary.withSelfUse).profitCny)],
],
));
const missingCosts = arrayOfRecords(completeness.missingCostAccounts);
@@ -161,12 +161,18 @@ function compactTokens(value: unknown): string {
return String(numeric);
}
function usd(value: unknown): string {
function apiUsd(value: unknown): string {
if (value === null || value === undefined || value === "") return "-";
const numeric = Number(value);
return Number.isFinite(numeric) ? `$${numeric.toFixed(6)}` : text(value);
}
function cny(value: unknown): string {
if (value === null || value === undefined || value === "") return "-";
const numeric = Number(value);
return Number.isFinite(numeric) ? `¥${numeric.toFixed(6)}` : text(value);
}
function table(headers: string[], rows: string[][]): string {
if (rows.length === 0) return `${headers.join(" ")}\n-`;
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
@@ -112,7 +112,7 @@ def run_profit_report():
config = payload.get("config") if isinstance(payload.get("config"), dict) else {}
window = payload.get("since")
cost_field = config.get("usageCostField")
net_rate = profit_decimal(config.get("netRevenueCoefficient"))
sale_rate = profit_decimal(config.get("saleRateCnyPerApiUsd"))
end_at = datetime.now(timezone.utc)
start_at = profit_window_start(window, end_at)
_, token, _ = login()
@@ -149,14 +149,14 @@ def run_profit_report():
for aggregate in aggregates.values():
rate = profit_account_rate(aggregate["accountName"])
amount = aggregate.pop("apiAmount")
revenue = amount * net_rate if group_class == "profit" else profit_decimal(0)
revenue = amount * sale_rate if group_class == "profit" else profit_decimal(0)
upstream_cost = amount * rate if rate is not None and group_class in ("profit", "self-use") else profit_decimal(0)
aggregate["tokenCount"] = aggregate.pop("inputTokens") + aggregate.pop("outputTokens")
aggregate["apiAmountUsd"] = profit_decimal_text(amount)
aggregate["costCoefficient"] = profit_decimal_text(rate) if rate is not None else None
aggregate["revenueUsd"] = profit_decimal_text(revenue)
aggregate["upstreamCostUsd"] = profit_decimal_text(upstream_cost) if rate is not None or group_class not in ("profit", "self-use") else None
aggregate["profitContributionUsd"] = profit_decimal_text(revenue - upstream_cost) if rate is not None or group_class not in ("profit", "self-use") else None
aggregate["costRateCnyPerApiUsd"] = profit_decimal_text(rate) if rate is not None else None
aggregate["revenueCny"] = profit_decimal_text(revenue)
aggregate["upstreamCostCny"] = profit_decimal_text(upstream_cost) if rate is not None or group_class not in ("profit", "self-use") else None
aggregate["profitContributionCny"] = profit_decimal_text(revenue - upstream_cost) if rate is not None or group_class not in ("profit", "self-use") else None
aggregate["completeness"] = "complete" if rate is not None or group_class not in ("profit", "self-use") else "partial"
account_rows.append(aggregate)
group_amount += amount
@@ -175,9 +175,9 @@ def run_profit_report():
missing_profit = [row for row in profit_accounts if row["completeness"] != "complete"]
missing_self = [row for row in self_accounts if row["completeness"] != "complete"]
unclassified = [row for row in account_rows if row["class"] == "unclassified" and row["requestCount"] > 0]
revenue = sum((profit_decimal(row["revenueUsd"]) for row in profit_accounts), profit_decimal(0))
profit_cost = sum((profit_decimal(row["upstreamCostUsd"]) for row in profit_accounts if row["upstreamCostUsd"] is not None), profit_decimal(0))
self_cost = sum((profit_decimal(row["upstreamCostUsd"]) for row in self_accounts if row["upstreamCostUsd"] is not None), profit_decimal(0))
revenue = sum((profit_decimal(row["revenueCny"]) for row in profit_accounts), profit_decimal(0))
profit_cost = sum((profit_decimal(row["upstreamCostCny"]) for row in profit_accounts if row["upstreamCostCny"] is not None), profit_decimal(0))
self_cost = sum((profit_decimal(row["upstreamCostCny"]) for row in self_accounts if row["upstreamCostCny"] is not None), profit_decimal(0))
without_self = revenue - profit_cost
with_self = without_self - self_cost
without_complete = not missing_profit and not unclassified
@@ -193,11 +193,11 @@ def run_profit_report():
"groups": group_rows,
"accounts": account_rows,
"summary": {
"revenueUsd": profit_decimal_text(revenue),
"profitPoolCostUsd": profit_decimal_text(profit_cost),
"selfUseCostUsd": profit_decimal_text(self_cost),
"withoutSelfUse": {"status": "complete" if without_complete else "partial", "profitUsd": profit_decimal_text(without_self) if without_complete else None},
"withSelfUse": {"status": "complete" if with_complete else "partial", "profitUsd": profit_decimal_text(with_self) if with_complete else None},
"revenueCny": profit_decimal_text(revenue),
"profitPoolCostCny": profit_decimal_text(profit_cost),
"selfUseCostCny": profit_decimal_text(self_cost),
"withoutSelfUse": {"status": "complete" if without_complete else "partial", "profitCny": profit_decimal_text(without_self) if without_complete else None},
"withSelfUse": {"status": "complete" if with_complete else "partial", "profitCny": profit_decimal_text(with_self) if with_complete else None},
},
"completeness": {
"missingCostAccounts": [{"groupName": row["groupName"], "accountId": row["accountId"], "accountName": row["accountName"]} for row in missing_profit + missing_self],
@@ -184,8 +184,8 @@ export interface CodexRuntimeConfig {
export interface CodexPoolProfitConfig {
defaultWindow: string;
usageCostField: "actual_cost";
accountCostSource: "account-name-trailing-coefficient";
netRevenueCoefficient: number;
accountCostSource: "account-name-trailing-cny-per-api-usd";
saleRateCnyPerApiUsd: number;
groups: {
profitable: string[];
selfUse: string[];