Avoid calling it "health". More just-the-facts phrasing.
diff --git a/DATA_SOURCES.md b/DATA_SOURCES.md index 1afd7fd..d418e14 100644 --- a/DATA_SOURCES.md +++ b/DATA_SOURCES.md
@@ -23,30 +23,32 @@ │ projects.apache.org │ ← SECONDARY: aggregates from above │ (DOAP + cronjobs) │ + project-maintained DOAP files └─────────────────────┘ + ``` ## Primary/Authoritative Sources | Data | Authoritative Source | Access | Notes | -|------|---------------------|--------|-------| +| --- | --- | --- | --- | | **PMC membership** | `committee-info.txt` (private SVN) | Via Whimsy JSON | Updated by Secretary | -| **Committer list** | LDAP (`id.apache.org`) | Via Whimsy `public_ldap_projects.json` | | +| **Committer list** | LDAP (`id.apache.org`) | Via Whimsy `public_ldap_projects.json` | | | **Podling status** | `incubator/.../podlings.xml` | Via Whimsy `public_podling_status.json` | Maintained by VP Incubator | -| **Project metadata** | DOAP files (maintained by each PMC) | Listed in `data/projects.xml` on SVN | | -| **Retired projects** | `committee-info.yaml` + Attic | Via Whimsy `committee-retired.json` | | +| **Project metadata** | DOAP files (maintained by each PMC) | Listed in `data/projects.xml` on SVN | | +| **Retired projects** | `committee-info.yaml` + Attic | Via Whimsy `committee-retired.json` | | +| **Board actions** | Board minutes | SVN: `private/committers/board/` | | ## Whimsy Public JSON (our best bet for programmatic access) Base URL: `https://whimsy.apache.org/public/` | File | Contents | Useful for | -|------|----------|------------| +| --- | --- | --- | | `committee-info.json` | PMC names, members, chairs | Community events (PMC additions) | | `committee-retired.json` | Retired committees | Project lifecycle | | `public_ldap_projects.json` | PMC/podling owners + members | Committer additions | | `public_podling_status.json` | Podling incubation status | Project lifecycle | | `public_ldap_people.json` | Person names + disabled status | People data | -| `icla-info.json` | ICLA signers with committer IDs | | +| `icla-info.json` | ICLA signers with committer IDs | | These are regenerated hourly by Whimsy cron jobs **only when underlying data changes**. @@ -61,37 +63,38 @@ ## Our Strategy ### Phase 1 (MVP — DONE — use what's publicly available without auth) -- **Mailing lists**: Pony Mail API (`lists.apache.org/api/`) — no auth needed - - Aggressive caching: past months immutable, only current month refreshed -- **Git metrics**: GitHub API (`api.github.com/orgs/apache`) — public, rate-limited (use token from `.secrets`) - - Also supports SVN via `svn log --xml` for projects configured with `vcs: svn` - - Per-project VCS override in `config.yml` → `project_overrides:` - - Rate-limit tracking with automatic pause-and-wait at 50 remaining calls - - Per-project VCS override in `config.yml` → `project_overrides:` -- **Project roster changes**: Whimsy public JSON (diff `committee-info.json` between runs) -- **Project list**: GitHub org repos + Pony Mail list discovery -- **Dashboard**: 12-month rolling window with linear regression trend lines - - Current month extrapolated to full-month estimate for trend accuracy -### Phase 2 (richer data) +- **Mailing lists**: Pony Mail API (`lists.apache.org/api/`) — no auth needed- Aggressive caching: past months immutable, only current month refreshed +- **Git metrics**: GitHub API (`api.github.com/orgs/apache`) — public, rate-limited (use token from `.secrets`)- Also supports SVN via `svn log --xml` for projects configured with `vcs: svn` or auto-detected from `repositories.json` +- Per-project VCS override in `config.yml` → `project_overrides:` +- Per-repo monthly breakdown with aggressive caching (same-day = skip, next-day = incremental) +- Rate-limit tracking with automatic pause-and-wait at 50 remaining calls +- **Project roster changes**: Whimsy public JSON (diff `committee-info.json` between runs) +- **Project list**: projects.apache.org `committees.json` + `podlings.json` (TLPs + active podlings) +- **Repo inventory**: GitHub org listing → `_project_map.json` (auto-run on first use) +- **Dashboard**: 12-month rolling window with linear regression trend lines- Current month extrapolated to full-month estimate for trend accuracy + +### Phase 2 (richer data, may need ASF Infra coordination) + - **Release history**: projects.apache.org JSON cache or direct DOAP parsing - **Podling lifecycle**: Whimsy `public_podling_status.json` - **Retired projects**: Whimsy `committee-retired.json` -- **Board report status**: Would need access to board minutes +- **Board report status**: Would need access to board minutes (private SVN) -### Phase 3 -- **LDAP direct queries**: Real-time committer/PMC additions? +### Phase 3 (if we get service account access) + +- **LDAP direct queries**: Real-time committer/PMC additions (requires Infra ticket) - **Next Committer integration**: PMC-only access via LDAP auth ## Key Insight **projects.apache.org is convenient but not authoritative.** It aggregates from: + 1. DOAP files (maintained by PMCs, varying quality/currency) 2. Whimsy/LDAP (the real source of truth for people/committee data) 3. Its own cronjob caches (may lag) -For community health metrics, **Whimsy public JSON + Pony Mail + GitHub API** gives us -everything we need for Phase 1 without any special access. +For community activity metrics, **Whimsy public JSON + Pony Mail + GitHub API** gives us everything we need for Phase 1 without any special access. We only need to involve ASF Infra if we want LDAP direct access (for Next Committer) or private SVN (for board data). ## URLs Quick Reference @@ -103,8 +106,8 @@ https://whimsy.apache.org/public/committee-retired.json # Pony Mail -https://lists.apache.org/api/preferences.json (list discovery) -https://lists.apache.org/api/stats.json (per-list stats) +https://lists.apache.org/api/preferences.json (list discovery, POST) +https://lists.apache.org/api/stats.json (per-list stats, POST + JSON body) # GitHub https://api.github.com/orgs/apache/repos (repo listing) @@ -112,4 +115,6 @@ # projects.apache.org data (secondary) https://projects.apache.org/json/projects.json (all projects metadata) https://projects.apache.org/json/releases.json (release data) + ``` +
diff --git a/PLAN.md b/PLAN.md index f22196e..bc06bf0 100644 --- a/PLAN.md +++ b/PLAN.md
@@ -1,7 +1,7 @@ # ComDev Metrics Site — Execution Plan Public, automated dashboard at **community.apache.org/metrics/** showing -health and activity of every Apache project with 12-month rolling trends. +activity of every Apache project with 12-month rolling trends. ## Design Principles @@ -55,8 +55,9 @@ | 5 | Static HTML dashboard + per-project pages (default: All Projects tab) | | ✅ Done | | 6 | Roster change detection (projects.apache.org JSON diffing) | | ✅ Done | | 7 | Deploy to ComDev VM for demo | | ⬜ | -| 8 | Next Committer integration (PMC-only, LDAP gated) | | ⬜ | -| 9 | Production deployment on ComDev VM | | ⬜ | +| 8 | Single-project self-serve mode | | ⬜ | +| 9 | Next Committer integration (PMC-only, LDAP gated) | | ⬜ | +| 10 | Production deployment on ComDev VM | | ⬜ | **Status:** Dashboard running at https://boxofclue.com/comdev-metrics/
diff --git a/README.md b/README.md index aa30d81..0f0dd56 100644 --- a/README.md +++ b/README.md
@@ -1,6 +1,6 @@ # ASF Community Development Metrics -Public dashboard showing community health metrics for Apache projects, +Public dashboard showing community activity metrics for Apache projects, with 12-month trend lines for meaningful context. **Status**: Working dashboard deployed at https://boxofclue.com/comdev-metrics/ — @@ -143,6 +143,11 @@ --refresh-repos Re-fetch the GitHub repo inventory (project → repos map) ``` +## Deployment + +**Dev/staging**: https://boxofclue.com/comdev-metrics/ (matrim.rcbowen.com, Alma Linux) +**Production**: ASF ComDev VM (Ubuntu) — eventually + ``` # Weekly cron 0 6 * * 1 rcbowen cd /opt/asfmetrics && uv run asfmetrics --config /etc/asfmetrics/config.yml
diff --git a/site/about.html b/site/about.html index b75f739..ef2bc2b 100644 --- a/site/about.html +++ b/site/about.html
@@ -91,14 +91,14 @@ <li>A collector runs weekly, fetching data from the sources above.</li> <li>Data is cached aggressively — past months are immutable and never re-fetched.</li> <li>Only the current month's data is refreshed on each run.</li> - <li>After collection, a health classification pass computes project status - (At Risk / Declining / Dormant / Healthy) from the collected data using fixed thresholds.</li> + <li>After collection, an activity classification pass categorizes projects by trend + (Sharp Decline / Declining / Dormant) from the collected data using fixed thresholds.</li> <li>The output is static HTML + JSON — no live server or database.</li> <li>Trend lines use linear regression over a 12-month rolling window.</li> <li>The current (incomplete) month is extrapolated to a full-month estimate for trend accuracy.</li> <li>Each project has a dedicated page showing all its metrics in one place — mailing lists (linked to <a href="https://lists.apache.org/">lists.apache.org</a> archives), - git repos, releases, community growth, and health status.</li> + git repos, releases, community growth, and activity trends.</li> </ul> </div> @@ -128,15 +128,14 @@ <h3>Releases</h3> <p>Release publications in the past 12 months, as recorded by projects.apache.org.</p> - <h3>Project Health</h3> - <p>A deterministic classification computed from the collected data. Compares the two + <h3>Activity Trends</h3> + <p>A deterministic classification of quarter-over-quarter trends. Compares the two most recent full quarters (excluding the current partial month) for both mailing list discussion activity and git commit volume.</p> <ul> - <li><strong>🔴 At Risk</strong> — both mailing list and git activity declining ≥ 30% quarter-over-quarter. Immediate attention needed.</li> - <li><strong>🟡 Declining</strong> — one metric declining ≥ 30%, with meaningful prior activity (≥ 30 messages or commits in the prior quarter). If the other metric is growing ≥ 30%, the project is not flagged.</li> + <li><strong>🔴 Sharp Decline</strong> — both mailing list and git activity down ≥ 30% quarter-over-quarter.</li> + <li><strong>🟡 Declining</strong> — one metric down ≥ 30%, with meaningful prior activity (≥ 30 messages or commits in the prior quarter). If the other metric is growing ≥ 30%, the project is not flagged.</li> <li><strong>⚫ Dormant</strong> — fewer than 5 commits and 5 mailing list messages in the recent quarter.</li> - <li><strong>🟢 Healthy</strong> — no significant decline detected (shown on per-project pages only).</li> </ul> <p>Only human discussion lists (dev, user, general, discuss) are counted for mailing list trends — automated lists (notifications, commits, issues) are excluded. The thresholds are
diff --git a/site/index.html b/site/index.html index 8295b64..6402ada 100644 --- a/site/index.html +++ b/site/index.html
@@ -94,7 +94,7 @@ </head> <body> <h1>ASF Community Metrics</h1> - <p class="subtitle">Apache project community health — <a href="about.html" style="color:#666;text-decoration:underline">about the data</a></p> + <p class="subtitle">Apache project metrics — <a href="about.html" style="color:#666;text-decoration:underline">about the data</a></p> <div id="loading">Loading data...</div> <div id="app" style="display:none"> @@ -123,7 +123,7 @@ <div class="tab" data-tab="releases">Recent Releases</div> <div class="tab" data-tab="mailing">Mailing Lists</div> <div class="tab active" data-tab="projects">All Projects</div> - <div class="tab" data-tab="health">Project Health</div> + <div class="tab" data-tab="health">Activity Trends</div> </div> <div class="section" style="border-radius: 0 8px 8px 8px;"> @@ -163,7 +163,7 @@ </table> </div> <div class="tab-content" id="tab-health"> - <h2 id="health-title">Project Health</h2> + <h2 id="health-title">Activity Trends</h2> <p id="health-window" style="color:#666;font-size:0.9rem;margin-bottom:0.5rem"></p> <p style="color:#666;font-size:0.85rem;margin-bottom:1.5rem" id="health-methodology"></p> @@ -174,8 +174,8 @@ <tbody></tbody> </table> - <h3 style="color:#c0392b;margin-top:2rem">🔴 At Risk <span id="at-risk-count" style="font-weight:normal;color:#666"></span></h3> - <p style="color:#666;font-size:0.85rem;margin-bottom:0.5rem">Both mailing list discussion and git commits trending sharply downward. Immediate attention needed.</p> + <h3 style="color:#c0392b;margin-top:2rem">🔴 Sharp Decline <span id="at-risk-count" style="font-weight:normal;color:#666"></span></h3> + <p style="color:#666;font-size:0.85rem;margin-bottom:0.5rem">Both mailing list discussion and git commits down ≥ 30% quarter-over-quarter.</p> <table id="at-risk-table"> <thead><tr><th>Project</th><th class="health-ml-header">Mailing List (Q1 vs Q2)</th><th class="health-git-header">Git Commits (Q1 vs Q2)</th></tr></thead> <tbody></tbody> @@ -190,7 +190,7 @@ <div style="margin-top:2rem;padding:1rem;background:#f8f9fa;border-radius:6px;font-size:0.85rem;color:#555"> <strong>Methodology:</strong> <span id="health-method-detail"></span><br> - <strong>Thresholds:</strong> At Risk = both axes ≤ <span id="threshold-decline"></span>%. + <strong>Thresholds:</strong> Sharp Decline = both axes ≤ <span id="threshold-decline"></span>%. Declining = one axis ≤ <span id="threshold-decline2"></span>% (min <span id="threshold-prior"></span> prior activity). Dormant = ≤ <span id="threshold-dormant-commits"></span> commits + ≤ <span id="threshold-dormant-msgs"></span> messages in recent quarter. </div> @@ -406,7 +406,7 @@ }); } - // --- Project Health tab --- + // --- Activity Trends tab --- let healthData = null; try { const healthResp = await fetch('data/_cache/project_health.json'); @@ -426,7 +426,7 @@ document.getElementById('dormant-git-header').textContent = `Git Commits (${win.recent_label})`; document.getElementById('health-title').textContent = - `Project Health — ${win.recent_label} vs ${win.prior_label}`; + `Activity Trends — ${win.recent_label} vs ${win.prior_label}`; document.getElementById('health-window').textContent = win.description; document.getElementById('health-methodology').textContent = 'Mailing list counts include only human discussion lists (dev, user, general, discuss). ' + @@ -443,7 +443,7 @@ `Current partial month excluded. Only projects with data files are assessed.`; // In JSON: "declining" = both axes down (severe), "at_risk" = one axis down (moderate) - // On page: "At Risk" (red) = severe, "Declining" (orange) = moderate + // On page: "Sharp Decline" (red) = severe, "Declining" (orange) = moderate document.getElementById('at-risk-count').textContent = `(${healthData.declining.length})`; document.getElementById('declining-count').textContent = `(${healthData.at_risk.length})`; document.getElementById('dormant-count').textContent = `(${healthData.dormant.length})`;
diff --git a/site/project.html b/site/project.html index a0442fb..5e15633 100644 --- a/site/project.html +++ b/site/project.html
@@ -108,7 +108,7 @@ </div> </div> - <div id="health-badge" style="display:none;margin-bottom:1.5rem;padding:0.8rem 1.2rem;border-radius:8px;font-size:0.95rem"> + <div id="trend-badge" style="display:none;margin-bottom:1.5rem;padding:0.8rem 1.2rem;border-radius:8px;font-size:0.95rem"> </div> <div class="section"> @@ -379,45 +379,42 @@ } } - // --- Health status badge --- + // --- Activity trend badge --- try { - const healthResp = await fetch('data/_cache/project_health.json'); - if (healthResp.ok) { - const health = await healthResp.json(); - const badge = document.getElementById('health-badge'); - const win = health.window; + const trendResp = await fetch('data/_cache/project_health.json'); + if (trendResp.ok) { + const trendData = await trendResp.json(); + const badge = document.getElementById('trend-badge'); + const win = trendData.window; - const inDeclining = health.declining.find(e => e.project === id); - const inAtRisk = health.at_risk.find(e => e.project === id); - const inDormant = health.dormant.find(e => e.project === id); + const inDeclining = trendData.declining.find(e => e.project === id); + const inAtRisk = trendData.at_risk.find(e => e.project === id); + const inDormant = trendData.dormant.find(e => e.project === id); if (inDormant) { badge.style.display = 'block'; badge.style.background = '#f0f0f0'; badge.style.borderLeft = '4px solid #555'; - badge.innerHTML = `<strong>⚫ Dormant</strong> — Near-zero activity across all metrics (${win.recent_label}).`; + badge.innerHTML = `<strong>⚫ Dormant</strong> — Near-zero activity in ${win.recent_label}: fewer than 5 commits and 5 mailing list messages.`; } else if (inDeclining) { badge.style.display = 'block'; badge.style.background = '#fdf2f2'; badge.style.borderLeft = '4px solid #c0392b'; const mlStr = inDeclining.ml_trend_pct !== null ? `ML: ${inDeclining.ml_trend_pct}%` : ''; const gitStr = inDeclining.git_trend_pct !== null ? `Git: ${inDeclining.git_trend_pct}%` : ''; - badge.innerHTML = `<strong>🔴 At Risk</strong> — Both mailing list and git activity declining sharply (${win.prior_label} → ${win.recent_label}). ${mlStr} ${gitStr}`; + badge.innerHTML = `<strong>🔴 Sharp Decline</strong> — Both mailing list and git activity down ≥ 30% (${win.prior_label} → ${win.recent_label}). ${mlStr} ${gitStr}`; } else if (inAtRisk) { badge.style.display = 'block'; badge.style.background = '#fef9e7'; badge.style.borderLeft = '4px solid #e67e22'; const mlStr = inAtRisk.ml_trend_pct !== null ? `ML: ${inAtRisk.ml_trend_pct}%` : ''; const gitStr = inAtRisk.git_trend_pct !== null ? `Git: ${inAtRisk.git_trend_pct}%` : ''; - badge.innerHTML = `<strong>🟡 Declining</strong> — Activity trending downward (${win.prior_label} → ${win.recent_label}). ${mlStr} ${gitStr}`; + badge.innerHTML = `<strong>🟡 Declining</strong> — One or both metrics down ≥ 30% (${win.prior_label} → ${win.recent_label}). ${mlStr} ${gitStr}`; } else { - badge.style.display = 'block'; - badge.style.background = '#eafaf1'; - badge.style.borderLeft = '4px solid #27ae60'; - badge.innerHTML = `<strong>🟢 Healthy</strong> — No significant decline detected (${win.prior_label} → ${win.recent_label}).`; + // No badge shown for projects without notable trends — the data speaks for itself } } - } catch(healthErr) { console.warn('Health data not available:', healthErr); } + } catch(trendErr) { console.warn('Trend data not available:', trendErr); } // --- Community growth (new committers + PMC members) --- try {