Skip to content

All time filter - activity counts are not adding up #413

Description

@exploreriii

🧑‍💻 Intermediate Issue — a complex task spanning multiple modules, with real design decisions to own.
Time: ~25 hours · Prerequisites: comfortable navigating this repo (a completed beginner issue is the usual route; demonstrated CI/CD proficiency substitutes for workflow-focused issues).
We expect more than "it works": maintainable code that fits the existing architecture.

The task

Problem:
Our tables have time filters: week, 1 month, 1 year, all time.

The all time filter is adding up to smaller values than the 1 year filter for example:
https://hiero-hackers.github.io/analytics/#tab=Governance&widget=repo
e.g. exploreriii has 148 all time reviews in the python SDK but 2247 1 year reviews.

This bug might be occuring elsewhere in the dashboard - we shoudl fix this.

What done looks like:
Identify the cause and breadth of the bug
Resolve so that the all time filter actually counts all time data

Modules involved / constraints:

How to work on this

  1. Claim it: comment /assign and wait to be assigned — unassigned PRs are closed automatically.
  2. Get a plan: once assigned, comment @coderabbitai plan for a draft plan, then do your own investigation — docs/architecture.md maps the layers and their rules.
  3. Propose your approach as a comment before coding. A paragraph is enough; early feedback here routinely saves days of rework.

🤖 AI: tools are welcome; verified work is required — you can explain every line and defend every design choice. See the AI policy. Fully automated bot PRs are closed.

Worth knowing about this repo before you design:

  • The layer rules in docs/architecture.md are strict — review will hold your solution to them.
  • Tests mirror src (tests/<pkg>/test_<module>.py), and the output-contract test pins the pipeline output surface — if your change adds or renames outputs, update the contract deliberately.

Before opening your PR:

  • I proposed my approach on this issue and incorporated any feedback
  • The solution fits the existing architecture and layer rules, and is clear enough for others to debug without me
  • Tests cover the happy path, edge cases, and error handling (testing guide)
  • I reviewed my own diff line by line; scope is limited to this issue
  • Workflow checks pass — CI green, signed commits, linked issue

Stuck? Comment here with what you've tried — see getting help.

Activity

  1. added
    intermediateA broader or larger issue requiring self-research and often, testing.
    on Aug 20, 2026
  2. added theissue type on Aug 20, 2026
  3. coderabbitai commented on Aug 20, 2026

    @coderabbitai

    Coding Plan

    Summary

    The defect is a pipeline-layer wiring error. The base ("All time") role_coverage table computes its *_recent count columns and status from a fixed 90-day ROLE_ACTIVE_DAYS window instead of from all-time data.

    Fix the single base-table call in pipelines/role_coverage.py::main() to use all-time profiles and active_within_days=None. This fits the layer rules, because the pipeline owns the window decision and the analysis function already supports the all-time case.

    The fix corrects all four affected Governance sections (repo, repoactivity, understaffed, loadshare) at once, because they all derive from the same base table.

    Add a regression test for the "All time ≥ 1 year" superset invariant and confirm the output contract and column schema are unchanged.

    Design Choices
    Design Choice 1: Scope of the all-time fix: status column and count columns

    Options Considered:

    1. Make the base table fully all-time. Pass the all-time profile set as recent_profiles and pass active_within_days=None for the base call. The *_recent counts become all-time, and a role holder is "active" if they have any recorded activity. This matches the module docstrings ("Status and days-since-active reflect all-time recency"), the existing gonedark base file (which already uses dark_after_days=None), and the prior #286 fix pattern.
    2. Make only the count columns all-time, but keep status on the 90-day ROLE_ACTIVE_DAYS threshold. Pass the all-time profile set as recent_profiles but keep active_within_days=ROLE_ACTIVE_DAYS. This fixes the reported count mismatch but leaves the base status/active_maintainers on a 90-day window, which still differs from the period tabs' windowed status.

    Chosen Option: 1

    Rationale: Option 1 removes the whole base-vs-period inconsistency in one place. It agrees with the existing docstrings and the gonedark/#286 precedent. It makes the "All time" tab a true superset for every affected column, including status and active_maintainers.

    💡 User Tips

    Regenerate the plan with different choices with @coderabbitai <feedback>.

    Implementation Steps

    Phase 1: Correct the all-time base table in the role_coverage pipeline

    This phase makes the base file represent true all-time data for its count and status columns. It keeps the 90-day ROLE_ACTIVE_DAYS window only where it is a genuine, tab-independent concept.

    Task 1: Build the base coverage table from all-time profiles

    Make the base role_coverage_all.csv compute its windowed columns from the full record history.

    • Update the base-table call in pipelines/role_coverage.py::main() (the combined = _build_role_coverage(...) call).
    • Pass the all-time per-repo profile set (all_time_by_repo) as the recent_profiles argument, so *_recent counts equal the all-time counts for the base file.
    • Pass active_within_days=None for the base call, so status marks a role holder active if they have any recorded activity.
    • Keep the separate 90-day recent_by_repo value produced by _build_profile_sets() unchanged. It is still needed for the co-membership network charts (_write_role_networks → build_active_membership), which are not driven by the period tabs.
    • Do not change the for period in ACTIVITY_PERIODS: loop. The period variants (_7d, _30d, _365d) are already correct.
    Task 2: Confirm derived base summaries inherit the all-time fix

    Make sure the three dependent Governance sections become all-time through the corrected base table.

    • Verify in pipelines/role_coverage.py that _write_repo_summaries(combined, ...) (the period=None call) derives repo_activity_overview.csv, maintainer_coverage_risk.csv, and review_load_share.csv from the corrected base combined table.
    • No separate change is expected here if these summaries read from combined. If any of them recompute their own 90-day window instead of reading combined, apply the same all-time correction (all-time counts, active_within_days=None) to that base call.
    Task 3: Align docstrings and comments with the corrected behavior

    Remove the mismatch between the documented invariant and the code.

    • Update the docstrings/comments in pipelines/role_coverage.py (_build_profile_sets, _build_role_coverage, and the base-table section of main()) to state clearly that the base file's *_recent counts and status are all-time, and that the 90-day ROLE_ACTIVE_DAYS window now feeds only the network charts.
    • Do not change the meaning of ROLE_ACTIVE_DAYS in config/analysis.py. It remains the network-chart recency window and the per-repo role_coverage.csv/role_promotion_candidates.csv threshold.
    🤖 Prompt for AI agents
    Implement the fix for the all-time base table in
    `src/hiero_analytics/pipelines/role_coverage.py`. The goal: the base ("All
    time") `role_coverage` table must compute its `*_recent` count columns and
    `status` from all-time data, not from a fixed 90-day window.
    
    - In `main()`, find the base-table call `combined = _build_role_coverage(...)`.
    Change its `recent_profiles` argument to use the all-time per-repo profile set
    (`all_time_by_repo`) instead of the 90-day `recent_by_repo`. Change
    `active_within_days` to `None` for this base call so `status` marks a role
    holder active based on any recorded activity, not a 90-day cutoff.
    - Do not modify `_build_profile_sets()`'s 90-day `recent_by_repo` value; it must
    remain available and unchanged, because it still feeds the co-membership network
    charts (`_write_role_networks` → `build_active_membership`).
    - Do not modify the `for period in ACTIVITY_PERIODS:` loop that rebuilds the
    `_7d`, `_30d`, and `_365d` variants; these are already correct.
    - Verify that `_write_repo_summaries(combined, ..., period=None)` derives
    `repo_activity_overview.csv`, `maintainer_coverage_risk.csv`, and
    `review_load_share.csv` from the corrected `combined` table. If any of these
    three summaries independently recompute a 90-day window instead of reading from
    `combined`, apply the same all-time correction (all-time counts,
    `active_within_days=None`) to that computation.
    - Update the docstrings and comments in `_build_profile_sets`,
    `_build_role_coverage`, and the base-table section of `main()` to state that the
    base file's `*_recent` counts and `status` are all-time, and that the 90-day
    `ROLE_ACTIVE_DAYS` window now applies only to network charts.
    - Do not change the meaning or value of `ROLE_ACTIVE_DAYS` in
    `config/analysis.py`. It must keep governing the network-chart recency window
    and the per-repo `role_coverage.csv`/`role_promotion_candidates.csv` threshold.
    

    Phase 2: Regression tests and output-contract verification

    This phase pins the corrected invariant with tests and confirms the output contract is unchanged.

    Task 1: Add a pipeline regression test for the all-time superset invariant

    Prove that the base file counts are never smaller than the 365-day counts.

    • Extend tests/pipelines/test_role_coverage.py.
    • Build a synthetic activity fixture that includes review/PR events older than 90 days but inside 365 days for at least one role holder in one repo.
    • Run main() and assert, per role holder and repo, that each base *_recent value in role_coverage_all.csv is greater than or equal to the matching value in role_coverage_all_365d.csv.
    • Assert that the base *_recent counts equal the true all-time counts for that fixture (the events older than 90 days are included).
    • Assert that a role holder whose only activity is older than 90 days is active (not quiet) in the base file, to lock the all-time status decision from Task 1.1.
    Task 2: Confirm the output contract and column schema are unchanged

    Confirm no column is renamed or added, so the contract test still passes without edits.

    • Do not rename or add columns in dashboard_spec/governance.py for the repo, repoactivity, understaffed, or loadshare sections. The fix changes only the values in existing columns.
    • Run tests/contracts/test_output_contract.py to confirm the spec↔producer contract still holds.
    • If a later design choice requires a column rename or a new column, update the matching SECTION_SPECS entry in dashboard_spec/governance.py. The contract test derives its expectations from the spec at run time, so it needs no direct edit.
    🤖 Prompt for AI agents
    Add regression tests and verify the output contract for the all-time fix made in
    Phase 1.
    
    - Extend `tests/pipelines/test_role_coverage.py` with a new test. Build a
    synthetic activity fixture that includes review/PR events older than 90 days but
    within 365 days, for at least one role holder in one repo.
    - Run `main()` against this fixture, then load `role_coverage_all.csv` and
    `role_coverage_all_365d.csv`.
    - Assert that, for each role holder and repo, every base `*_recent` value
    (`reviews_recent`, `prs_recent`, `merges_recent`, `issues_recent`,
    `labels_recent`) in `role_coverage_all.csv` is greater than or equal to the
    corresponding value in `role_coverage_all_365d.csv`.
    - Assert that the base `*_recent` counts equal the true all-time counts,
    including the events older than 90 days.
    - Assert that a role holder whose only activity is older than 90 days has
    `status` equal to `active` (not quiet) in the base file.
    - Do not rename or add any column in `dashboard_spec/governance.py` for the
    `repo`, `repoactivity`, `understaffed`, or `loadshare` sections; the fix changes
    only column values, not the schema.
    - Run `tests/contracts/test_output_contract.py` and confirm it still passes,
    since it derives its expectations from `SECTION_SPECS` at run time.
    - Only if a later design decision requires a column rename or addition, update
    the matching `SECTION_SPECS` entry in `dashboard_spec/governance.py`; otherwise
    leave it unchanged.
    
    Research

    Ticket Summary

    The "All time" filter shows smaller activity counts than the "1 year" filter in several Governance tab widgets. The reported example is exploreriii reviews in the python SDK. The task is to make the "All time" filter count truly all-time data, so it is always a superset of any period window.

    Codebase Summary

    The project follows a strict layered architecture (docs/architecture.md): config/domain define shared vocabulary, data_sources fetch and persist GitHub data, analysis performs pure DataFrame transforms, export/dashboard_spec render output and the JSON API, and pipelines orchestrate fetch → analysis → output.

    Time filtering uses domain/periods.py. ACTIVITY_PERIODS contains only Week (7d), 1 month (30d), and 1 year (365d). "All time" is not a period. It is the base table that each pipeline writes before it loops over ACTIVITY_PERIODS. The web app (web/src/components/SectionTable.tsx, PeriodTabs.tsx) selects the base rows for the "All time" tab (period === null) and periods[key] for a period tab, and it always uses the same columns.

    The Governance "repo" widget is produced by pipelines/role_coverage.py and analysis/role_coverage.py. It is declared in dashboard_spec/governance.py.

    Root Cause

    pipelines/role_coverage.py builds the base file role_coverage_all.csv incorrectly for its windowed columns.

    • _build_profile_sets() builds recent_by_repo from records filtered to a fixed 90-day window (ROLE_ACTIVE_DAYS, config/analysis.py).
    • main() calls _build_role_coverage(...) for the base table with that 90-day recent_by_repo and lets active_within_days default to ROLE_ACTIVE_DAYS (90 days).
    • The base file's *_recent columns (reviews_recent, prs_recent, merges_recent, issues_recent, labels_recent) and its status therefore reflect the last 90 days, not all time.
    • The period loop rebuilds each variant (_7d, _30d, _365d) with the correct window, so role_coverage_all_365d.csv uses a 365-day window.

    The Governance "repo" widget maps reviews_recent to the displayed "reviews" column. The "All time" tab reads the base file (90-day window). The "1 year" tab reads the 365-day file. The 365-day window is a strict superset of the 90-day window. So all-time counts appear smaller than 1-year counts.

    Breadth

    The same base-table window is reused for three more Governance sections, all derived from the same base combined table via _write_repo_summaries(..., period=None):

    • repoactivity → repo_activity_overview.csv (active_recent, *_actions_recent).
    • understaffed → maintainer_coverage_risk.csv (active_maintainers, derived from status).
    • loadshare → review_load_share.csv (load_recent).

    Other periods: True sections (for example teams, teamrepo, tscrepo, and profiles) rebuild every period file independently and do not mix an all-time base with a fixed 90-day window, so they do not show this defect. Fixing the base computation in pipelines/role_coverage.py fixes all four affected Governance sections at once.

    File-Level Change Summary

    • src/hiero_analytics/pipelines/role_coverage.py: change the base-table _build_role_coverage(...) call in main() to use all-time profiles for recent_profiles and active_within_days=None; keep the 90-day recent_by_repo only for network charts; update docstrings/comments to match. Verify _write_repo_summaries(..., period=None) reads the corrected combined table.
    • tests/pipelines/test_role_coverage.py: add a regression test that asserts the all-time superset invariant (base *_recent ≥ _365d *_recent), that base counts include activity older than 90 days, and that all-time status marks any ever-active holder active.
    • src/hiero_analytics/dashboard_spec/governance.py: no change expected (columns unchanged). Change only if a later column rename is chosen.
    • tests/contracts/test_output_contract.py: no change expected; run to confirm the contract still holds.

    🚀 Next Steps

    🤖 All AI agent prompts combined
    Task: 1
    
    Implement the fix for the all-time base table in
    `src/hiero_analytics/pipelines/role_coverage.py`. The goal: the base ("All
    time") `role_coverage` table must compute its `*_recent` count columns and
    `status` from all-time data, not from a fixed 90-day window.
    
    - In `main()`, find the base-table call `combined = _build_role_coverage(...)`.
    Change its `recent_profiles` argument to use the all-time per-repo profile set
    (`all_time_by_repo`) instead of the 90-day `recent_by_repo`. Change
    `active_within_days` to `None` for this base call so `status` marks a role
    holder active based on any recorded activity, not a 90-day cutoff.
    - Do not modify `_build_profile_sets()`'s 90-day `recent_by_repo` value; it must
    remain available and unchanged, because it still feeds the co-membership network
    charts (`_write_role_networks` → `build_active_membership`).
    - Do not modify the `for period in ACTIVITY_PERIODS:` loop that rebuilds the
    `_7d`, `_30d`, and `_365d` variants; these are already correct.
    - Verify that `_write_repo_summaries(combined, ..., period=None)` derives
    `repo_activity_overview.csv`, `maintainer_coverage_risk.csv`, and
    `review_load_share.csv` from the corrected `combined` table. If any of these
    three summaries independently recompute a 90-day window instead of reading from
    `combined`, apply the same all-time correction (all-time counts,
    `active_within_days=None`) to that computation.
    - Update the docstrings and comments in `_build_profile_sets`,
    `_build_role_coverage`, and the base-table section of `main()` to state that the
    base file's `*_recent` counts and `status` are all-time, and that the 90-day
    `ROLE_ACTIVE_DAYS` window now applies only to network charts.
    - Do not change the meaning or value of `ROLE_ACTIVE_DAYS` in
    `config/analysis.py`. It must keep governing the network-chart recency window
    and the per-repo `role_coverage.csv`/`role_promotion_candidates.csv` threshold.
    ===============================================================================
    
    Task: 2
    
    Add regression tests and verify the output contract for the all-time fix made in
    Phase 1.
    
    - Extend `tests/pipelines/test_role_coverage.py` with a new test. Build a
    synthetic activity fixture that includes review/PR events older than 90 days but
    within 365 days, for at least one role holder in one repo.
    - Run `main()` against this fixture, then load `role_coverage_all.csv` and
    `role_coverage_all_365d.csv`.
    - Assert that, for each role holder and repo, every base `*_recent` value
    (`reviews_recent`, `prs_recent`, `merges_recent`, `issues_recent`,
    `labels_recent`) in `role_coverage_all.csv` is greater than or equal to the
    corresponding value in `role_coverage_all_365d.csv`.
    - Assert that the base `*_recent` counts equal the true all-time counts,
    including the events older than 90 days.
    - Assert that a role holder whose only activity is older than 90 days has
    `status` equal to `active` (not quiet) in the base file.
    - Do not rename or add any column in `dashboard_spec/governance.py` for the
    `repo`, `repoactivity`, `understaffed`, or `loadshare` sections; the fix changes
    only column values, not the schema.
    - Run `tests/contracts/test_output_contract.py` and confirm it still passes,
    since it derives its expectations from `SECTION_SPECS` at run time.
    - Only if a later design decision requires a column rename or addition, update
    the matching `SECTION_SPECS` entry in `dashboard_spec/governance.py`; otherwise
    leave it unchanged.
    
    💡 Iterate on the plan with: `@coderabbitai` <feedback>
    Example Feedback
    - `@coderabbitai` You can skip phase 3. Add a simple unit test case for phase 2.
    - `@coderabbitai` For design choice 1 go ahead with option 3 and replan.
    

    💬 Have feedback or questions? Drop into our discord!

  4. 5affron commented on Aug 26, 2026

    @5affron
    Contributor

    /assign

  5. coderabbitai commented on Aug 27, 2026

    @coderabbitai

    CodeRabbit chat interactions are restricted to organization members for this repository. Ask an organization member to interact with CodeRabbit, or set chat.allow_non_org_members: true in your configuration.

  6. coderabbitai commented on Aug 27, 2026

    @coderabbitai

    CodeRabbit chat interactions are restricted to organization members for this repository. Ask an organization member to interact with CodeRabbit, or set chat.allow_non_org_members: true in your configuration.

  7. exploreriii commented on Aug 27, 2026

    @exploreriii
    ContributorAuthor

    we can creat an issue to solve this planning issue please @5affron

  8. 5affron commented on Aug 31, 2026

    @5affron
    Contributor

    @exploreriii Sure, I’ll create the follow-up issue. I’ll first verify the proposed cause in the code, then add the implementation and regression-testing details there before starting the changes.

  9. exploreriii commented on Sep 30, 2026

    @exploreriii
    ContributorAuthor

    @5affron please confirm if you are still working on this, and if the issue is still relevant given the recent updates

  10. exploreriii commented on Oct 7, 2026

    @exploreriii
    ContributorAuthor

    assignment released due to staleness. Open to all once again

  11. exploreriii commented on Oct 8, 2026

    @exploreriii
    ContributorAuthor
  12. sudo-anshul commented on Oct 9, 2026

    @sudo-anshul
    Contributor

    on it, thanks for the ping @exploreriii! 🙌

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

Metadata

Metadata

Assignees

Labels

intermediateA broader or larger issue requiring self-research and often, testing.pythonTouches Python code (src/, tests/)

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions