Show only the last MONTHS_TO_SHOW months (default 5) in the fee table columns so the page fits on screen without horizontal scrolling. Reconciliation still runs over the full month history so balances, credits, and debts are unaffected. Set MONTHS_TO_SHOW=0 to show all months. Implemented in both Python and Go. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.0 KiB
Plan: Limit /adults and /juniors to last N months (default 5)
Context
The /adults and /juniors dashboard tables render one column per month of
attendance/fee history. As the season accumulates months, the tables grow wider
than the screen. The goal is to show only the last N months by default
(N = 5), with N configurable via an env var, so the tables fit on screen. This
must be implemented in both the Python/Flask version and the Go version,
keeping their behavior identical.
Key correctness requirement
Member balances, credits, and debts must continue to reflect all history, not just the visible window. Hiding older columns must not hide older debt.
This is naturally satisfied because in both codebases the balance math iterates over the full per-member month map produced by reconcile, while only the column rendering iterates over the passed-in month list:
- Python
scripts/views.py:settled_balance/_settled_balance/ credits / debts loop overdata["months"].items()(full), whereas columns, totals, and per-row cells loop oversorted_months. - Go
build_adults.go/build_juniors.go:settledBalance(mr, ...)loops overmr.Months(full); columns/totals/cells loop oversortedMonths.
Therefore the correct seam is: run reconcile() / Reconcile() on the full
month list, then trim the list to the last N only for the view-model builder.
The member-details modal also keeps full history because it reads the untrimmed
member_data / MemberData.
Approach
Add a MONTHS_TO_SHOW tunable (default 5; <= 0 means "show all" as an escape
hatch). Trim sorted_months/sortedMonths to the last N immediately before the
view-model builder, leaving reconcile on the full list.
Python
-
scripts/config.py— add, next to the existingCACHE_TTL_SECONDSpattern (int(os.environ.get(...))):MONTHS_TO_SHOW = int(os.environ.get("MONTHS_TO_SHOW", 5)) -
app.py— add a small helper (module-level) and apply it in all four routes that build the adults/juniors view models:from config import MONTHS_TO_SHOW # add to existing config import def _last_n_months(months): return months[-MONTHS_TO_SHOW:] if MONTHS_TO_SHOW > 0 else monthsIn each route, keep
reconcile(members, sorted_months, ...)on the full list, then pass the trimmed list to the builder:result = reconcile(members, sorted_months, transactions, exceptions) display_months = _last_n_months(sorted_months) vm = build_adults_view_model(members, display_months, result, ...)Apply to:
adults_view()(app.py:226),juniors_view()(app.py:260), and the JSON twins/api/adults(app.py:161) and/api/juniors(app.py:184) for parity.No changes to
scripts/views.py— it already derivesmonths,raw_months,totals, and per-rowrow.monthsfrom whatever month list it receives, and balances/credits/debts from the fullresult.
Go
-
go/internal/config/config.go— addMonthsToShow intto theConfigstruct (~line 57-67), populate it inLoad()(~line 80-90) with a new integer helper modeled onenvDuration:func envInt(key string, fallback int) int { if v := os.Getenv(key); v != "" { if n, err := strconv.Atoi(v); err == nil { return n } } return fallback }MonthsToShow: envInt("MONTHS_TO_SHOW", 5),(Note: unlike
envDuration, accept<= 0so it can mean "show all".) -
go/internal/web/api/handler.go— inAssembleAdults(lines 50-57) andAssembleJuniors(lines 71-78), keepReconcileon the fullsortedMonths, then trim before the builder:result := domreconcile.Reconcile(members, sortedMonths, txns, exceptions, time.Now().Year()) displayMonths := lastNMonths(sortedMonths, h.Config.MonthsToShow) return buildAdultsResponse(members, displayMonths, result, txns, h.Config, time.Now().Format("2006-01")), nilAdd a small helper (e.g. in handler.go):
func lastNMonths(months []string, n int) []string { if n > 0 && len(months) > n { return months[len(months)-n:] } return months }No changes to
build_adults.go/build_juniors.goor the templates — they already deriveMonths,RawMonths,Totals, and per-row cells from the passed-insortedMonths, and balances/credits/debts from the fullresult.Members[...].
Critical files
scripts/config.py— newMONTHS_TO_SHOWconstant.app.py— trim helper + apply in 4 routes (HTML + JSON, adults + juniors).go/internal/config/config.go—MonthsToShowfield +envInthelper.go/internal/web/api/handler.go—lastNMonthshelper + apply inAssembleAdults/AssembleJuniors.
No template or views.py / build_*.go changes required.
Verification
Python:
make test(and a targeted run, e.g.PYTHONPATH=scripts:. python -m unittest tests.test_app).make web, open/adultsand/juniors: confirm exactly 5 month columns by default, and that the Balance column / credits / debts are unchanged from before (compare against an untrimmed run, e.g.MONTHS_TO_SHOW=0).MONTHS_TO_SHOW=3 make web→ 3 columns;MONTHS_TO_SHOW=0→ all columns.- Spot-check that the month-range filter dropdowns and a member-details modal (full history) still work.
Go:
cd go && go build ./... && go test ./....- Run the Go server, open
/adultsand/juniors: same checks as above (default 5 columns, balances unchanged,MONTHS_TO_SHOWenv override works). - Confirm Python and Go render the same number of columns and identical balances for the same data.
Housekeeping
- Per
CLAUDE.md, copy this plan todocs/plans/YYYY-MM-DD-HHMM-months-to-show.mdduring implementation (createdocs/plans/if missing) and add aCHANGELOG.mdentry once verified. - Per
CLAUDE.md, this is a feature → do it on afeat/months-to-showbranch and open a Gitea MR withtea; do not commit tomain.