Files
ray zhou f71a5c59af ok
2026-05-29 11:21:40 +08:00

15 lines
26 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{"role":"user","message":{"content":[{"type":"text","text":"<user_query>\nExplore the slot_console codebase at /Users/ray/Documents/project/www/slot/slot_console to understand the Free Credits (首充前免费余额定格与分档释放) feature data model and innerapi.\n\nSpecifically, I need:\n\n1. **Data tables**: Find all DB tables/Models related to Free Credits (activity type 11). Common patterns:\n - User Free Credits Pool (user_free_credits / s_free_credits_pool / similar)\n - Tier/Package list (free_credits_tier / package)\n - Activity log\n Show:\n - Full file paths to Model files (e.g., `slot_console/app/.../FreeCreditsPoolModel.php`)\n - Table name & key columns (with type/comment when present)\n - Status enums (e.g., what values 'frozen', 'first_cash_ready', 'completed' map to; tier states locked/ready/claimed/completed)\n - Total amount field name (frozen_amount?), withdrawn amount, claimed amount, etc.\n - Created_at / completed_at fields\n\n2. **FreeCreditsLogic class**: Read this file fully (likely under `slot_console/app/.../logic/FreeCreditsLogic.php` or app/.../activity/). Show class methods and what data they expose.\n\n3. **Existing innerapi for activity stats**: Look in `slot_console/app/innerapi/controller/` (or similar) for any existing activity statistics endpoints (search for 'statistics', 'stats', '统计', 'list users' patterns). Get a sample existing innerapi controller method to understand the pattern.\n\n4. **Activity config model**: Confirm `RechargeGiftConfigModel::TYPE_FREE_CREDITS = 11` and show the ext_config keys actually used at runtime (`*_qf` keys).\n\nReturn: full file paths, table schemas (column names + comments), enum values, and one example existing innerapi controller method to mimic. Be very thorough — this will be used to design backend statistics API.\n</user_query>"}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"Exploring the slot_console codebase for Free Credits data models, logic, and innerapi patterns.\n\n[REDACTED]"},{"type":"tool_use","name":"updateCurrentStep","input":{"current_step":"Exploring Free Credits models"}},{"type":"tool_use","name":"Grep","input":{"pattern":"free_credits|FreeCredits|TYPE_FREE_CREDITS|activity.*11","path":"/Users/ray/Documents/project/www/slot/slot_console","-i":true,"output_mode":"files_with_matches"}},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/*FreeCredits*","target_directory":"/Users/ray/Documents/project/www/slot/slot_console"}},{"type":"tool_use","name":"Grep","input":{"pattern":"TYPE_FREE_CREDITS|_qf","path":"/Users/ray/Documents/project/www/slot/slot_console","output_mode":"content","head_limit":80}},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/innerapi/**/*.php","target_directory":"/Users/ray/Documents/project/www/slot/slot_console"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/db/install.sql"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/model/common/FreeCreditsPlayerModel.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/model/common/FreeCreditsPackageModel.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/model/common/RechargeGiftConfigModel.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/innerapi/controller/FreeCreditsController.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/innerapi/controller/ActivityController.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"statistics|stats|统计|activity.*log|ActivityLog","path":"/Users/ray/Documents/project/www/slot/slot_console","-i":true,"output_mode":"content","head_limit":60}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"FreeCredits|free-credits|free_credits","path":"/Users/ray/Documents/project/www/slot/slot_console/config","output_mode":"content"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/config/free_credits.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/controller/FreeCreditsController.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"win_threshold|first_cash_amount|package_amount|recharge_unlock|subsequent_min|max_unlock","path":"/Users/ray/Documents/project/www/slot/slot_console","output_mode":"content","head_limit":50}},{"type":"tool_use","name":"Grep","input":{"pattern":"free_credits|FreeCredits","path":"/Users/ray/Documents/project/www/slot/slot_console","glob":"**/route*.php","output_mode":"content"}},{"type":"tool_use","name":"Grep","input":{"pattern":"innerapi.*FreeCredits|FreeCredits.*statistics","path":"/Users/ray/Documents/project/www/slot/slot_console","output_mode":"content"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/*route*","target_directory":"/Users/ray/Documents/project/www/slot/slot_console"}},{"type":"tool_use","name":"Grep","input":{"pattern":"FreeCreditsController|freeCredits|free-credits","path":"/Users/ray/Documents/project/www/slot/slot_console","output_mode":"content"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/innerapi/controller/BaseController.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/doc/首充前免费余额定格与分档释放需求部署说明.md","limit":120}},{"type":"tool_use","name":"Grep","input":{"pattern":"free_credits|freeCredits","path":"/Users/ray/Documents/project/www/slot/slot_wallet","output_mode":"files_with_matches","head_limit":20}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/config/route.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/innerapi/controller/SignController.php","limit":80}},{"type":"tool_use","name":"Grep","input":{"pattern":"ext_config|TYPE_FREE_CREDITS|type.*11","path":"/Users/ray/Documents/project/www/slot/docs","glob":"**/*免费*","output_mode":"content","head_limit":40}},{"type":"tool_use","name":"Grep","input":{"pattern":"win_threshold_qf|first_cash_amount_qf","path":"/Users/ray/Documents/project/www/slot/docs","output_mode":"content","head_limit":30}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"innerapi|free-credits|FreeCredits","path":"/Users/ray/Documents/project/www/slot/slot_console","glob":"**/*","output_mode":"content","head_limit":80}},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/plugin.php","target_directory":"/Users/ray/Documents/project/www/slot/slot_console"}},{"type":"tool_use","name":"Grep","input":{"pattern":"statistics|/innerapi","path":"/Users/ray/Documents/project/www/slot/slot_lib","output_mode":"content","head_limit":40}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"config/*.php","target_directory":"/Users/ray/Documents/project/www/slot/slot_console"}},{"type":"tool_use","name":"Grep","input":{"pattern":"free-credits|free_credits|FreeCredits","path":"/Users/ray/Documents/project/www/slot/docs/requirements","output_mode":"content","head_limit":50}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/event/FreeCreditInitEvent.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/innerapi/controller/admin/DashboardController.php","limit":60}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"ext_config|win_threshold|first_cash|package_amount|recharge_unlock|subsequent_min|max_unlock","path":"/Users/ray/Documents/project/www/slot/docs/requirements/首充前免费余额定格与分档释放需求文档.md","output_mode":"content","head_limit":60}},{"type":"tool_use","name":"Grep","input":{"pattern":"free_credits_player|free_credits_package|s_recharge_gift","path":"/Users/ray/Documents/project/www/slot/docs","output_mode":"content","head_limit":40}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/config/middleware.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/config/app.php","limit":80}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/docs/requirements/首充前免费余额定格与分档释放需求文档.md","offset":980,"limit":150}},{"type":"tool_use","name":"Grep","input":{"pattern":"ext_config|千分位|_qf","path":"/Users/ray/Documents/project/www/slot/docs/requirements/首充前免费余额定格与分档释放需求文档.md","output_mode":"content","head_limit":40}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/tests/Support/FreeCreditsConfigStub.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"free_credits|FreeCredits","path":"/Users/ray/Documents/project/www/slot/slot_center","output_mode":"files_with_matches","head_limit":15}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"TYPE_FREE_CREDITS|free.?credits|11","path":"/Users/ray/Documents/project/www/slot/slot_console/app/entity","output_mode":"content","head_limit":30}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/event/RechargeEvent.php","limit":80}},{"type":"tool_use","name":"Grep","input":{"pattern":"free-credits|freeCredits","path":"/Users/ray/Documents/project/www/slot/slot_console","glob":"**/*.md","output_mode":"content"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/napi/controller/LobbyController.php","limit":40}},{"type":"tool_use","name":"Grep","input":{"pattern":"class BaseController","path":"/Users/ray/Documents/project/www/slot/slot_lib","output_mode":"files_with_matches"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/tests/Unit/FreeCreditsLogicConfigTest.php"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"free_credits","path":"/Users/ray/Documents/project/www/slot/slot_wallet/app","output_mode":"content","head_limit":40}},{"type":"tool_use","name":"updateCurrentStep","input":{"current_step":"Compiling exploration report","final_summary":"Free Credits in slot_console uses two s_common tables plus s_recharge_gift_config (type 11), with statistics/list innerapi already implemented. No dedicated activity log table—state lives in player/package rows and wallet biz_ids.","completed_subtitle":"Mapped Free Credits data model"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"FreeCredits|freeCredits","path":"/Users/ray/Documents/project/www/slot/slot_wallet","output_mode":"content","head_limit":25}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"# Free Credits in `slot_console` — Data Model & InnerAPI Reference\n\nExploration of `/Users/ray/Documents/project/www/slot/slot_console` for activity type **11** (首充前免费余额定格与分档释放).\n\n---\n\n## 1. Data tables & models\n\nThere is **no dedicated Free Credits activity-log table** in `slot_console`. State is in two `s_common` tables plus the shared activity config table. Wallet/pay side effects use `slot_wallet` / `slot_pay` (not modeled in console).\n\n### Model file paths\n\n| Model | Path |\n|-------|------|\n| Player pool | `/Users/ray/Documents/project/www/slot/slot_console/app/model/common/FreeCreditsPlayerModel.php` |\n| Tier/package | `/Users/ray/Documents/project/www/slot/slot_console/app/model/common/FreeCreditsPackageModel.php` |\n| Activity config | `/Users/ray/Documents/project/www/slot/slot_console/app/model/common/RechargeGiftConfigModel.php` |\n\nDDL source: `/Users/ray/Documents/project/www/slot/slot_console/db/install.sql`\n\n---\n\n### `s_common.free_credits_player` (player pool)\n\n| Column | Type | Comment |\n|--------|------|---------|\n| `id` | bigint unsigned PK | 主键 |\n| `activity_id` | bigint unsigned | 关联 `s_recharge_gift_config.id` |\n| `uid` | bigint unsigned | 用户ID |\n| `source` | varchar(64) | 渠道 |\n| `home_withdraw_unlocked` | tinyint unsigned | 首页 Withdraw 是否已解锁 0否 1是 |\n| `frozen_amount_qf` | bigint unsigned | **首充时定格金额**,千分位 |\n| `first_cash_amount_qf` | bigint unsigned | **第一档免打码提现金额**,千分位 |\n| `first_recharge_order_id` | varchar(64) | 触发定格的首笔充值订单号 |\n| `first_recharge_time` | datetime nullable | **首笔充值成功时间**(定格时间) |\n| `first_cashout_order_id` | varchar(64) | 第一档独立提现订单号 |\n| `status` | tinyint unsigned | 主状态(见 enums below |\n| `completed_time` | datetime nullable | **全部完成时间** |\n| `create_time` | datetime | 创建时间 |\n| `update_time` | datetime | 更新时间 |\n\n**Unique:** `(activity_id, uid)` \n**Indexes:** `uid`, `(activity_id, status)`, `first_recharge_order_id`\n\n**No columns** for aggregate withdrawn/claimed totals or cumulative recharge — those are derived from `free_credits_package` sums or wallet stats at read time.\n\n---\n\n### `s_common.free_credits_package` (tiers)\n\n| Column | Type | Comment |\n|--------|------|---------|\n| `id` | bigint unsigned PK | 主键 |\n| `player_id` | bigint unsigned | `free_credits_player.id` |\n| `activity_id` | bigint unsigned | 关联活动配置 |\n| `uid` | bigint unsigned | 用户ID |\n| `package_no` | int unsigned | 档位序号从1开始 |\n| `package_type` | tinyint unsigned | 1第一档免打码提现 2后续释放档 |\n| `amount_qf` | bigint unsigned | **档位金额**,千分位 |\n| `status` | tinyint unsigned | 档位状态(见 enums |\n| `withdraw_order_id` | varchar(64) | 第一档提现订单号 |\n| `claim_biz_id` | varchar(64) nullable | 后续档 Claim 幂等业务号 |\n| `unlocked_time` | datetime nullable | 解锁时间 |\n| `claimed_time` | datetime nullable | **领取时间**release claim |\n| `completed_time` | datetime nullable | **完成时间** |\n| `create_time` / `update_time` | datetime | 创建/更新 |\n\n**Unique:** `(player_id, package_no)`, `(claim_biz_id)`\n\n**Amount semantics for stats:**\n- **First cashout (withdrawn):** `package_type=1` AND `status=3` (completed) → sum `amount_qf`\n- **Release claimed:** `package_type=2` AND `status=3` → sum `amount_qf`\n- **Pending release:** often computed as `frozen_amount_qf - first_cashout_completed - release_claimed` (see existing innerapi)\n\n---\n\n### `s_recharge_gift_config` (activity config, type 11)\n\n**Model:** `RechargeGiftConfigModel` → table `s_recharge_gift_config` (not in `install.sql`; shared recharge-gift table).\n\n**Confirmed constant:**\n\n```70:71:/Users/ray/Documents/project/www/slot/slot_console/app/model/common/RechargeGiftConfigModel.php\n /** 首充前免费余额定格与分档释放;配置存 ext_config玩家状态见 free_credits_* 表 */\n const TYPE_FREE_CREDITS = 11;\n```\n\nLookup: `RechargeGiftConfigModel::getFreeCreditsActiveInfo($source)` — `status=1`, `type=11`, time window, source priority.\n\nRelevant config columns: `id`, `type`, `status`, `start_time`, `end_time`, `source`, `ext_config` (JSON), `title`, etc.\n\n---\n\n### Player main status (`FreeCreditsPlayerModel`)\n\n| Value | Constant | Meaning |\n|------:|----------|---------|\n| -1 | `STATUS_NOT_VISIBLE` | C端/API only非库字段 |\n| 0 | `STATUS_NOT_STARTED` | 活动未开始 |\n| 1 | `STATUS_HOME_UNLOCKED` | 首页 Withdraw 已解锁,未首充定格 |\n| 2 | `STATUS_FROZEN` | 已首充定格 |\n| 3 | `STATUS_DEPOSIT_PENDING` | 等待累计充值解锁第一档 |\n| 4 | `STATUS_FIRST_CASH_READY` | 第一档可提现 |\n| 5 | `STATUS_FIRST_CASH_PROCESSING` | 第一档提现处理中 |\n| 6 | `STATUS_FIRST_CASH_DONE` | 第一档已提现 |\n| 7 | `STATUS_RELEASING` | 后续档位释放中 |\n| 10 | `STATUS_COMPLETED` | 全部档位已完成 |\n\n`home_withdraw_unlocked`: `0` = locked, `1` = unlocked.\n\n---\n\n### Package status (`FreeCreditsPackageModel`)\n\n| Value | Constant | Meaning |\n|------:|----------|---------|\n| 0 | `STATUS_LOCKED` | 未解锁 |\n| 1 | `STATUS_READY` | 可操作 |\n| 2 | `STATUS_PROCESSING` | 处理中 |\n| 3 | `STATUS_COMPLETED` | 已完成 |\n| 4 | `STATUS_FAILED` | 失败可重试 |\n| 5 | `STATUS_REJECTED` | 风控/审核拒绝 |\n\n**Package type:** `1` = `TYPE_FIRST_CASH`, `2` = `TYPE_RELEASE`.\n\n---\n\n### Related non-table artifacts\n\n- **Env:** `FREE_CREDITS_ENROLL_REG_AFTER` — reg-time gate (`config/free_credits.php`, `FreeCreditsLogic::isEligibleByRegTime`)\n- **MQ types:** `FreeCreditsFirstCashoutSuccess/Fail/Rejected` in `MQBusEntity`\n- **Wallet biz (slot_wallet):** `freeCreditsFreeze`, `freeCreditsClaim`; pay `bizType=free_credit_first_cashout`\n- **`user_dot`** in `install.sql` is generic tracking, not Free Creditsspecific\n\n---\n\n## 2. `FreeCreditsLogic` — methods & exposed data\n\n**Path:** `/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php`\n\n| Method | Role | Returns / side effects |\n|--------|------|------------------------|\n| `calcFrozenAmount` | Static: balance after recharge recharge amount | int qf |\n| `calcFirstCashAmount` | Static: min(frozen, configured first cash) | int qf |\n| `splitReleasePackageAmounts` | Static: split remainder into release tiers | `int[]` |\n| `status($uid)` | C端状态 | `{status, home_withdraw_unlocked?, frozen_amount, first_cash_amount, recharge_unlock_amount, packages[]}` or `{status:-1}` |\n| `syncHomeWithdrawUnlocked($uid)` | Unlock home Withdraw when balance ≥ win_threshold | `FreeCreditsPlayerModel|null` |\n| `getFreeCreditsPlayerModel($uid)` | Load player row if activity + record exist | model or null |\n| `handleFreeCreditInit(...)` | First-recharge freeze (MQ `free_credit_init`) | void; creates player + packages |\n| `advanceAfterRecharge($uid, $walletAmount)` | Post-recharge tier unlock | void |\n| `handleRecharge(...)` | **Deprecated** for freeze; only advances non-first | void |\n| `claim($uid, $packageId)` | Release tier claim → wallet | same as `status()` |\n| `markFirstCashoutProcessing` | After first cashout submit | void |\n| `firstCashout($uid, $packageId, $post)` | Pay apply for tier 1 | `{order_id, amount}` (client float) |\n| `handleFirstCashoutResult($orderId, $success, $rejected)` | MQ callback | updates package + player status |\n| `freezeFirstRecharge` (protected) | DB txn + wallet freeze + package creation | `FreeCreditsPlayerModel` |\n| `buildStatus` (protected) | Internal status DTO | array (client amounts formatted) |\n| `resetEnrollRegAfterTimestampCache` | Test helper | void |\n\n**`status()` / `buildStatus()` payload (when visible):**\n\n```php\n[\n 'status' => int, // player.status\n 'home_withdraw_unlocked' => int,\n 'frozen_amount' => float, // from frozen_amount_qf\n 'first_cash_amount' => float, // from first_cash_amount_qf\n 'recharge_unlock_amount' => float, // from ext_config\n 'packages' => [ // per tier\n 'id', 'package_no', 'package_type', 'amount' (float), 'status'\n ],\n]\n```\n\nAll internal DB/wallet math uses **千分位 `_qf`**; client fields use `CommonFn::getNumberFormat`.\n\n---\n\n## 3. InnerAPI — existing stats & list\n\n**Controller:** `/Users/ray/Documents/project/www/slot/slot_console/app/innerapi/controller/FreeCreditsController.php` \n**Extends:** `slotLib\\basic\\BaseController` \n**Middleware:** `innerapi` → `InnerAuthMiddleware` (`config/middleware.php`)\n\nWebman convention routes (no explicit entry in `config/route.php`):\n\n- `POST/GET /innerapi/free-credits/statistics`\n- `POST/GET /innerapi/free-credits/list`\n\n### `statistics()` — already implements §18.1-style aggregates\n\n```21:68:/Users/ray/Documents/project/www/slot/slot_console/app/innerapi/controller/FreeCreditsController.php\n public function statistics(Request $request)\n {\n $activityId = intval($request->input('activity_id', 0));\n $query = FreeCreditsPlayerModel::where([]);\n if ($activityId > 0) {\n $query->where('activity_id', $activityId);\n }\n\n $playerIds = $query->column('id');\n // ... empty → zeros ...\n\n $frozenTotal = FreeCreditsPlayerModel::whereIn('id', $playerIds)->sum('frozen_amount_qf');\n $completedCount = FreeCreditsPlayerModel::whereIn('id', $playerIds)\n ->where('status', FreeCreditsPlayerModel::STATUS_COMPLETED)\n ->count();\n $firstCashoutCount = FreeCreditsPackageModel::whereIn('player_id', $playerIds)\n ->where('package_type', FreeCreditsPackageModel::TYPE_FIRST_CASH)\n ->where('status', FreeCreditsPackageModel::STATUS_COMPLETED)\n ->count();\n // ... sums on package amount_qf ...\n\n return $this->success([\n 'frozen_user_count' => count($playerIds),\n 'first_cashout_user_count' => $firstCashoutCount,\n 'completed_user_count' => $completedCount,\n 'frozen_amount' => $frozenTotal,\n 'first_cashout_amount' => $firstCashoutAmount,\n 'claimed_amount' => $claimedAmount,\n 'pending_amount' => max($frozenTotal - $firstCashoutAmount - $claimedAmount, 0),\n ]);\n }\n```\n\n**Query param:** `activity_id` (optional). \n**All amounts in response are 千分位 sums**, not client floats.\n\n### `list()` — paginated players + nested packages\n\nParams: `page`, `limit` (max 100), `uid`, `activity_id`, `status`. \nReturns `{ total, list[] }` where each player row includes `packages[]` (raw DB columns).\n\n### Other innerapi controllers (pattern reference)\n\n- `/Users/ray/Documents/project/www/slot/slot_console/app/innerapi/controller/ActivityController.php` — generic activity CRUD via entities\n- `/Users/ray/Documents/project/www/slot/slot_console/app/innerapi/controller/SignController.php` — `input()` + `success()` / `errorCode()`\n- `/Users/ray/Documents/project/www/slot/slot_console/app/innerapi/controller/admin/DashboardController.php` — uses `OutputService::ok()` (different style)\n\n**Best mimic target:** `FreeCreditsController::statistics` + `list` — same domain, same models, `slotLib\\basic\\BaseController::success()`.\n\n### C端 API (for contrast)\n\n`/Users/ray/Documents/project/www/slot/slot_console/app/api/controller/FreeCreditsController.php` \nRoutes: `/api/free-credits/status`, `/claim`, `/first-cashout` (JWT `api` middleware).\n\n---\n\n## 4. Activity config — `TYPE_FREE_CREDITS = 11` & `ext_config` keys\n\n### Runtime `ext_config` keys (via `configAmount` / `configValue`)\n\n`configAmount($config, $key, $default)`:\n1. Tries **`{key}_qf`** first (integer 千分位).\n2. Else **`{key}`** passed through `CommonFn::setNumberFormat()` (display unit → qf).\n\n| Logical key | `_qf` key used at runtime | Default (display unit before format) | Used for |\n|-------------|-------------------------|--------------------------------------|----------|\n| `win_threshold` | `win_threshold_qf` | 50 | Home Withdraw unlock |\n| `recharge_unlock_amount` | `recharge_unlock_amount_qf` | 50 | Cumulative recharge to unlock tier 1 |\n| `first_cash_amount` | `first_cash_amount_qf` | 20 | Tier 1 cap / amount |\n| `package_amount` | `package_amount_qf` | 10 | Release tier split size |\n| `subsequent_min_recharge` | `subsequent_min_recharge_qf` | 10 | Min single recharge to unlock next release tier |\n| `max_unlock_per_recharge` | *(no `_qf`)* | 1 | Integer count via `configValue` only |\n\nTests confirm `_qf` precedence, e.g. `win_threshold_qf: 50000` over `win_threshold: 1` in `FreeCreditsLogicConfigTest.php`.\n\n**Not in `ext_config`:** enroll cutoff — env `FREE_CREDITS_ENROLL_REG_AFTER` only.\n\n**Player table mirrors at freeze:** `frozen_amount_qf`, `first_cash_amount_qf` (snapshotted from config + calc at freeze time).\n\n---\n\n## 5. Design notes for extending statistics API\n\nRequirements doc §18 (`/Users/ray/Documents/project/www/slot/docs/requirements/首充前免费余额定格与分档释放需求文档.md`) asks for fields **not yet on player rows**:\n\n| Req field | Suggested source |\n|-----------|------------------|\n| 累计充值金额 | Wallet `r` / `WalletService` with `needStat=1` at query time |\n| 第一档状态 | `free_credits_package` where `package_no=1` |\n| 完成进度 | `count(completed packages) / count(packages)` |\n| 剩余未释放 | `frozen_amount_qf - sum(completed amounts)` or package-level pending |\n| 已领取金额 | sum release `type=2, status=3` |\n| 时间筛选 | `first_recharge_time`, `completed_time`, `create_time` on player |\n\nExisting `statistics()` counts **all players in scope** as `frozen_user_count` (any row in `free_credits_player`), not only `status >= FROZEN` — align product definition when extending.\n\n**Gap vs requirements:** no date/source filters on `statistics()` yet; `list()` lacks cumulative recharge, progress, and remaining amount.\n\n---\n\n## Quick file index\n\n| Purpose | Path |\n|---------|------|\n| Logic | `.../app/api/logic/FreeCreditsLogic.php` |\n| InnerAPI stats/list | `.../app/innerapi/controller/FreeCreditsController.php` |\n| DDL | `.../db/install.sql` |\n| Config model | `.../app/model/common/RechargeGiftConfigModel.php` |\n| Events | `.../app/command/event/FreeCreditInitEvent.php`, `RechargeEvent.php` |\n| Validator | `.../app/api/validator/FreeCreditsValidator.php` |\n\n[REDACTED]"}]}}