Every day, millions of public GitHub events (pushes, PRs, issues, stars, forks) are generated, a goldmine of signals about developer productivity, community health, and ecosystem trends. But the raw data from GH Archive isn't query-ready.
GitPulse Analytics solves this with a Lakehouse pipeline (Bronze β Silver β Gold) that ingests, cleans, and aggregates this data into an interactive dashboard. The result? Visibility into what the GitHub ecosystem is doing right now: trending repos, community health, and code quality signals. All with open-source tools, running locally by default.
Local-first and learning-focused. This is a personal study project: it runs on a single machine with local infrastructure, and the hosted dashboard is a read-only demo fed by a static dataset snapshot.
Dashboard pages: overview, community, code quality, and ecosystem.
Data flows from GH Archive β Bronze (raw) β Silver (cleaned) β Gold (aggregated) β Dashboard.
| Layer | Storage | Format | Description |
|---|---|---|---|
| Bronze | MinIO bronze |
Parquet (zstd) | Raw .json.gz β Parquet. Immutable, partitioned by year/month/day/hour. |
| Silver | MinIO silver |
Delta Lake | Cleaned, typed, validated with Pandera. 1 fact (events) + 3 dimensions (actors, repos, orgs). UTC timestamps, same partition scheme. |
| Gold | MinIO gold |
Delta Lake | daily_activity table at (day, repo_id, org_id) grain with GitPulse Score (0β100). Partitioned by year/month. |
A daily activity index (0β100) built from four weighted GitHub event types. It measures how much a repository did on a given day, not how healthy or important it is.
| Event | Weight | Rationale |
|---|---|---|
| PushEvent | 1Γ | Baseline coding activity |
| WatchEvent (star) | 2Γ | Community interest |
| IssuesEvent | 3Γ | Bug tracking & discussion |
| PullRequestEvent | 5Γ | Collaboration & code review |
score = min(pushΓ1 + watchΓ2 + issuesΓ3 + prΓ5, 100)
| π Python 3.12+ | python.org/downloads |
| βοΈ make | Built-in on macOS/Linux Β· Windows (WSL) |
| π³ Docker & Compose | docs.docker.com/get-docker |
| π¦ uv | docs.astral.sh/uv pip install uv or curl -LsSf https://astral.sh/uv/install.sh | sh |
git clone https://github.com/geogabrielp/gitpulse-analytics.git
cd gitpulse-analytics
make install # creates .env from .env.example and syncs dependenciesmake up # MinIO, PostgreSQL, Prefect server
make pipeline # bronze β silver β gold (last 7 days)The window and the layers are configurable:
uv run main.py --days 15 # full pipeline, 15 days
uv run main.py bronze --days 15 # only bronze
uv run main.py silver # only pending partitions
uv run main.py gold # only pending daysmake dashboard # http://localhost:8501The code default is
hf, so the hosted demo needs no configuration. Sethfin.envto read the public snapshot locally.
make serve # long-running process, one run every hour at :35 UTCThe Prefect server must be running. Trigger a run manually with:
uv run prefect deployment run 'hourly-pipeline/gitpulse-hourly'| Service | URL |
|---|---|
| MinIO API | http://localhost:9010 |
| MinIO console | http://localhost:9011 |
| Prefect UI | http://localhost:4200 |
| Dashboard | http://localhost:8501 |
π‘ Run
make helpfor every command.make downstops the stack,make resetalso deletes the MinIO and Postgres volumes (destructive).
make install copies .env.example to .env on first run (an existing .env is never overwritten). The file is gitignored, so credentials stay local.
| Variable | Purpose |
|---|---|
MINIO_ROOT_USER, MINIO_ROOT_PASSWORD |
MinIO credentials used by the pipeline and the local dashboard |
PREFECT_API_URL |
Prefect server API (http://localhost:4200/api) |
DASHBOARD_DATA_SOURCE |
minio for local development or hf for the public snapshot (the code default) |
DASHBOARD_HF_DATASET |
Hugging Face dataset read when DASHBOARD_DATA_SOURCE=hf |
DASHBOARD_CACHE_TTL |
Query cache TTL in seconds |
DASHBOARD_EXPORT_PATH |
Output folder for make export-data (gitignored) |
Clean run from make reset β make up β uv run main.py --days 15 on a MacBook Pro M1 Pro (8 cores, 16 GB RAM, 8 ingestion workers).
Workload: 15 days of GH Archive (2026-09-29 β 2026-10-14), 381 hourly files, 24.5M events.
| Stage | What happens | Duration | Share | Data written |
|---|---|---|---|---|
| Bronze | GH Archive .json.gz β Parquet, 8 workers |
4m 53s | 63% | 4.6 GB |
| Silver | Validation, cleaning, dedup | 2m 37s | 34% | 982 MB |
| Gold | Daily repo metrics | 14s | 3% | 75 MB |
| End-to-end | 7m 48s | 100% | 5.6 GB |
Bronze is download-bound, so its share tracks network throughput, silver and gold scale linearly with data volume.
| Page | Highlights |
|---|---|
| π Overview | KPI cards (events, repos, contributors, score), daily timeline, event type distribution, top repos, stacked events |
| π₯ Community | Bot vs. human breakdown, top contributors, contributor growth, activity heatmap Γ hour |
| π» Code Quality | PR merge rate, PR funnel, issue close rate, review metrics (reviews & comments per PR), Score timeline |
| π Ecosystem | Org summary, trending repos, star & fork growth, stars vs. engagement analysis |
| Category | Technology | Purpose |
|---|---|---|
| Storage | MinIO + Delta Lake | S3-compatible storage, ACID transactions, time travel, schema enforcement |
| Format | Apache Parquet (zstd) | Columnar storage with high compression |
| ETL | Polars | Lazy DataFrame API, streaming, no pandas in the ETL |
| Validation | Pandera | Runtime data contracts, fast-fail on quality issues |
| Orchestration | Prefect v3 | Retries, monitoring, idempotent flows, hourly schedules |
| Visualization | Streamlit + ECharts | Interactive dashboard with rich chart types |
| Public data | Hugging Face Datasets | Parquet snapshot read by the hosted dashboard |
| Config | Pydantic Settings | Typed, validated environment variables |
| Infra | Docker Compose | Local MinIO, PostgreSQL, Prefect server |
| Deps | uv | Fast Python package manager |
All data comes from the GH Archive, a public dataset recording every public GitHub event since February 2015, released as hourly .json.gz files.
Bronze (raw): all event types are ingested with no filtering, every GH Archive event lands in the lake.
Silver (cleaned): 12 event types with analytical value are kept. 4 low-value types are explicitly excluded:
| Excluded type | Reason |
|---|---|
GollumEvent |
Wiki page changes, no analytical signal |
MemberEvent |
Internal membership management |
DiscussionEvent |
Discussions (low adoption in GH Archive) |
PublicEvent |
Repo visibility toggle (one-time event) |
Tracked event types:
PushEvent Β· PullRequestEvent Β· IssuesEvent Β· WatchEvent Β· IssueCommentEvent Β· PullRequestReviewEvent Β· PullRequestReviewCommentEvent Β· CreateEvent Β· DeleteEvent Β· ForkEvent Β· ReleaseEvent Β· CommitCommentEvent
gitpulse-analytics/
βββ src/core/ # Business logic (orchestrator-agnostic)
β βββ config/ # Pydantic settings for env vars
β βββ contracts/ # Pandera schemas for bronze, silver, gold
β βββ ingestion/ # GH Archive β Bronze
β βββ transforms/ # Bronze β Silver, Silver β Gold
β βββ helpers/ # S3, Delta Lake, logging
βββ pipelines/ # Prefect flows (thin orchestration) and the hourly flow
βββ dashboard/
β βββ app.py # Streamlit entrypoint and navigation
β βββ queries.py # Cached queries over MinIO or HF Parquet
β βββ pages/ # One module per dashboard page
β βββ components/ # Charts, KPI cards, sidebar
βββ tests/ # Pytest suite (contracts, transforms, helpers)
βββ scripts/ # Export lake tables to Parquet for Hugging Face
βββ infra/ # MinIO bucket bootstrap
βββ docs/images/ # Architecture diagram and demo GIF
βββ main.py # CLI entrypoint to run or serve the flows
βββ docker-compose.yaml # MinIO, PostgreSQL, Prefect server
βββ Makefile # Common commands (see `make help`)
βββ pyproject.toml # Dependencies and tool configuration
βββ uv.lock # Locked dependency graph
βββ .env.example # Configuration template
βββ .github/workflows/ # CI: lint, format, types, tests
make test # pytest, fast-fail
make test-cov # pytest with coverage
make lint # ruff check
make format # ruff format
make typecheck # ty checkCI (.github/workflows/ci.yml) runs the same checks: ruff lint, ruff format --check, ty check, and the test suite.
| Aspect | Implementation | Benefit |
|---|---|---|
| Data contracts | Pandera schemas validate every write at Silver and Gold layers | Corrupted data is rejected immediately, never silently ingested, downstream consumers always get consistent, reliable data |
| Idempotent pipelines | Partition-aware overwrite mode, skip-if-exists checks | Safe to re-run any flow without duplicates or data loss |
| Clean architecture | Business logic isolated in src/core/, pipelines are thin orchestrators |
Swap orchestration (Prefect, Airflow, Dagster) without touching transforms |
| Reproducible infra | Docker Compose for MinIO + PostgreSQL + Prefect, uv for deterministic deps |
New contributors run the full stack with 2 commands |
| Observability | Structured logging at every stage, Prefect retries (3Γ, 30s delay), error UI in dashboard | Failures are traceable and recoverable without manual intervention |
| Typed configuration | Pydantic Settings validates all environment variables at startup | Misconfiguration is caught before pipeline execution, not during |
- Batch, not streaming. Data is processed hourly from GH Archive files, there is no real-time ingestion.
- Single node. Everything runs on one machine with Docker Compose: no auth, no high availability, no horizontal scaling.
- Static public demo. The hosted dashboard reads a dataset snapshot uploaded manually, so it can lag behind the local lake.
- GH Archive delays. Hours can be published late, the hourly flow re-checks a rolling window because Prefect 3 does not backfill missed schedules.
Contributions are welcome! Feel free to open an issue or submit a PR.
MIT Β© 2026 George Gabriel - fork it, modify it, or just use it as a reference for your own projects.