149 lines
4.9 KiB
Markdown
149 lines
4.9 KiB
Markdown
|
|
# AI Usage CNY Cost Implementation Plan
|
||
|
|
|
||
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
|
|
||
|
|
**Goal:** Make AI usage fees calculated and displayed as Chinese yuan.
|
||
|
|
|
||
|
|
**Architecture:** Keep the existing AI usage ledger API and database compatibility fields. Update `AiCostCalculator` to use CNY model rates while preserving cache-hit, cache-miss, and completion token calculation. Update the admin Vue page label and formatter to present CNY.
|
||
|
|
|
||
|
|
**Tech Stack:** Java, JUnit 4, Spring/MyBatis, Vue 2, Element UI.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### File Structure
|
||
|
|
|
||
|
|
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiCostCalculatorTest.java`
|
||
|
|
- Owns pricing expectations for Flash, Pro, default DeepSeek models, missing usage, and cent rounding.
|
||
|
|
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiUsageRecorderTest.java`
|
||
|
|
- Verifies recorded ledger/scope costs reflect the calculator's CNY amount.
|
||
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiCostCalculator.java`
|
||
|
|
- Owns token-to-money calculation and model rate selection.
|
||
|
|
- Modify: `ruoyi-ui/src/views/generator/aiUsage/index.vue`
|
||
|
|
- Owns AI usage fee labels and currency formatting.
|
||
|
|
|
||
|
|
### Task 1: Backend Pricing Tests
|
||
|
|
|
||
|
|
- [ ] **Step 1: Write failing calculator expectations**
|
||
|
|
|
||
|
|
Update expected values in `AiCostCalculatorTest`:
|
||
|
|
|
||
|
|
```java
|
||
|
|
assertEquals(300, calculator.estimateCostCents("deepseek", "deepseek-chat", 1_000_000, 1_000_000));
|
||
|
|
assertEquals(new BigDecimal("2.90200000"),
|
||
|
|
calculator.estimateCostUsd("deepseek", "deepseek-v4-flash", usage));
|
||
|
|
assertEquals(new BigDecimal("9.02500000"),
|
||
|
|
calculator.estimateCostUsd("deepseek", "deepseek-v4-pro", usage));
|
||
|
|
assertEquals(new BigDecimal("3.00000000"),
|
||
|
|
calculator.estimateCostUsd("deepseek", "deepseek-chat", usage));
|
||
|
|
```
|
||
|
|
|
||
|
|
- [ ] **Step 2: Verify tests fail under old USD rates**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mvn -pl ruoyi-generator -Dtest=AiCostCalculatorTest test
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: failures show old USD amounts such as `0.40628000` where CNY amounts are expected.
|
||
|
|
|
||
|
|
- [ ] **Step 3: Update recorder expectation**
|
||
|
|
|
||
|
|
In `AiUsageRecorderTest`, replace `0.40628000` expectations with `2.90200000`.
|
||
|
|
|
||
|
|
- [ ] **Step 4: Verify recorder test fails under old USD rates**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mvn -pl ruoyi-generator -Dtest=AiUsageRecorderTest test
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: failures show old USD amount `0.40628000` instead of `2.90000000`.
|
||
|
|
|
||
|
|
### Task 2: Backend CNY Implementation
|
||
|
|
|
||
|
|
- [ ] **Step 1: Replace calculator rate constants**
|
||
|
|
|
||
|
|
In `AiCostCalculator`, replace USD constants with CNY constants:
|
||
|
|
|
||
|
|
```java
|
||
|
|
private static final BigDecimal FLASH_CACHE_HIT_CNY_PER_1M = new BigDecimal("0.02");
|
||
|
|
private static final BigDecimal FLASH_CACHE_MISS_CNY_PER_1M = new BigDecimal("1");
|
||
|
|
private static final BigDecimal FLASH_OUTPUT_CNY_PER_1M = new BigDecimal("2");
|
||
|
|
private static final BigDecimal PRO_CACHE_HIT_CNY_PER_1M = new BigDecimal("0.025");
|
||
|
|
private static final BigDecimal PRO_CACHE_MISS_CNY_PER_1M = new BigDecimal("3");
|
||
|
|
private static final BigDecimal PRO_OUTPUT_CNY_PER_1M = new BigDecimal("6");
|
||
|
|
```
|
||
|
|
|
||
|
|
Keep public method names unchanged for compatibility.
|
||
|
|
|
||
|
|
- [ ] **Step 2: Update internal variable names where local only**
|
||
|
|
|
||
|
|
Rename local variables and `Rate` fields from `Usd` to neutral `Amount` or `Cny` inside `AiCostCalculator`. Do not rename public methods or domain fields in this task.
|
||
|
|
|
||
|
|
- [ ] **Step 3: Verify backend tests pass**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mvn -pl ruoyi-generator -Dtest=AiCostCalculatorTest,AiUsageRecorderTest test
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: both test classes pass.
|
||
|
|
|
||
|
|
### Task 3: Admin UI CNY Display
|
||
|
|
|
||
|
|
- [ ] **Step 1: Update label and formatter**
|
||
|
|
|
||
|
|
In `ruoyi-ui/src/views/generator/aiUsage/index.vue`:
|
||
|
|
|
||
|
|
```vue
|
||
|
|
<div class="metric-label">费用 CNY</div>
|
||
|
|
<div class="metric-value">{{ formatCny(summary.costUsd) }}</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
Use `formatCny` for row values and return a yuan-prefixed string:
|
||
|
|
|
||
|
|
```js
|
||
|
|
formatCny(value, cents) {
|
||
|
|
let amount = Number(value || 0)
|
||
|
|
if (!amount && cents) {
|
||
|
|
amount = Number(cents || 0) / 100
|
||
|
|
}
|
||
|
|
return `¥${amount.toFixed(8).replace(/0+$/, '').replace(/\.$/, '') || '0'}`
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
- [ ] **Step 2: Verify UI references**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
rg -n "formatUsd|费用 USD|\\$[0-9]" ruoyi-ui/src/views/generator/aiUsage/index.vue
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: no matches.
|
||
|
|
|
||
|
|
### Task 4: Final Verification
|
||
|
|
|
||
|
|
- [ ] **Step 1: Run targeted backend tests**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mvn -pl ruoyi-generator -Dtest=AiCostCalculatorTest,AiUsageRecorderTest test
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: exit code 0.
|
||
|
|
|
||
|
|
- [ ] **Step 2: Check changed file diff**
|
||
|
|
|
||
|
|
Run:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
git diff -- ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiCostCalculator.java ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiCostCalculatorTest.java ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiUsageRecorderTest.java ruoyi-ui/src/views/generator/aiUsage/index.vue
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected: diff only changes pricing tests, CNY rates, and UI CNY formatter/labels.
|