docs(stock): record stock info delivery

This commit is contained in:
2026-07-23 12:27:12 +07:00
parent 26b969b5ef
commit 824e71b3ec
6 changed files with 303 additions and 0 deletions
@@ -0,0 +1,47 @@
# Stock Info Command
**Date**: 2026-07-23 12:25
**Severity**: Medium
**Component**: `stock` module, SSI quote client, command docs/tests
**Status**: Resolved
## What Happened
We added a new read-only `/stock_info <ticker>` command to show a compact SSI iBoard quote snapshot with company, exchange, current price, gain/loss since open, change versus reference price, open/high/low, and volume. `/stock_price` stayed unchanged on purpose.
The implementation had to be forced into a safer shape after review. The first pass reused the same SSI quote DTO for legacy price paths, which was a bad idea for an undocumented upstream schema. We split the detail response into its own DTO so the old decode path would not get dragged into the new fields, and we kept `/stock_price` on the existing fallback chain.
## The Brutal Truth
This was the kind of change that looks simple until it starts biting in review. The upstream API is undocumented, so every assumption becomes a liability. The frustrating part is that we had to spend time proving that the “easy” version would have been brittle: redirects, malformed fields, and bogus math all showed up as real failure modes. That is annoying, but it is also exactly why the extra guardrails were necessary.
## Technical Details
- `/stock_info` uses exactly one SSI GET and no KBS/VCI fallback.
- Since-open change is computed from `matchedPrice - openPrice`; reference change remains separate.
- Review blockers fixed:
- separate SSI detail DTO to protect legacy `/stock_price` decoding
- redirect refusal so the command cannot silently fan out into extra requests
- finite-safe change math to avoid `Inf%`
- explicit zero volume preserved as `0` while nil/invalid volume stays `N/A`
- company/exchange text bounded so Telegram replies stay safe
- Best-effort behavior is still required because SSI is undocumented.
- Verification passed: focused stock tests, race tests, `go vet ./...`, `go build ./...`, `golangci-lint run`, and repository test gates.
## What We Tried
- Started with a shared quote structure and a direct SSI detail fetch.
- Tightened the handler and formatting tests around the compact output.
- Reworked the client after adversarial review exposed the legacy decode and redirect risks.
## Root Cause Analysis
The root mistake was treating an undocumented provider like a stable contract. That made the first design too optimistic: one shared DTO, unchecked redirect behavior, and derived math that assumed normal values. The code worked until review forced the ugly cases into the open.
## Lessons Learned
When the upstream schema is not under our control, isolate the new path instead of extending old contracts in place. Keep the user-facing command additive, keep the legacy command stable, and assume the provider will return missing, malformed, or absurd values.
## Next Steps
Keep `/stock_info` best-effort and monitor for SSI schema drift. If the upstream shape changes again, update only the detail path and leave `/stock_price` untouched. No migration work is needed; the owner is the stock module maintainer.
@@ -0,0 +1,54 @@
---
phase: 1
title: Model SSI Quote Details
status: completed
priority: P2
dependencies: []
---
# Phase 1: Model SSI Quote Details
## Overview
Extend the existing SSI single-stock response with a read-only detailed quote
model and a dedicated one-request fetch method. Do not change `FetchPrice`.
## Requirements
- Functional: capture symbol, Vietnamese/English company name, exchange,
matched/open/reference/high/low prices, and normal traded quantity from SSI.
- Functional: perform one GET to `/stock/<ticker>` with existing SSI headers.
- Functional: reject missing/non-positive matched price as `ErrNoPrice`.
- Non-functional: preserve KBS/VCI/SSI fallback behavior for `FetchPrice` and
batch portfolio pricing.
## Architecture
Add an SSI-specific quote DTO and dedicated fetch method on `PriceClient`.
Reuse `newSSIRequest`, `doSSIJSON`, `baseURL`, and the shared HTTP client.
The command receives the single decoded response directly; it never calls
`FetchPrice`.
## Related Code Files
- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\prices_ssi.go`
- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\prices_test.go`
## Implementation Steps
1. Extend the SSI quote struct with only the fields used by `/stock_info`.
2. Add a dedicated detailed-quote fetch method that issues one GET.
3. Keep current `fetchSSIPrice`, `FetchPrice`, and `FetchPrices` contracts intact.
4. Test path, headers, one-call count, decoded fields, invalid price, HTTP, and decode errors.
## Success Criteria
- [x] Detailed quote fetch uses exactly one SSI GET.
- [x] Required and optional fields decode without inventing fallback requests.
- [x] Existing price and batch provider tests remain unchanged and pass.
## Risk Assessment
SSI is undocumented and may omit fields. Keep optional fields nullable by value
and make the handler degrade to `N/A`; isolate the new method from trading and
portfolio callers.
@@ -0,0 +1,58 @@
---
phase: 2
title: "Add Stock Info Command"
status: completed
priority: P2
dependencies: [1]
---
# Phase 2: Add Stock Info Command
## Overview
Register `/stock_info <ticker>` and render a compact, sender-independent quote
snapshot from the Phase 1 SSI detail method.
## Requirements
- Functional: exact syntax `/stock_info <ticker>` and public metadata `<ticker>`.
- Functional: normalize ticker; handle usage, unknown ticker, no price, upstream,
and decode failures with friendly replies.
- Functional: show Vietnamese company name with English fallback, exchange,
current price, since-open amount/percent, reference amount/percent,
open/high/low, and normal traded quantity.
- Non-functional: read-only, senderless, one SSI request, Telegram-safe text.
## Architecture
Create a narrow handler/formatter file. Calculate changes from matched price
against positive open/reference prices; otherwise show `N/A`. Reuse existing
VND, sign, and integer formatting where their output matches the command.
## Related Code Files
- Create: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\stock_info.go`
- Create: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\stock_info_test.go`
- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\stock.go`
- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\handlers_test.go`
- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\cmd\server\command_menu_test.go`
## Implementation Steps
1. Implement exact argument validation and ticker normalization.
2. Fetch with `chathelper.FetchContext` through the one-call SSI detail method.
3. Format compact fields with signed amount/percent and `N/A` for unavailable comparisons.
4. Register the command and update shared command-discovery expectations.
5. Test calculations, signs, missing fields, company fallback, senderless use,
request count, usage, no-price, and upstream errors.
## Success Criteria
- [x] `/stock_info TCB` returns the agreed compact quote in one SSI call.
- [x] Since-open and reference changes are mathematically correct and signed.
- [x] `/stock_price` registration, output, and fallbacks remain unchanged.
## Risk Assessment
Avoid calling `FetchPrice` from the new handler because it can trigger multiple
providers. Keep the detailed quote method private to the new command path.
@@ -0,0 +1,51 @@
---
phase: 3
title: "Verify and Document"
status: completed
priority: P2
dependencies: [1, 2]
---
# Phase 3: Verify and Document
## Overview
Align public documentation and prove the new one-call command does not change
existing quote, portfolio, event, or dividend behavior.
## Requirements
- Functional: document syntax, compact fields, one-call SSI-only behavior, and
best-effort upstream limitation in README.
- Non-functional: satisfy focused, repository, race, vet, build, lint, and diff gates.
## Architecture
No new runtime behavior. Verify command metadata/usage/docs as one contract and
exercise existing `/stock_price` plus portfolio quote callers as regressions.
## Related Code Files
- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\README.md`
- Verify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\prices_test.go`
- Verify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\stock_info_test.go`
- Verify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\cmd\server\command_menu_test.go`
## Implementation Steps
1. Update README with `/stock_info <ticker>` and its SSI-only behavior.
2. Run `gofmt` and focused stock/server command-discovery tests.
3. Run `go test -count=1 ./...` and stock race tests.
4. Run `go vet ./...`, `go build ./...`, `golangci-lint run`, and `git diff --check`.
5. Review all price callers and public contracts for unintended changes.
## Success Criteria
- [x] README, registration metadata, handler usage, and tests match exactly.
- [x] All focused and repository gates pass with no `/stock_price` regression.
- [x] Review confirms one SSI call and no storage/schema/provider-fallback changes.
## Risk Assessment
The SSI endpoint is undocumented. Document best-effort behavior and keep the
new surface additive so it can be removed without migration or data cleanup.
@@ -0,0 +1,60 @@
---
title: Stock Info Command
description: >-
Add a one-call SSI quote-detail command while preserving /stock_price and its
provider fallbacks.
status: completed
priority: P2
branch: main
tags:
- feature
- stock
- telegram
- api
blockedBy: []
blocks: []
created: '2026-07-23T03:17:04.304Z'
createdBy: 'ck:plan'
source: skill
---
# Stock Info Command
## Overview
Add public `/stock_info <ticker>` for a compact SSI quote snapshot. The command
uses exactly one SSI single-ticker GET and reports company, exchange, current
price, since-open and reference changes, open/high/low, and volume. Existing
`/stock_price`, batch quotes, portfolio valuation, and provider fallbacks stay
unchanged.
## Phases
| Phase | Name | Status |
|-------|------|--------|
| 1 | [Model SSI Quote Details](./phase-01-model-ssi-quote-details.md) | Completed |
| 2 | [Add Stock Info Command](./phase-02-add-stock-info-command.md) | Completed |
| 3 | [Verify and Document](./phase-03-verify-and-document.md) | Completed |
## Dependencies
- No cross-plan dependencies; all existing stock plans are completed.
- Reuse the current SSI request headers, client, timeout, and response envelope.
- No storage, portfolio, stats, event, dividend, migration, or environment changes.
## Contract
- Syntax: `/stock_info <ticker>`.
- One SSI request only; never fall back to KBS or VCI.
- Missing optional quote fields render consistently as `N/A`.
- A missing/non-positive matched price returns a friendly no-info response.
## Validation
- Focused provider/handler/metadata tests plus `/stock_price` regression coverage.
- `go test -count=1 ./...`, stock race tests, `go vet ./...`, `go build ./...`,
`golangci-lint run`, and `git diff --check`.
## Open Questions
None.
@@ -0,0 +1,33 @@
# PM Completion Report
- Plan: `Stock Info Command`
- Plan status: `completed`
- Date: `2026-07-23 12:24`
## Delivery
- Phase 1: completed
- Phase 2: completed
- Phase 3: completed
- Plan summary aligned to verified implementation and harness evidence
## Evidence
- Focused stock and server tests passed
- Stock race tests passed
- Full repository test suite passed
- `go vet` passed
- `go build` passed
- `golangci-lint run` passed with 0 issues
- `git diff --check` passed
- Live SSI quote verification passed
## Reconciliation
- No unresolved phase mappings
- No blockers
- No docs gap recorded in plan artifacts
## Next Step
- Main agent: finish any remaining implementation-plan closure work, then handle commit and push in the normal ship flow if still desired.