- Python 67.4%
- HTML 31.4%
- Batchfile 0.9%
- Shell 0.2%
- PowerShell 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| assets | ||
| data | ||
| docs | ||
| tests | ||
| tools | ||
| web | ||
| .gitattributes | ||
| .gitignore | ||
| app.py | ||
| CHANGELOG.md | ||
| config.example.json | ||
| HISTORY.md | ||
| LICENSE | ||
| README.md | ||
| requirements.txt | ||
| rik_db.py | ||
| SECURITY.md | ||
| start-2023-test.bat | ||
| start-2026-live.bat | ||
| start.bat | ||
RIK Election Control Center
Live Serbian election results, broadcast graphics and vMix control from one local control center.
The RIK Election Control Center polls the public Serbian electoral commission (RIK) results feed, keeps a local history of every snapshot, and renders election-night graphics into vMix or OBS through a single browser input. One operator page controls what goes on air: national dashboards, regional and city results, polling-station detail, abroad results, comparisons and tickers. The application is a plain Python 3 service with no third-party packages, and it runs entirely on the production machine.
Overview | Features | Quick Start | Configuration | Documentation | History
Overview
The Control Center sits between the official RIK results and the graphics that go to air:
- It reads the public RIK results API for a parliamentary election round and keeps polling until results are published.
- It stores every distinct result set in a local SQLite database, so earlier states of the night remain available.
- It renders all graphics as HTML and JavaScript inside a browser overlay, which means there are no title templates to map by hand.
- It exposes a small local HTTP API that an external automation agent can use to select graphics and read state.
The project was built for election-night operations on a single Windows production machine running vMix, with a second machine available on the local network for automation. Nothing in the graphics path depends on a cloud service.
Visual assets and screenshots
The repository currently ships two real graphic assets and no user-interface screenshots:
assets/tracker-qr.png: the QR code used by the QR tracker scene, which points viewers at a public live tracker page.assets/statistical-regions-serbia.svg: a lightweight placeholder for the statistical region map. The full statistical-regions map is downloaded and cached indata/on first use.
No control-center screenshots, dashboard captures or vMix output captures exist in the repository yet. Screenshots are recommended for a future documentation pass, because a landing page cannot show the graphics from source code alone. The most useful captures, in order of value:
- Control center page with the scene selector and the current national dashboard.
- On-air national dashboard, top-5 bar chart and vote share donut.
- Lower third ticker and the waiting-for-RIK state.
- Region heatmap and region bars.
- Municipality result, polling station result and polling station detail.
- Abroad dashboard and embassy carousel.
- Actual vMix program output with a graphic on air.
- Production monitor page showing the connected output clients.
Features
Live election results
- Reads the public RIK results API for a parliamentary election round (
election_type = 2). - Discovers election rounds, regions, municipalities and polling stations, and refreshes that hierarchy on a schedule.
- Tracks processed polling stations, count progress and turnout.
- Tracks valid and invalid ballots, nationally and per region.
- Produces list and party results, including per-list percentage, votes and mandate information.
- Covers every result level: national, region, municipality and city, polling station, and polling stations abroad.
- Records every distinct source state as a snapshot, so changes during the night are preserved instead of overwritten.
- Detects revisions in the source data and can show a revision alert on air.
- Keeps a list registry of the participating electoral lists, refreshed on a schedule.
- Can monitor the RIK documents page to detect newly published material.
Broadcast graphics
- One browser overlay renders every graphic: no vMix title templates and no per-field mapping are required for the graphics path.
- National graphics: lower third ticker, national dashboard, top 3, top 5, full results, count progress, vote bars, vote share donut, turnout donut, turnout overview, valid and invalid ballots, ballots by region, mathematical seat distribution, region heatmap, region bars, major cities and city ranking, source status, QR tracker.
- List-oriented graphics: list detail and list strongholds for a selected electoral list.
- Abroad graphics: top abroad, abroad directory, abroad dashboard, embassy carousel and a single foreign location result.
- Comparison graphics: national comparison, region comparison and municipality comparison against a selected earlier round.
- Breaking graphics: a breaking result scene and a revision detail scene, with a takeover timer that returns to the previous scene.
- Picture-in-picture scene for a smaller result panel over the program feed.
- Waiting state: while RIK has not published results yet, the overlay shows an explicit waiting state rather than empty numbers.
vMix integration
- Add exactly one
1920 x 1080Web Browser input pointing athttp://127.0.0.1:8788/overlay/vmix.html. - The Control Center switches scenes, selects regions, municipalities, stations and lists, and adjusts ticker speed and graphic scale.
- Dedicated overlay aliases exist for production clients:
/overlay/vmix.html,/overlay/obs.html,/overlay/live.htmland/overlay/preview.html. - Optional legacy bridge for GT title and Data Source workflows: pushes text into a vMix title through the vMix HTTP API (
http://127.0.0.1:8088) with TCP function fallback (127.0.0.1:8099), matches the input automatically by name or by fields, and can cycle Data Source rows. - Optional continuous ticker mode joins the list rows into one scrolling line, and adaptive timing scales the cycle to the length of the text.
Control center
- Operator page at
http://127.0.0.1:8788/controlwith the full scene list, scope selectors, comparison round, seat distribution settings and rotation settings. - Interface language can be switched between Serbian Latin and English. On-air graphic labels use the same two options, and the interface choice is stored locally in the browser.
- Scene presets for common positions, for example the national dashboard, Beograd, Novi Sad, Nis, the abroad dashboard, Sarajevo and Vienna.
- Automatic rotation of a configured scene list, with a per-scene rotation interval and a separate interval for the embassy carousel.
- Status dashboard at
http://127.0.0.1:8788/and a production monitor athttp://127.0.0.1:8788/productionthat shows which output clients are connected and when the data last changed.
Automation and the RIK Agent API
- The RIK Agent API is the local technical and control interface of this application. It is served by the same process as the Control Center.
- The RIK Agent API reports capabilities, scenes, presets, output status, elections, lists, modes and scope, and accepts control requests such as scene selection.
- Hermes Agent is the external automation agent that can call the RIK Agent API over the local network. The repository contains the LAN instructions and the self-test scripts used to verify that path.
- The two names are not interchangeable: the Control Center serves the RIK Agent API, and Hermes Agent is a client of it.
Data, history and analysis
- SQLite database (
data/rik_elections.sqlite3) with snapshots, scope states, per-list snapshot results, the election hierarchy, electoral lists, documents, collector events and an analysis cache. - Snapshot deduplication: only distinct source states are stored, and the latest good snapshot is reused at startup so the graphics are never empty.
- Reconciliation between rounds and scopes, which is what makes the comparison graphics possible.
- Historical endpoints for rounds, national history and a database timeline, plus a database schema endpoint.
- Deep analysis mode walks every region, municipality and polling station with a configurable request delay, stores the result in the analysis cache and reports progress.
- Database backup helper (
tools/backup_database.py,tools/backup-database.bat).
Operations and diagnostics
- Heartbeat registry for output clients with a diagnostic view of what is on air (
/api/output/status,/api/agent/outputs). - Network status endpoint that reports the listen address, the advertised address and the URLs a second machine can use.
- Collector and database diagnostics, source status graphic and collector event log.
- Read-only behaviour towards RIK: the service only reads public pages and never submits anything.
- Protections against false zeros: empty response bodies are retried, the mode profiles define a fallback round, and a hierarchy refresh keeps round discovery current.
How It Works
In short: the public RIK results API feeds the ingestion and processing layer, which feeds the Control Center service, and from there the browser control UI, the local SQLite history and the browser graphics engine are served, with the overlay pages rendered into vMix or OBS program output.
The graphics engine does not call RIK directly. The collector keeps the current state, and the browser overlay renders it, which keeps the on-air picture stable when the source is slow or temporarily unavailable.
Graphics and scenes
Every scene below is defined in the application source and can be selected from the Control Center or through the RIK Agent API.
National graphics:
| Scene | Graphic | Purpose |
|---|---|---|
lower |
Lower third / ticker | Continuous ticker of the leading lists |
dashboard |
National dashboard | Turnout, processed stations and current standings |
top3 |
Top 3 | Three leading lists with vote share |
top5 |
Top 5 | Five leading lists with vote share |
full_results |
Full results | All lists with votes and share |
progress |
Count progress | Processed polling stations and counted votes |
breaking |
Breaking result | Highlighted headline result |
changes |
Latest RIK changes | Most recent changes in the source data |
revision_detail |
Revision detail | Detail view of a detected revision |
pip |
Picture in picture | Compact result panel over the program feed |
bars |
Vote bars | Horizontal bar comparison of list results |
party_pie |
Vote share donut | Share of votes per list |
turnout |
Turnout donut | Share of voters who turned out |
turnout_overview |
Turnout overview | Turnout by region |
ballots |
Valid / invalid ballots | Valid and invalid ballot proportion |
ballots_regions |
Valid / invalid by region | The same split per region |
seat_distribution |
Mathematical seat distribution | Seat projection from current shares |
region_map |
Region heatmap | Statistical regions coloured by metric |
region_bars |
Region bars | Region comparison as bars |
city_showcase |
Major cities | Selected major city results |
city_overview |
City ranking | Cities ranked by the selected metric |
list_detail |
List detail | Detail for one electoral list |
list_strongholds |
List strongholds | Strongest municipalities for one list |
foreign_top |
Top abroad | Leading lists among voters abroad |
foreign_directory |
Abroad directory | Overview of polling stations abroad |
comparison |
National comparison | Current round against an earlier round |
source_status |
Source status | Collector and source health |
qr |
QR tracker | Public live tracker QR code |
Regional and local graphics:
| Scene | Graphic | Purpose |
|---|---|---|
region |
Region result | Full result for one region |
city |
Municipality result | Full result for one municipality or city |
station |
Polling station result | Result at one polling station |
station_detail |
Polling station detail | Detailed polling station view |
comparison_region |
Region comparison | One region against an earlier round |
comparison_city |
Municipality comparison | One municipality against an earlier round |
Abroad graphics:
| Scene | Graphic | Purpose |
|---|---|---|
foreign_dashboard |
Abroad dashboard | Aggregate results of voters abroad |
embassy_carousel |
Embassy carousel | Rotating embassy and consulate results |
foreign |
Foreign location result | Result for one polling station abroad |
Utility scene:
| Scene | Graphic | Purpose |
|---|---|---|
off |
Hide graphic | Clear the on-air graphic |
Quick Start
- Clone the repository onto the production machine.
- Copy
config.example.jsontoconfig.jsonand review the RIK base URL, the election type and the polling interval. - Set
advertise_hostto the local address of the production machine if a second machine needs to reach the API. It is empty by default, and an empty value keeps the reported address on localhost. - Start the service with the start script for the mode you need:
start-2026-live.batfor the current parliamentary election orstart-2023-test.batfor the December 2023 test data. - Open
http://127.0.0.1:8788/to confirm the collector is reading RIK, then openhttp://127.0.0.1:8788/control. - Add one
1920 x 1080Web Browser input in vMix pointing athttp://127.0.0.1:8788/overlay/vmix.html, or a Browser source in OBS pointing athttp://127.0.0.1:8788/overlay/obs.html. - If a second machine will drive the graphics, run
tools/setup-lan-firewall.baton the production machine and usetests/test-hermes-agent-lan.shto verify access. - Select a graphic in the Control Center and confirm it in the vMix or OBS preview before taking it to air.
Detailed material is in docs/setup/ for the quick start and the local network, docs/integration/ for vMix, OBS, the dual output and the RIK Agent API, and docs/operations/ for the database. The full index is docs/README.md. The service reads public data only, so no credentials are required to start it.
Requirements
- Windows production machine with vMix, or a machine with OBS Studio.
- Python 3 installed, with the standard library only. There are no third-party packages and no build step;
requirements.txtdocuments this explicitly. - Network access to the public RIK results API.
- Write access to the
data/directory for the SQLite database and the cached map asset. - vMix: one Web Browser input, and the vMix HTTP API on
127.0.0.1:8088with TCP functions on127.0.0.1:8099only if the optional legacy bridge is used. - OBS Studio, if OBS is the production renderer: one Browser source.
Configuration
config.json is the runtime configuration file and is not tracked by Git; the repository ships config.example.json as the reference. The parameters below are the ones that matter in normal operation. Parameter names match the application source.
| Parameter | Purpose |
|---|---|
rik_base_url |
Base URL of the public RIK results API |
election_type |
Election type identifier, 2 for the parliamentary election |
election_label_contains |
Text used to find the intended election round |
fallback_election_round |
Round used when discovery cannot find the intended round |
poll_seconds |
Results polling interval in seconds |
round_refresh_seconds |
Interval for rediscovering election rounds |
hierarchy_refresh_seconds |
Interval for refreshing regions, municipalities and stations |
request_timeout_seconds |
Timeout for a single source request |
empty_body_retry_seconds |
Wait before retrying an empty response body |
empty_body_max_attempts |
Number of attempts before an empty body is treated as no data |
listen_host, listen_port |
Bind address and port of the local service |
advertise_host |
Address reported to other machines for LAN access; empty by default, and then reported as 127.0.0.1 |
agent_host_hint |
Informational address of the machine that may drive the API |
database_enabled, database_path |
SQLite storage switch and file location |
analysis_request_delay_seconds |
Pause between requests during deep analysis |
list_registry_refresh_seconds |
Interval for refreshing the electoral list registry |
documents_enabled, documents_max_pages |
RIK documents monitoring switch and page limit |
vmix_bridge |
Optional legacy vMix title and Data Source bridge |
Graphics and control parameters are part of the same file and can also be changed live from the Control Center or through the RIK Agent API:
| Parameter | Purpose |
|---|---|
scene |
Current graphic scene id |
lang |
On-air label language, sr for Serbian Latin and en for English |
speed |
Ticker speed |
scale |
Graphic scale |
top_n |
Number of lists shown in ranking graphics |
region, municipality, station |
Selected scope ids |
list_number |
Selected electoral list |
map_metric, city_metric, foreign_metric |
Metric used by map, city and abroad graphics |
comparison_round |
Round used by comparison graphics |
seat_total, seat_threshold_percent |
Seat distribution assumptions |
auto_rotate, rotation_seconds, rotation_scenes |
Automatic scene rotation |
embassy_seconds |
Dwell time in the embassy carousel |
breaking_takeover_seconds |
How long a breaking scene holds the on-air graphic |
show_update_alert, show_revision_alert |
On-air alerts for new data and revisions |
qr_caption, public_tracker_url |
Caption and target of the QR tracker scene |
vMix Integration
- The recommended setup needs one Web Browser input only:
http://127.0.0.1:8788/overlay/vmix.html. The overlay renders the current scene, so the Control Center changes the picture without touching the vMix project. - The browser input must be sized
1920 x 1080and use a transparent background to sit correctly over the program feed. - The Control Center runs on the same machine as vMix and talks to it only if the optional bridge is enabled. That bridge uses the vMix HTTP API on
127.0.0.1:8088, with TCP function calls on127.0.0.1:8099for longer ticker payloads, and falls back to HTTP when a command is too long for the function interface. - The bridge matches its target input automatically by input name or by field detection, then pushes text through the configured
field_map. It can cycle Data Source rows, use adaptive timing based on text length, and join rows into one continuous ticker. - No vMix title template, Data Source mapping or shortcut configuration is required for the browser overlay path.
- All host and port values in this section are the vMix defaults and are configurable.
OBS Studio
- Add one Browser source pointing at
http://127.0.0.1:8788/overlay/obs.html, sized1920 x 1080. - The same scene state drives vMix and OBS at the same time, so both renderers can be connected without duplicating the control logic.
- The production monitor shows which renderers are connected.
RIK Agent API
The RIK Agent API is the local-network technical and control interface served by the Control Center on the configured port, 8788 by default. It binds to all interfaces so the local machine and trusted devices on the same LAN can reach it, and it requires no Authorization header, no token and no API key. That is a deliberate design decision for a control interface intended for trusted local networks and not for public internet exposure.
Representative read endpoints:
/api/agent/status,/api/agent/capabilities,/api/agent/scenes,/api/agent/presets/api/agent/lists,/api/agent/outputs,/api/agent/resolve/api/control/state,/api/show,/api/scope,/api/mode,/api/raw/api/history/rounds,/api/history/national,/api/database/timeline,/api/database/schema,/api/database/status/api/registry/lists,/api/registry/status,/api/meta/regions,/api/meta/municipalities,/api/meta/stations/api/analysis/status,/api/analysis/data,/api/analysis/regions/api/output/status,/api/network/status,/api/diagnostics,/health
Control endpoints:
/api/agent/for remote control of the on-air graphic./api/takeover/start,/api/takeover/stop,/api/takeover/statusfor taking over the on-air scene and releasing it again./api/analysis/start,/api/analysis/stopfor deep analysis runs./api/refreshfor a manual refresh of the source data.
docs/integration/AGENT-API.md documents the payloads and expected values.
Hermes Agent Integration
Hermes Agent is the external automation agent that may drive this Control Center over the local network. The integration is deliberately thin: Hermes Agent calls the RIK Agent API, and the Control Center does not call back into it.
docs/internal/agent/HERMES-AGENT-KALI-LAN-INSTRUCTIONS.mddescribes the network path and the machine that may drive the API.tests/test-hermes-agent-lan.shverifies that the API is reachable from that machine.docs/internal/audits/holds the verification and audit procedures used against this release.tests/v16_agent_fixes_self_test.pyand the other self-test scripts cover the agent control path, the LAN path and the v1.6 fixes. Run them from the repository root.
Security and Deployment
- Local control design: the Control Center, the SQLite database and the graphics engine all run on the production machine. The graphics reach vMix or OBS through a browser input on
127.0.0.1. - The RIK Agent API is a local-network API by design and does not require authentication headers. It is intended for trusted local networks and not for public internet exposure, and the project does not add token or API-key authentication to it.
- LAN access is opt-in.
tools/setup-lan-firewall.batandtools/setup-lan-firewall.ps1create an inbound rule for TCP8788, limited to the private profile and the local subnet. Nothing is exposed beyond the local network. - Configuration and secrets stay outside version control:
config.json,.envfiles, keys, the runtime database, logs and caches are all ignored by Git. Onlyconfig.example.jsonis published. - The collector is read-only towards RIK. It requests public result pages and never submits data.
- The public tracker URL used by the QR scene is a normal public web address and is configurable.
- The
data/directory holds runtime state only and can be rebuilt from the source data.
History, database and analysis
The SQLite database is the reason the graphics survive the night. It stores snapshots, scope states, per-list snapshot results, the election hierarchy, the electoral list registry, documents and the analysis cache, with indexes for time-based lookup and snapshot deduplication so only distinct source states are kept.
This makes several things possible:
- Replay and comparison: an earlier state of the night, or an earlier round, can be compared against the current one.
- Stable startup: the last good snapshot is used until the source publishes something new.
- Deep analysis: a full walk of every region, municipality and polling station with a configurable delay, cached for later use.
- Diagnostics: collector events and source status are stored, not just displayed.
docs/operations/DATABASE.md describes the schema, the snapshot rules and the analysis cache in detail.
Documentation
| Document | Purpose |
|---|---|
README.md |
This project landing page |
SECURITY.md |
Security model and how to report accidental secret exposure |
HISTORY.md |
Development timeline before the clean Git repository |
CHANGELOG.md |
Notable changes per release |
LICENSE |
MIT License terms for this project |
docs/README.md |
Documentation index: setup, integration, operations, history and internal notes |
docs/setup/LAN-SETUP.md |
Local network access for an external automation agent |
docs/setup/QUICKSTART-VMIX-OBS.txt |
Short setup checklist for vMix and OBS |
docs/integration/AGENT-API.md |
RIK Agent API endpoints, payloads and behaviour |
docs/integration/VMIX-SETUP.md |
vMix setup steps |
docs/integration/OBS-SETUP.md |
OBS Studio setup steps |
docs/integration/DUAL-OUTPUT.md |
Running vMix and OBS from one graphics engine |
docs/operations/DATABASE.md |
SQLite schema, snapshots, analysis cache and backup |
docs/operations/MAP-ATTRIBUTION.md |
Attribution for the statistical region map asset |
docs/history/RELEASE-NOTES-v1.6.0.md |
Release notes of the current formal release |
docs/history/STATUS-CHECKLIST.md |
v1.2.0 feature status matrix, preserved as history |
docs/history/TECHNICAL-HANDOFF-v1.2.0.md |
Historical technical handoff for the v1.2.0 SQLite stage, preserved from the v1.6.0 archive. Describes that stage, not the current feature set |
config.example.json |
Reference configuration |
Internal and development material, the audits and the repository rules, is indexed in docs/README.md instead of this table.
Project History
The project was developed as a fast build towards the 2026 parliamentary election, in two linked stages.
The first stage was a direct vMix bridge. Early archives drove a vMix title and then a browser overlay from the December 2023 results as test data: a local collector, an auto bridge for Data Source mapping, and finally a standalone browser overlay, which is the architecture that survived.
The second stage turned that bridge into a control center. From v0.7.0 the package became a single local service with one browser input and many graphics, followed by stability fixes, the first full election-night suite, a separate operator interface, dual modes for test data and the live round, SQLite snapshot history, the local agent API, dual vMix and OBS output, LAN access for the external automation agent, and finally the v1.6.0 hardening pass.
All of those development archives were kept outside Git because they contained runtime data, databases, caches and local configuration. The clean repository starts with a single import of v1.6.0, and that is the current formal release:
- Version:
v1.6.0 - Tag:
v1.6.0 - Release commit:
e2f118fb5e1aa05b26adbbdf79538613b63d4a53
One historical document is preserved in the repository: docs/history/TECHNICAL-HANDOFF-v1.2.0.md, the technical handoff written for the v1.2.0 SQLite stage and kept unchanged from the v1.6.0 archive. It documents that stage only, and the agent API and LAN access that arrived in v1.3.0 and v1.5.0 are not part of it.
The full timeline, including the archives that predate the Control Center, is in HISTORY.md. The release-level change list is in CHANGELOG.md.
Repository Layout
app.py local service: collector, control center, RIK Agent API
rik_db.py SQLite snapshot and analysis storage
start.bat, start-*.bat mode selection and start scripts
config.example.json reference configuration
web/ control center, graphics engine and production monitor
assets/ QR asset, map placeholder and architecture image
data/ runtime database, cached assets and registry seed
docs/ setup, integration, operations, history and internal notes
tests/ self-tests for the database, the agent API, the LAN path and v1.6 fixes
tools/ operator launchers, database backup and LAN firewall setup
HISTORY.md, SECURITY.md, ... top-level project documents
Credits
Built by CYB3RJAN for Serbian parliamentary election-night broadcast graphics. Statistics and result data come from the public results service of the Republic Electoral Commission of the Republic of Serbia.
Disclaimer
Election data in this application depends entirely on the availability and the response format of the public RIK results service. Results published during the night are intermediate: values can change, be corrected or be republished, and the graphics always display the data as it was retrieved at that moment. Rankings in the graphics are computed from the retrieved values at that moment and are not predictions. No political advocacy, prediction or judgement about any political actor is intended or expressed anywhere in this software. Official final results should always be verified with the official election authority.
License
This project is released under the MIT License. You may use, modify and redistribute it under those terms. The full license text is in LICENSE.
Copyright (c) 2026 CyberJan.