Skip to content

feat(charts): draw a growth forecast chart per top repository - #186

Merged
fbuireu merged 1 commit into
mainfrom
fbuireu/regression-moving-average-per-repo-9rnyf0
Sep 6, 2026
Merged

fbuireu merged 1 commit into
mainfrom
fbuireu/regression-moving-average-per-repo-9rnyf0

Conversation

@fbuireu

@fbuireu fbuireu commented Sep 6, 2026 •

Copy link
Copy Markdown
Owner

Description

The forecast maths was already per repo — computeForecast fits both Forecast Methods to each Top Repository's own reconstructed history, and both Report dialects already printed those figures as tables. Only the chart was aggregate-only: forecastSpec read forecastData.aggregate and plotted snapshot.totalStars, so the single visual in the Growth Forecast section described the Tracked Set's total while the tables underneath it talked about individual repositories.

This closes that gap: one Forecast Chart per Top Repository, plotted over the history its projection was actually fitted to.

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📝 Documentation update
  • 🔧 Configuration or workflow change
  • ♻️ Code refactoring
  • ⚡ Performance improvement
  • ✅ Test update

Related Issue

Changes Made

  • charts/forecast-<owner>-<repo>.svg per Top Repository, linked from both Reports under that repository's Forecast table. buildChartFiles plots each over chartHistories.reconstructedForRepo(name) — the very History @domain/forecast fitted — so the observed curve and the projection continuing it cannot describe different series.
  • A repository fitted to the aggregate gets its table and no chart. That projection describes the whole Tracked Set's shape, not the repository named above it; drawing it would be the same drift the two per-repo accessors (forRepo vs reconstructedForRepo) already exist to prevent.
  • RepoForecast gains source (ForecastSource: OWN | AGGREGATE). It earns a type under ADR 0022 on all three counts: @presentation reads it, it crosses the domain boundary, and the alternative is re-deriving the fallback rule against MIN_SNAPSHOTS_FOR_FORECAST in a second place.
  • PER_REPO_FORECAST is a fifth ChartKind, mirroring how PER_REPO sits beside STAR_HISTORY. Both forecast builders are two lines over one private projectionSpec, which owns the guard, the dated x-axis, the week labels and the three-series layout, so the two kinds cannot drift apart in anything but which series they read. Neither adapter changes — svg-chart.ts and chart.ts take any request — which is what ADR 0014 was for.
  • model.perRepoForecasts becomes the single list both dialects iterate for the per-repo half of the section, each entry carrying the History its chart was drawn from (or null when the run drew none), following the existing "the linked set is the drawn set" rule.
  • buildReportModel takes the drawn filenames as one ReadonlySet<string> and composes the names it asks about itself. A second chart family costs one more lookup, not one more parameter; the alternative was a pair of adjacent, same-typed, non-interchangeable predicates — the history / velocityHistory hazard the presentation guide already documents.
  • resolveChartHistories memoises reconstructedForRepo (null results included). Five consumers ask for the same repository in one run and buildStarHistory buckets every starred_at each time, so the second per-repo chart would otherwise have turned three reconstructions per repository into five. The closure captures every input, so the cache is sound.
  • Filename prefixes rather than suffixes the stem. As a suffix, forecast- would collide with the star-history chart of any repository actually named *-forecast, a plausible name; as a prefix it only collides with an owner called forecast-….

Testing

  • Existing unit tests pass (pnpm test:ut)
  • Added new tests for changes
  • Manually tested with a sample workflow
  • The bundle builds (pnpm build)
  • Attached the rendered output (report, badge or chart) if this PR changes what gets rendered

1026 tests pass; coverage 99.2% statements / 95.12% branches / 100% functions / 99.84% lines, against the 85% threshold.

New cases: source resolution in forecast.test.ts; the fifth kind in both enumerations plus its own series and the unknown-repo null in chart-spec.test.ts; the OWN-only file emission and the once-per-repo reconstruction in charts.test.ts; perRepoForecasts and its three chart-history outcomes in report-model.test.ts; the link in markdown.test.ts, the decoded QuickChart title in html.test.ts, and drawn-set/linked-set parity in run.test.ts.

The sample below is rendered by the real pipeline (computeForecast + renderSvgChart), not drawn by hand, and it came out byte-identical before and after the fifth-kind refactor. Its curve accelerates recently on purpose: with linear growth the two methods projected 414 and 415 and the lines overlapped, which shows nothing. Here the weighted moving average follows the recent weeks (427) and the linear regression averages the whole span (404), so they fan out.

Per-Repo Growth Forecast

Checklist

  • pnpm verify passes (format check, typecheck, coverage and build)
  • I have performed a self-review of my own code
  • My change carries no inline comments; rationale lives in this PR, the commit messages, an ADR or the folder's guide
  • I rebuilt dist/ with pnpm build and committed it, if I touched anything under src/
  • Changed inputs, outputs or defaults are reflected in action.yml, the wiki and the README
  • I updated any CLAUDE.md, CONTEXT.md, ARCHITECTURE.md, ADR or wiki page my change affects, in this same PR, and pnpm test:docs passes
  • My changes generate no new warnings or errors
  • I have added tests that prove my fix is effective or that my feature works

The unchecked input/output box is not an omission: this PR adds no action input, output or default. The new charts are gated by the existing include-charts and top-repos.

Documentation updated in the same commit: src/presentation/CLAUDE.md, src/domain/CLAUDE.md, src/application/CLAUDE.md (whose resolveChartHistories bullet did not name reconstructedForRepo, and was already incomplete before this change), the Chart entry in CONTEXT.md (now five kinds), the data-branch tree in ARCHITECTURE.md, five wiki pages, and the sample gallery.

Additional Notes

No ADR. The decisions here meet two of the three bars — surprising without context, a real trade-off — but not the third: each is reversible by deleting one condition, one case or one Map. The rationale lives in the two layer guides instead.

Why a chart is withheld rather than drawn from the Stored History. forRepo falls back to the Stored History, where repoStarSeries yields 0 for every Snapshot taken before the repository joined the Tracked Set. Plotting a projection over that fabricated 0 → total ramp is the failure mode src/domain/CLAUDE.md already documents for the forecast figures; this keeps the chart honest the same way.


GIF (mandatory)

Thanks for contributing! ✨

@codecov

codecov Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The forecast maths was already per repo: computeForecast fits linear
regression and a weighted moving average to each Top Repository's own
reconstructed history, and both report dialects print those figures as
tables. Only the chart was aggregate-only, so the one visual in the
Growth Forecast section plotted the Tracked Set's total while the tables
underneath it talked about individual repositories.

Emit forecast-<owner>-<repo>.svg per top repository, plotted over the
history the forecast was actually fitted to, and link it from both
reports under that repository's table.

A repository the domain fell back to the aggregate for gets its table and
no chart: that projection describes the whole Tracked Set's shape, not the
repository named above it, and drawing it would be the same drift the two
per-repo accessors already exist to prevent.

RepoForecast gains `source` (ForecastSource: OWN | AGGREGATE). It earns a
type under ADR 0022 on all three counts: presentation reads it, it crosses
the domain boundary, and the alternative is re-deriving the fallback rule
against MIN_SNAPSHOTS_FOR_FORECAST in a second place.

PER_REPO_FORECAST is a fifth ChartKind rather than an optional field on
FORECAST, mirroring how PER_REPO sits beside STAR_HISTORY; both forecast
builders are two lines over one private projectionSpec, so they cannot
drift apart in anything but which series they read.

The report model takes the drawn filenames as one ReadonlySet and
composes the names it asks about, replacing what would have been a pair
of adjacent, same-typed, non-interchangeable predicates.

resolveChartHistories now memoises reconstructedForRepo. Five consumers
ask for the same repository in one run and buildStarHistory buckets every
starred_at each time, so the second per-repo chart would otherwise have
turned three reconstructions per repository into five.

The filename prefixes rather than suffixes the stem: forecast-user-repo
cannot collide with the star history chart of a repository named
"*-forecast", which is a plausible name, only with an owner called
"forecast-...", which is not.

The sample in docs/examples is rendered by the real pipeline rather than
drawn by hand, over a curve whose recent acceleration separates the two
methods, which is the difference between them a single sample can show.
@fbuireu
fbuireu force-pushed the fbuireu/regression-moving-average-per-repo-9rnyf0 branch from f80c96b to 7a0b2b1 Compare September 6, 2026 20:29
@fbuireu
fbuireu merged commit ddf63da into main Sep 6, 2026
15 of 16 checks passed
@fbuireu
fbuireu deleted the fbuireu/regression-moving-average-per-repo-9rnyf0 branch September 6, 2026 20:32
fbuireu pushed a commit that referenced this pull request Sep 6, 2026
# [1.27.0](v1.26.4...v1.27.0) (2026-09-06)

### Features

* **charts:** draw a growth forecast chart per top repository ([#186](#186)) ([ddf63da](ddf63da))
@fbuireu

fbuireu commented Sep 6, 2026

Copy link
Copy Markdown
Owner Author

🎉 This PR is included in version 1.27.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant