A high-stability, extensible C++20 library for parsing CCS (Contest Control System) event-feed and contest package data. Designed for ICPC-style programming contests.
- CCS Spec 2023-06 — full event-feed + contest package support
- CCS Spec 2026-01 — full event-feed + contest package support
- Auto-detection — best-effort version detection from feed content
| Input Type | API |
|---|---|
std::istream stream |
EventFeedParser::ParseStream(stream, options) |
Local .ndjson file |
EventFeedParser::ParseFile(path, options) |
| Contest package directory | PackageParser::ParsePackageDirectory(path, options) |
| Contest package ZIP | PackageParser::ParsePackageZip(path, options) |
| Incremental line-by-line | EventFeedParser::CreateStreamingSession(options) |
Prerequisites:
- vcpkg installed (e.g. at
C:\vcpkg) - MSVC Build Tools (Visual Studio Build Tools with VC component)
1. Add to PATH (user or system environment variables):
# Example, you should modify this to your custom vcpkg installation dir
C:\vcpkg
C:\vcpkg\installed\x64-windows\bin2. Set the MSVC compiler root (adjust the year/edition as appropriate):
# Example, you should modify this to your actual installation dir
$env:BAZEL_VC="C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\VC"3. Install the curl dependency via vcpkg:
vcpkg install curl:x64-windows4. Build and test:
bazel build //...
bazel test //... --cxxopt=/std:c++20 --repo_env=VCPKG_INSTALLATION_ROOT=C:/vcpkg1. Install dependencies:
sudo apt-get update
sudo apt-get install -y libcurl4-openssl-dev pkg-config2. Build and test:
bazel test //... --cxxopt=-std=c++201. Install dependencies via Homebrew:
brew install curl pkg-config2. Build and test:
bazel build //...
bazel test //... --cxxopt=-std=c++20#include "ccsparser/ccsparser.h"
using namespace ccsparser;
// Parse from file
ParseOptions opts;
opts.version = ApiVersion::kAuto;
opts.error_policy = ErrorPolicy::kContinue;
auto result = EventFeedParser::ParseFile("event-feed.ndjson", opts);
if (result.ok()) {
auto& pr = result.value();
auto* contest = pr.store.GetContest();
auto teams = pr.store.ListObjects(ObjectType::kTeams);
// ...
}struct ParseOptions {
ApiVersion version = ApiVersion::kAuto;
ErrorPolicy error_policy = ErrorPolicy::kContinue;
UnknownFieldPolicy unknown_field_policy = UnknownFieldPolicy::kPreserve;
UnknownTypePolicy unknown_type_policy = UnknownTypePolicy::kWarnAndIgnore;
bool keep_event_log = true;
bool keep_raw_json = false;
bool enable_validation = true;
bool enable_checkpointing = true;
ParseLimits limits;
};GetObject(type, id)— lookup by type and IDListObjects(type)— list all objects of a typeGetContest()/GetState()— singleton accessGetEventCount()— total events processedCreateCheckpoint()/Rollback(cp)— checkpoint/rollbackAddObserver(observer)— register for notifications
class Observer {
virtual void OnRawEventParsed(const RawEvent& event);
virtual void OnObjectUpserted(ObjectType type, const std::string& id, const ContestObject& obj);
virtual void OnObjectDeleted(ObjectType type, const std::string& id);
virtual void OnCollectionReplaced(ObjectType type, size_t count);
virtual void OnDiagnostic(const Diagnostic& diag);
virtual void OnEndOfUpdates();
};auto session = EventFeedParser::CreateStreamingSession(opts);
session->ConsumeLine(line);
session->Finish();
const auto& store = session->store();contest, judgement-types, languages, problems, groups, organizations, teams, persons, accounts, state, submissions, judgements, runs, clarifications, awards, commentary
| Feature | 2023-06 | 2026-01 | Internal |
|---|---|---|---|
contest.penalty_time |
int (minutes) |
RELTIME string |
RelativeTime (ms) |
clarifications recipient |
to_team_id |
to_team_ids / to_group_ids |
vector<string> |
awards |
standard | + honors/high-honors/highest-honors | string (no enum) |
problems limits |
— | memory/output/code_limit | optional<int> |
judgements.current |
— | boolean |
optional<bool> |
FILE.tag |
— | array of string |
vector<string> |
Two error policies:
kContinue(default) — skip bad records, emit diagnostics, continuekFailFast— abort on first error
malformed_json, invalid_utf8, line_too_long, missing_type, invalid_type_field, missing_data, invalid_id_type, unknown_event_type, invalid_object_shape, invalid_required_field, invalid_time, invalid_reltime, invalid_collection_item, delete_unknown_object, version_conflict, max_consecutive_errors_exceeded
| Category | Treatment |
|---|---|
| Line-level (bad JSON, too long) | Skip line, emit diagnostic, no store mutation |
| Event skeleton (missing type/data) | Discard event, no token/cursor update |
| Object data (bad fields) | Atomic event failure, old state preserved |
| Collection replace (any element fails) | Entire replace fails, old collection preserved |
| Singleton update (decode failure) | Old singleton preserved |
| Delete unknown | Warning diagnostic, not fatal |
| Unknown type | Warning + ignore (configurable) |
| Unknown field | Preserve + warning (configurable) |
struct ParseLimits {
size_t max_line_bytes = 64 * 1024 * 1024;
size_t max_diagnostics = 10000;
size_t max_consecutive_errors = 100;
};bazel build //:ccsparser
bazel test //...Requires: Bazel 9.0.1, C++20 compiler.
The library includes a reusable scoreboard module (#include "ccsparser/scoreboard_builder.h") that constructs ICPC-style final standings from a ContestStore:
auto sb = ccsparser::BuildScoreboard(parse_result.store);
// sb->rows: ranked teams with solved, penalty, per-problem cells, awardsRanking: solved ↓, penalty ↑, team_id (stable tie-break). Awards are associated with team rows. First-to-solve is flagged per cell.
A standalone cc_binary for human review of parsed event-feeds:
bazel run //:eventfeed_scoreboard_preview -- \
--eventfeed=/path/to/event-feed.ndjson \
--output=scoreboard.html \
[--title="My Contest"] \
[--version=auto|2023-06|2026-01]Produces a self-contained HTML file with:
- Full ranked scoreboard table with problem cells
- Award badges (gold/silver/bronze/default styling)
- First-to-solve highlights
- Problem color blocks in column headers
- Parser diagnostics summary in footer
include/ccsparser/ — Public headers
src/
core/ — Status, Diagnostic, types, time utilities
event/ — NDJSON line parser, RawEvent
profile/ — Version profiles, object decoders
store/ — ContestStore implementation
scoreboard/ — ScoreboardBuilder
api/ — EventFeedParser, StreamingParseSession
io/ — PackageLoader, FileRefResolver
tools/ — Standalone binaries (manual tag)
tests/
unit/ — Unit tests
integration/ — Integration tests (per-version, package, case files)
regression/ — Regression tests
fixtures/ — Test data
docs/ — API reference, architecture
- Event layer decoupled from object layer — NDJSON parsing produces RawEvents; version profiles decode them into typed objects
- Version differences isolated in profiles — adding a new CCS version requires only a new profile, not changes to the main loop
- Atomic event application — objects are fully decoded and validated before any store mutation
- No JSON types in public API — public headers have zero dependency on nlohmann/json
- Unknown fields preserved — extensibility without data loss
- HTTP client / REST API consumption
- XML feed
- Pre-2020
op=create/update/deletefeed format - Resolver business logic
- UI / scoreboard rendering
-
ZIP support via system
unzip: Rather than bundling a C library (miniz/libzip), ZIP extraction delegates to the systemunzipcommand. This keeps dependencies minimal; production deployments can ensureunzipis available. -
Rollback via replay: Checkpoint/rollback clears state and truncates the event log. Full state restoration requires re-feeding events through the session, which provides correctness guarantees but is O(n) in events.
-
Auto-detection heuristic: Version auto-detection looks at
penalty_timetype andclarificationfield names. It defaults to 2023-06 when ambiguous. Explicit version specification is recommended for reliable behavior.