Modern combat analytics platform for Guild Wars 2 WvW (World vs World).
Independent third-party platform — no dps.report, no Elite Insights web. WvW combat logs (
.zevtc) are parsed locally and stored in a stable internal model from which all analytics, API, and frontend derive.
| Release | Highlights |
|---|---|
| v0.15.0 | GlobalStatsBar, timeline activity toggle, CSV removal, heatmap in Analyse, arq-worker, tab fix, unit tests. |
| v0.14.3 | PlayerPositionHeatmap canvas component + presence_pct math fix. |
| v0.14.2 | Plan 173: 14 boon uptimes + presence % + 14 outgoing boons in Combat Readout. Grouped bars + tooltips. |
| v0.14.1 | Slow-path blob walk tests (3 hermetic tests for _contributions_from_blob_walk). |
| v0.14.0 | CI guard against legacy db.query() reintroduction. Coverage plan updated. |
| v0.13.9 | Zero legacy SQLAlchemy queries — last db.query() migrated to select(). |
| v0.13.8 | backfill_role_detection tests (67%→85%). Redundant limiter.reset() cleanup. |
| v0.13.7 | Rate limiting integration tests. Guilds route tests (0%→70%). |
| v0.13.6 | Players route coverage (30%→69%). Guilds router mounted in main.py. |
See CHANGELOG.md for the full per-release history.
- 🎯 Per-target / per-subgroup / per-skill roll-ups on every fight — DPS, healing, and buff removals via stable pydantic aggregations with deterministic ordering + cross-field invariants.
- 📈 Account-level historical timelines — per-day / per-fight bucketing, linear / log Y-axis, and player-name resolution on the fight drilldown's TargetFilter.
- 🔌 Webhook subscriptions for parse-completion notifications — HMAC-SHA256 signed, 3-attempt retry + DLQ + replay, with SSRF block (HTTPS-only + universal private-IP gate).
- 🎭 Heuristic role detection — per-(fight, account) DPS / HEAL / STRIP / BOON / MIXED classification from the 3 magnitudes + spec/profession hint table.
- 📊 Per-player timeline overlay — one per-bucket series per player agent for multi-line chart overlays.
- 🎨 GW2Mists-inspired frontend — dark palette, sticky glass header, inline SVG logo, favicon, and Next.js
<Link>navigation. - ⚔️ Combat-readout UI — per-player Damage / Heal / Boons / Defense 4-table roll-up via
/fights/[id]?tab=readout(default tab), with native HTML sortable tables, boon In/Out columns (14 boons), GlobalStatsBar (squad DPS/Heal/Strips/Cleanses/CC/Healers/Supports), compact FightSummaryCards (Top 3 per category), timeline activity toggle ("Toute la durée" / "Activité seulement"), and 2D position heatmap with play/pause animation. - 🧪 Comprehensive multi-layer test suite —
pytest(libs + apps) +vitest(web components) + Playwright e2e (web flows), 117 tests, 84% coverage, all gated and green. - 🛡️ Audit hardening — Caddyfile HSTS/CSP, CI
pip-audit/pnpm-audit, Next.js error boundaries, headers() defense-in-depth, Trivy filesystem scan + detect-secrets pre-commit hook. - 📊 Observability — OpenTelemetry tracing (FastAPI + SQLAlchemy + Redis) with OTLP HTTP export, structured JSON logging, Prometheus metrics endpoint, Grafana dashboard.
- 📦 Pure monorepo —
libs/gw2_core(no I/O),libs/gw2_evtc_parser(replaceable Protocol),libs/gw2_analytics(frozen pydantic),apps/api(FastAPI),web(Next.js). - 🔧 Zero legacy SQLAlchemy — all production queries use
select()(SQLAlchemy 2.x style). CI guard prevents regression. - ⚡ Arq worker — dedicated background worker process for .zevtc parsing, eliminating in-request GIL contention on parallel uploads.
gw2_evtc_parser
│
▼ produces
gw2_core ◀──constrains── gw2_analytics
│
▼
apps/api ──gw2_api_client── GW2 v2
│
▼
web (Next.js)
| Component | Role |
|---|---|
libs/gw2_core |
Stable Pydantic models (combat + API). Single source of truth. No I/O. |
libs/gw2_evtc_parser |
Binary .zevtc parser behind an EvtcParser Protocol. V1.3 layout. |
libs/gw2_analytics |
Single-, multi-fight, and event-driven aggregations. Frozen pydantic shapes with deterministic ordering + cross-field invariants. |
libs/gw2_api_client |
Typed async httpx wrapper for the Guild Wars 2 REST API v2. |
apps/api |
FastAPI gateway. MinIO blobs + Alembic + Postgres. |
web |
Next.js frontend. AG Grid Community tables + SSR fetches. |
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/uploads |
Ingest a .zevtc log; returns 201 (parse runs in background). |
GET |
/api/v1/uploads/{id} |
Upload metadata. |
GET |
/api/v1/fights[/{id}] |
List fights (paginated) or fetch a single fight. |
GET |
/api/v1/fights/{id}/events |
Per-target trio (DPS + healing + buff removal) + per-bucket event windows. |
GET |
/api/v1/fights/{id}/squads |
Per-subgroup roll-up. |
GET |
/api/v1/fights/{id}/skills |
Per-skill hit count + damage / heal / strip totals. |
GET |
/api/v1/fights/{id}/readout |
Combat readout — per-player Damage / Heal / Boons / Defense 4-table roll-up. |
GET |
/api/v1/fights/{id}/timeline?window_s=N |
Per-fight temporal view (3-series, M:SS relative time). |
GET |
/api/v1/fights/{id}/timeline/players |
Per-player timeline overlay. |
GET |
/api/v1/fights/{id}/players/{account}/skills |
Per-player skill roll-up + loadout. |
GET |
/api/v1/players?profession=&limit=&offset= |
Cross-fight player roll-up (paginated). |
GET |
/api/v1/players/{account_name:path} |
Player profile + per-fight breakdown. |
GET |
/api/v1/players/{account_name:path}/timeline |
Account-level historical timeline. |
POST/GET/DELETE |
/api/v1/webhooks[/{id}] |
Webhook subscription management (HTTPS-only URLs). |
GET |
/api/v1/health/summary |
Operational drift probe. |
GET |
/api/v1/healthz |
Liveness probe. |
| Route | Capture |
|---|---|
/ |
![]() |
/upload |
![]() |
/fights |
![]() |
/players |
![]() |
/players/[account_name] |
![]() |
/fights/[id]?tab=replay |
![]() |
# 1. Install uv (Python package manager)
curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. Install all monorepo deps including libs + apps
uv sync
# 3. Install git hooks
uv run pre-commit install
# 4. Bring up the infra (Postgres + MinIO + Redis + arq-worker)
# The arq-worker handles .zevtc parsing in a dedicated background
# process with its own GIL, avoiding in-request latency.
docker compose up -d
# If the infra is already running from a previous version, start
# just the new arq-worker:
# docker compose up -d arq-worker
# 5. Configure local app env (DB + S3 creds; never commit the real .env)
cp .env.example .env
# 6. Apply the Postgres schema
cd apps/api && uv run alembic upgrade head && cd ../..
# 7. Boot the API (http://localhost:8000/docs)
uv run fastapi dev apps/api/src/gw2analytics_api/main.py
# 8. Frontend
cd web
pnpm install
pnpm dev # http://localhost:3000# Run all backend checks (lint + typecheck + tests)
uv run ruff check libs apps
uv run pytest libs apps -q
# Run all frontend checks (typecheck + lint + tests)
cd web
pnpm typecheck && pnpm lint && pnpm test:unit| File | Purpose |
|---|---|
| CHANGELOG.md | Canonical per-commit history. |
| CONTRIBUTING.md | Workflow conventions, branch protection rules, CI gates. |
| docs/ROADMAP.md | Forward-looking candidates and technical-debt ledger. |
| monitoring/grafana-dashboard.json | Pre-built Grafana dashboard (upload rate, parse duration, errors, queue, drift). |
| plans/README.md | Senior-advisor audit trails and scoped cycle implementation plans. |
See CONTRIBUTING.md for branch protection, pre-commit, code style, and test requirements.
gw2_coreis the only contract between layers. Everything depends on it; it depends on nothing but Pydantic.- The parser is replaceable behind the
EvtcParserProtocol. Swap Python for Rust + PyO3 with zero churn elsewhere. - The frontend never knows about EVTC, parser internals, or DB schema — only the OpenAPI surface.
- Each component evolves independently — enforced by
pyproject.tomlper lib.
Proprietary software — all rights reserved. Copyright (c) 2024-2026
Roddy. See LICENSE for the full legal text and
NOTICE.md for a plain-language summary of what is
and isn't permitted without explicit written permission from the
copyright holder.
The project is independent third-party software — no affiliation with ArenaNet or any Guild Wars 2 trademark holder. All Guild Wars 2 game content references are nominative fair use under the GW2 content policy.





