From 442876c812df33526da8328e82e09b00ff68afca Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Fri, 5 Jun 2026 08:48:45 +0700 Subject: [PATCH] feat(trading): show remaining VND after buy/sell success --- internal/modules/trading/handlers.go | 6 +- .../debugger-2026-05-22-stats-failure.md | 115 ++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 plans/reports/debugger-2026-05-22-stats-failure.md diff --git a/internal/modules/trading/handlers.go b/internal/modules/trading/handlers.go index fb09120..1f558f6 100644 --- a/internal/modules/trading/handlers.go +++ b/internal/modules/trading/handlers.go @@ -156,7 +156,8 @@ func (s *state) handleBuy(ctx context.Context, b *bot.Bot, update *models.Update } return chathelper.Reply(ctx, b, update.Message, "Bought "+FormatStock(float64(qty))+" "+resolved.Symbol+ - " @ "+FormatVND(price)+"\nCost: "+FormatVND(cost)) + " @ "+FormatVND(price)+"\nCost: "+FormatVND(cost)+ + "\nRemaining: "+FormatVND(p.Currency["VND"])) } func (s *state) handleSell(ctx context.Context, b *bot.Bot, update *models.Update) error { @@ -215,7 +216,8 @@ func (s *state) handleSell(ctx context.Context, b *bot.Bot, update *models.Updat } return chathelper.Reply(ctx, b, update.Message, "Sold "+FormatStock(float64(qty))+" "+resolved.Symbol+ - " @ "+FormatVND(price)+"\nRevenue: "+FormatVND(revenue)) + " @ "+FormatVND(price)+"\nRevenue: "+FormatVND(revenue)+ + "\nRemaining: "+FormatVND(p.Currency["VND"])) } func (s *state) handleConvert(ctx context.Context, b *bot.Bot, update *models.Update) error { diff --git a/plans/reports/debugger-2026-05-22-stats-failure.md b/plans/reports/debugger-2026-05-22-stats-failure.md new file mode 100644 index 0000000..9668075 --- /dev/null +++ b/plans/reports/debugger-2026-05-22-stats-failure.md @@ -0,0 +1,115 @@ +# /stats failure root-cause report — 2026-05-22 + +**Status:** DONE + +**Summary:** +The `stats` module is absent from the live Lambda's `MODULES` env var (`util,misc,wordle,loldle,lolschedule,twentyq,trading` — 7 entries, no `stats`). Consequently `/stats` commands arrive, are logged as dispatched, but no handler is registered so no reply is sent. The cause is commit `5e40ffe`: when it hardcoded non-secret CFN params in `deploy.yml` to fix the `sed` regex breakage, it listed `BotOwnerID` and `AdminUserIDs` but omitted `ModulesCSV`, leaving `sam deploy --parameter-overrides` without it. Because SAM CLI `--parameter-overrides` *replaces* (not merges with) `samconfig.toml`, the stack retained its previously-deployed value of the param — which at that point did not yet include `stats`. + +--- + +## Evidence + +### 1. Live Lambda env — `stats` absent from MODULES + +``` +aws lambda get-function-configuration --function-name miti99bot ... +MODULES = "util,misc,wordle,loldle,lolschedule,twentyq,trading" +``` +7 entries. `stats` is not present. + +### 2. Deployed CloudFormation stack parameter confirms the same + +```json +{ "ParameterKey": "ModulesCSV", "ParameterValue": "util,misc,wordle,loldle,lolschedule,twentyq,trading" } +``` +(from `aws cloudformation describe-stacks`) + +### 3. Template default has `stats`; samconfig.toml has `stats` + +`template.yaml:17`: +```yaml +ModulesCSV: + Default: util,misc,wordle,loldle,lolschedule,twentyq,trading,stats +``` + +`samconfig.toml:16` (parameter_overrides): +``` +ModulesCSV="util,misc,wordle,loldle,lolschedule,twentyq,trading,stats" +``` + +Neither is used in CI — `sam deploy --parameter-overrides "$OVERRIDES"` overrides everything. + +### 4. deploy.yml OVERRIDES string omits ModulesCSV + +`.github/workflows/deploy.yml:62`: +```bash +OVERRIDES="CronSharedSecret=$CRON_SECRET BotOwnerID=1064111334 AdminUserIDs=1064111334" +``` +`ModulesCSV` is not included. This was introduced by commit `5e40ffe` ("fix(deploy): hardcode non-secret CFN params instead of sed-parsing samconfig"). That commit fixed the `sed` regex breakage (commit `85579e5`) but did not carry over `ModulesCSV`. + +### 5. CloudWatch logs: `/stats` dispatched, zero handler hits + +``` +2026-05-22T08:26:01Z {"msg":"dispatch","text":"/stats@miti99bot"} +2026-05-22T08:34:14Z {"msg":"dispatch","text":"/stats@miti99bot"} +2026-05-22T08:47:02Z {"msg":"dispatch","text":"/stats@miti99bot"} +2026-05-22T09:06:17Z {"msg":"dispatch","text":"/stats@miti99bot"} +2026-05-22T09:49:35Z {"msg":"dispatch","text":"/stats@miti99bot"} +``` + +Metrics logs never record `stats` command; only `trade_stats`: +``` +2026-05-22T08:31:24Z {"msg":"metrics","commands":{"trade_stats":1}} +2026-05-22T08:40:01Z {"msg":"metrics","commands":{"trade_stats":1}} +2026-05-22T09:12:00Z {"msg":"metrics","commands":{"trade_stats":1}} +``` + +### 6. `modules loaded` confirms 7 modules, not 8 + +``` +2026-05-22T08:25:49Z {"msg":"modules loaded","modules":7,"commands":28,"crons":1} +``` +Every cold start since `5e40ffe` merged shows `modules:7`. + +### 7. DynamoDB table has zero `stats`-module rows + +Full table scan (`miti99bot-data`, 23 items): no item with `pk = "stats"`. The module has never run in production. (`count:*` and `stats:count:*` prefix scans also return 0 rows — confirming no data written under alternative key schemes.) + +### 8. Code path — why `/stats` produces no reply when module absent + +- `cmd/server/main.go:114`: `modules.Build(cfg.Modules, factories(), ...)` — only builds modules listed in `cfg.Modules` (= `MODULES` env). +- `internal/modules/registry.go:120-186`: `stats` factory is never called; `/stats` command never registered in `reg.AllCommands`. +- `internal/modules/dispatcher.go:57-84`: `modules.Install` only registers handlers for commands in `reg.AllCommands`. No entry for `stats` → `matchCommand("stats", update)` never fires → bot silently ignores the update. + +--- + +## Root Cause + +Commit `5e40ffe` hardcoded `BotOwnerID` and `AdminUserIDs` into `deploy.yml`'s `OVERRIDES` string but omitted `ModulesCSV`. Because `sam deploy --parameter-overrides` replaces `samconfig.toml`'s overrides entirely (SAM CLI behavior), the stack deployed with `ModulesCSV` at the CFN-stored value from the prior deploy — which predated the `stats` addition — and the `stats` module was never enabled in production. + +--- + +## Recommended Fix + +**File:** `.github/workflows/deploy.yml`, line 62 + +Add `ModulesCSV` to the `OVERRIDES` string: + +```bash +OVERRIDES="CronSharedSecret=$CRON_SECRET BotOwnerID=1064111334 AdminUserIDs=1064111334 ModulesCSV=util,misc,wordle,loldle,lolschedule,twentyq,trading,stats" +``` + +This is the only change needed. No Lambda code changes required. After the next deploy the `MODULES` env will include `stats`, the module will load, and `/stats` will reply. + +**Alternatively** (more robust long-term): read `ModulesCSV` from `samconfig.toml` at deploy time using `tomlq` or a simple `grep`/`awk` so `samconfig.toml` remains the single source of truth: +```bash +MODULES_CSV=$(grep 'ModulesCSV=' samconfig.toml | sed 's/.*ModulesCSV="\([^"]*\)".*/\1/') +OVERRIDES="... ModulesCSV=$MODULES_CSV" +``` +But the direct hardcode is sufficient and consistent with how `BotOwnerID` is handled. + +--- + +## Unresolved Questions + +None. Root cause and fix are unambiguous.