From a58aa150febc99160c46c731e73bc57cefdc7e5f Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sun, 16 Aug 2026 22:47:24 +0700 Subject: [PATCH] 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/. --- README.md | 24 ++++++++-- docs/amlich-known-issues.md | 90 +++++++++++++++++++++++++++++++++++ internal/modules/gold/gold.go | 4 +- 3 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 docs/amlich-known-issues.md diff --git a/README.md b/README.md index 366d64e..b579e60 100644 --- a/README.md +++ b/README.md @@ -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 1800–2199 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 `, `/stats cmd ` | | `monkeyd` | `/monkeyd_crawl [font_size]` export a monkeydd.com novel as a PDF, `/monkeyd_tags ` 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 1800–2199. +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 [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 diff --git a/docs/amlich-known-issues.md b/docs/amlich-known-issues.md new file mode 100644 index 0000000..c1e80e1 --- /dev/null +++ b/docs/amlich-known-issues.md @@ -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 1800–2199; 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, Espenak–Meeus piecewise ΔT, month 11 anchored by bisecting the +winter solstice) was written and diffed against the current one over every day +of 1800–2199 — 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 2072–2199, 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 1800–2199 +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. diff --git a/internal/modules/gold/gold.go b/internal/modules/gold/gold.go index 9e0c5f3..11cef46 100644 --- a/internal/modules/gold/gold.go +++ b/internal/modules/gold/gold.go @@ -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{