docs: correct stale module claims and document amlich edge cases

README omitted /lol_subscribe and /lol_unsubscribe, called gold opt-in
though an empty MODULES loads every catalog module, and left out both
the amlich 1800-2199 bound and the lol module's PandaScore token. The
gold factory comment repeated the same opt-in claim.

Promote the lunar algorithm decision record and known-issue list into
docs/ so they survive cleanups of plans/.
This commit is contained in:
2026-08-16 22:47:24 +07:00
parent 1652320150
commit a58aa150fe
3 changed files with 113 additions and 5 deletions
+21 -3
View File
@@ -9,12 +9,12 @@ Atlas via long polling and an in-process cron scheduler.
|---|---|
| `util` | `/help`, `/info`, `/stickerid` |
| `misc` | `/ping`, `/ping_stats`, `/random`, `/wheelofnames`, `/ff`, `/the_answer`, `/trongtruonghop` + `/tth`, `/trongtruonghopvng` + `/tthvng` disclaimers |
| `amlich` | Vietnamese lunar calendar: `/amlich` (dương lịch → âm lịch, defaults to today), `/duonglich` (âm lịch → dương lịch, `nhuan` flag for leap months); dates accept `d`, `d/m`, or `d/m/yyyy` — missing parts fill from today in the input's calendar |
| `amlich` | Vietnamese lunar calendar: `/amlich` (dương lịch → âm lịch, defaults to today), `/duonglich` (âm lịch → dương lịch, `nhuan` flag for leap months); dates accept `d`, `d/m`, or `d/m/yyyy` — missing parts fill from today in the input's calendar. Years 18002199 only |
| `wordle` | Daily Wordle game |
| `loldle` | League-of-Legends "guess the champion" |
| `lol` | Pro-match schedule (`/lol`, `/lol_tomorrow`, `/lol_this_week`, `/lol_next_week`) + daily push |
| `lol` | Pro-match schedule (`/lol`, `/lol_tomorrow`, `/lol_this_week`, `/lol_next_week`), per-chat digest opt-in (`/lol_subscribe`, `/lol_unsubscribe`) + daily push at 08:00 ICT |
| `stock` | VN-stocks paper trading |
| `gold` | Gold paper trading (opt-in; VNAppMob SJC buy/sell VND/luong) |
| `gold` | Gold paper trading (VNAppMob SJC buy/sell VND/luong) |
| `coin` | Crypto paper trading in USD (Binance -> Coinbase -> CoinGecko price fallback) |
| `stats` | `/stats` (top commands), `/stats users`, `/stats user <username>`, `/stats cmd <command_name>` |
| `monkeyd` | `/monkeyd_crawl <url> [font_size]` export a monkeydd.com novel as a PDF, `/monkeyd_tags <url>` list its tags as hashtags |
@@ -39,6 +39,17 @@ Future commands must follow the
[command parameter conventions](docs/command-parameter-conventions.md). Keep
command metadata, handler usage text, tests, and documentation aligned.
### Lunar calendar accuracy
`/amlich` and `/duonglich` port Hồ Ngọc Đức's truncated-Meeus algorithm, the
same one behind most Vietnamese calendar apps, over the years 18002199.
Output matches published Vietnamese calendars on every verifiable date. It
diverges from Chinese-calendar sources in some years by design (Vietnam uses
UTC+7, China UTC+8), and month boundaries from 2072 on carry irreducible
uncertainty. See
[amlich known issues](docs/amlich-known-issues.md) for the decision record and
the full edge-case list.
### Stock corporate events
`/stock_events <ticker> [days]` lists SSI iBoard corporate actions for a VN
@@ -213,6 +224,8 @@ internal/systemstate/ shared `system` collection helper for startup migra
third_party/monkeyd-crawler/ git submodule; resolved by a go.mod replace directive
compose.yml Coolify self-host stack (single bot service)
docs/deploy-coolify-selfhosted.md Self-host deploy and operations guide
docs/command-parameter-conventions.md Command parameter syntax rules
docs/amlich-known-issues.md Lunar algorithm decision and known edge cases
```
## Run locally
@@ -252,6 +265,11 @@ The bot uses long polling, so a local run talks to Telegram directly — no
`ngrok` or public URL. The server clears any existing webhook on startup. The
dev bot is created manually; its token is injected through the environment.
The `lol` module reads its schedule from the PandaScore REST API and needs
`LOL_PANDASCORE_TOKEN` (free tier). Without it every `/lol*` fetch fails while
the rest of the bot runs normally. See [`.env.example`](.env.example) for the
full variable list.
Persistent MongoDB locally (auto-selected when `MONGO_URL` is set):
```sh
+90
View File
@@ -0,0 +1,90 @@
# Amlich: Algorithm Decision and Known Issues
Reference for `internal/modules/amlich`. Records why the current conversion
algorithm was chosen and which edge cases it is known to get wrong or to
report differently from other sources.
## Algorithm
The module ports Hồ Ngọc Đức's truncated-Meeus algorithm (`lunar.go`), the
de-facto standard behind essentially every Vietnamese calendar app and site.
Supported range is 18002199; both commands reject anything outside it with an
explicit message.
A full Meeus *Astronomical Algorithms* ch. 49 implementation (25 periodic + 14
planetary new-moon terms, apparent solar longitude with nutation and
aberration, EspenakMeeus piecewise ΔT, month 11 anchored by bisecting the
winter solstice) was written and diffed against the current one over every day
of 18002199 — 146,097 days. They disagree on 270 days (0.18%), which is 9
lunations, every one a new moon within ±2 minutes of 17:00 UT (= midnight at
UTC+7).
Two of those nine are historically verifiable, and the current algorithm is
right on both: 20/6/1944 (published Vietnamese calendar: 30/4 nhuận) and
7/7/1967 (30/5 Đinh Mùi). Vietnam used the Chinese-calendar convention of UTC+8
before the 1968 reform, which puts these new moons unambiguously on the next
civil day; the higher-precision engine lands on the wrong side of both. The
remaining seven fall in 20722199, where ΔT uncertainty exceeds the disputed
margin, so neither engine can claim them.
Both dates are pinned in `lunar_test.go` `knownDates` so a future
"higher-precision" rewrite cannot silently flip them.
## Known issues
**Razor-edge lunations from 2072 on.** New moon within ~2 minutes of UTC+7
midnight, so the month boundary — and therefore output within roughly 30 days
of it — may differ by one day from future official tables: 09/12/2072,
15/11/2077, 07/05/2130, 26/05/2150, 17/05/2159, 22/01/2175, 26/01/2199. Not
fixable today, only documentable. The bot gives no caveat for these dates.
**ΔT extrapolation drift.** The two-branch polynomial diverges from actual
Earth rotation (ΔT has been roughly flat near 69 s since ~2016 against
predicted growth), reaching minutes-level error past 2100. It only matters near
a razor-edge boundary, but it means everything from about 2070 onward is a
best-effort prediction.
**Solar-term precision.** `sunLongitude` returns true longitude without
nutation or aberration, a systematic offset of about 10 minutes in solar-term
timing. A solstice or trung khí within ~10 minutes of midnight could shift
month numbering or leap-month placement for a whole lunar year. The 18002199
diff found zero such occurrences — low probability, high impact.
**No override for decreed changes.** The module is pure astronomy. A
state-decreed deviation or rule change would diverge silently. None has
occurred since 1968.
## Correct output that gets reported as a bug
**Divergence from Chinese-calendar sources.** Vietnam uses UTC+7 and China
UTC+8, so the two calendars genuinely differ in some years — historically 1985
and 2007, upcoming around 2030 and 2053. Users cross-checking against a Chinese
source will see a mismatch that is not an error.
**`/duonglich` defaults inside leap months.** A bare day argument fills in the
current month as a regular month; the leap month requires the explicit `nhuan`
flag. In leap years such as 2028 (leap 5) and 2031 (leap 3) this is ambiguous
to the user, but the output is correct for what was entered.
## Handled, with regression tests
- Lunation overshoot that returned lunar day 0 (07/05/2054, 09/04/2062) —
fixed, covered by `TestSolarToLunar_LunationOvershoot`.
- Leap month 11 in 2033, the classic stress case — correct, covered by
`TestLeapMonthTable`.
- Gregorian 2100 being a non-leap year — handled by the JD arithmetic.
- Out-of-range years — rejected cleanly at the 1800/2199 bounds.
## Open questions
1. Should the seven razor-edge lunations from 2072 on carry a caveat in the
bot's reply? Currently silent.
2. Should pre-1968 UTC+8 be modelled explicitly? The current algorithm matches
the published record on both known razor-edge cases by numeric coincidence
rather than by rule. An explicit UTC+8 mode would change roughly 1/24 of
pre-1968 month boundaries and diverge from the standard everyone else uses —
not worth doing unless a user reports a concrete mismatch.
3. Revisit the ΔT model if the flat IERS trend persists. A data-driven update
only pays off near razor-edge boundaries.
4. Watch for state calendar bureau tables published past 2100; they would be
the first ground truth for the 2072+ disputes.
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"github.com/tiennm99/miti99bot/internal/modules"
)
// New is the gold paper-trading module factory. It is opt-in through MODULES
// and keeps its portfolio state separate from the stock module.
// New is the gold paper-trading module factory. It keeps its portfolio state
// separate from the stock module.
func New(deps modules.Deps) modules.Module {
s := newState(deps.Store)
return modules.Module{