Back to home
π΅οΈββοΈ OSS Scout
Data-driven health scores for open-source projects β because star count is a terrible signal
Engineering teams routinely bet on open-source dependencies using gut instinct and GitHub stars. OSS Scout replaces that with a multi-source data pipeline that collects metrics from the GitHub API, npm, PyPI, and Hacker News, then combines quantitative signals with LLM-powered documentation analysis to produce a composite health score.
The system runs on an automated daily/weekly collection schedule, tracking how projects evolve over time. Each score breaks down into five weighted dimensions β community, maintenance, documentation, contributor diversity, and growth trajectory β so you can see exactly why a project scores high or low.
I validated the scoring methodology with a backtesting framework against 20 historical projects with known outcomes. The results confirmed the core thesis: maintenance responsiveness (median issue close time, commit recency) had a Spearman rank correlation of Ο=0.860 with real-world project outcomes, while star count β the metric most developers rely on β correlated at just Ο=0.204 (not even statistically significant).
The frontend is a React dashboard with filterable project rankings, per-project detail views with trend charts, head-to-head comparison, and a trending movers page that surfaces score changes over 7/30/90-day windows.

π οΈ Tech Stack
π Features
Multi-source data pipeline β Orchestrates concurrent API calls to GitHub REST API (repo metadata, commit activity, contributor stats, issue/PR responsiveness), npm registry, PyPI stats, and HN Algolia, with
asyncio.gatherand per-source error isolation so a single API failure never blocks the restLLM documentation analysis β Uses OpenAI function calling to extract structured quality assessments from README content across five dimensions (clarity, getting-started, examples, architecture, contribution guide), returning typed JSON scores with a written summary and improvement suggestions
Composite health scoring engine β Five weighted sub-scores using logarithmic scaling, ratio-based metrics, and recency decay β designed so each component is independently interpretable and the formula is fully transparent in
/docs/scoring_methodology.mdBacktesting & evaluation framework β Scores 20 historical projects (known successes like FastAPI, known failures like gpt-engineer) using frozen metric snapshots, then computes Spearman rank correlation against actual outcomes to validate predictive power
Automated scheduling β APScheduler cron triggers run daily (new project discovery) and weekly (full refresh of all tracked projects), with a status API endpoint exposing last-run time, next-run time, and pipeline health
Production-grade infrastructure β Token-bucket rate limiter (1.3 req/s for GitHub's 5K/hr quota), Redis caching with TTL, exponential backoff with
Retry-Afterheader support, and transactionalflush()-based persistence so partial pipeline failures roll back cleanlyInteractive React dashboard β Filterable project list (language, score range, sort), detail views with radar chart score breakdowns and time-series trend lines, head-to-head project comparison, and a trending movers page β all with URL-synced filter state via
useSearchParamsfor shareable/bookmarkable viewsResponsive design β Desktop table + mobile card layout pattern using Tailwind breakpoint toggles, with conditional chart rendering that only shows npm/PyPI visualizations when the project actually has package registry data
π§ββοΈ Challenges
Star count is genuinely a terrible health signal β Backtesting proved it: projects like gpt-engineer (48K stars, stagnated) and open-interpreter (42K stars, abandoned) scored high on community but low on maintenance. The data showed maintenance responsiveness was 4x more predictive than star count (Ο=0.860 vs Ο=0.204), which required reweighting the formula away from the spec's original assumptions
Growth sub-score needs historical data that doesn't exist yet β The spec called for
stars_gained_30d, which requires a previous snapshot to diff against. On a fresh database, that field is always zero β meaning 40% of the growth sub-score's weight was dead. Solved by leaning on download acceleration and commit trend signals for the remaining 60%, and documenting star velocity as a Phase 2 improvement once the system has 30+ days of dataGitHub stats endpoints are eventually consistent β The
/stats/commit_activityand/stats/contributorsendpoints return HTTP 202 with empty bodies on first request while GitHub computes them server-side. The pipeline treats these as graceful nulls rather than errors, and subsequent collection runs pick up the computed valuesNaive vs. timezone-aware datetime mismatch β SQLAlchemy
DateTimecolumns store naive timestamps, but Python 3.12+ deprecatesdatetime.utcnow(). Trend query functions usingdatetime.now(UTC)(timezone-aware) couldn't compare against the naive DB values. Fixed withdatetime.now(UTC).replace(tzinfo=None)at the application boundary, with a documented note that the real fix is aDateTime(timezone=True)migrationCORS errors masking real backend failures β FastAPI's CORS middleware doesn't add
Access-Control-Allow-Originheaders to 500-level responses that bypass the middleware chain, so browser-side the datetime bug above surfaced as a misleading CORS error. Diagnosed by hitting the endpoint directly withcurlto see the actual traceback
π§ Learnings
LLMs are most powerful as one component in a larger system, not the whole system β The LLM handles qualitative README analysis (something quantitative metrics can't capture), but the scoring engine, data pipeline, and trend detection are all deterministic. This hybrid approach is more reliable and auditable than an end-to-end LLM solution
Evaluation methodology should be designed before the feature, not after β Building the backtesting framework alongside the scorer (not as an afterthought) caught the star-count-as-signal assumption early and directly shaped the final formula weights
Error isolation at the data-source level is critical for multi-source pipelines β Each collector (GitHub, npm, PyPI, HN) is wrapped in its own try/except. A transient npm outage returns neutral scores for that dimension instead of crashing the entire pipeline. Partial data is far more useful than no data
flush()vscommit()matters for pipeline transactionality β Storage functions usesession.flush()to generate IDs without committing, letting the pipeline orchestrator wrap the full sequence (project β snapshot β assessment β score) in a single transaction. If any step fails, the entire operation rolls back cleanly instead of leaving the DB in an inconsistent stateDocumentation is a forcing function for design quality β Writing the scoring methodology doc surfaced that 40% of the growth sub-score weight was dead (star velocity always returning 0). Writing the README's architecture section revealed the collectorβscorerβstorageβAPI flow wasn't obvious from the code alone. Both led to concrete improvements