RIK Election Control Center - live Serbian election results, broadcast graphics, vMix control and local automation interface
  • Python 67.4%
  • HTML 31.4%
  • Batchfile 0.9%
  • Shell 0.2%
  • PowerShell 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-29 09:41:44 +02:00
assets Replace ASCII architecture diagram with visual overview 2026-09-27 10:10:59 +02:00
data Rename Svetlana references to Hermes Agent 2026-09-26 15:17:32 +02:00
docs Polish repository for public access 2026-09-29 09:41:44 +02:00
tests Reorganize project documentation and repository layout 2026-09-27 10:29:34 +02:00
tools Reorganize project documentation and repository layout 2026-09-27 10:29:34 +02:00
web Redesign documentation and sanitize network configuration 2026-09-27 09:51:02 +02:00
.gitattributes Initial clean import of RIK Election Control Center v1.6.0 2026-09-26 15:00:41 +02:00
.gitignore Polish repository for public access 2026-09-29 09:41:44 +02:00
app.py Redesign documentation and sanitize network configuration 2026-09-27 09:51:02 +02:00
CHANGELOG.md Redesign documentation and sanitize network configuration 2026-09-27 09:51:02 +02:00
config.example.json Redesign documentation and sanitize network configuration 2026-09-27 09:51:02 +02:00
HISTORY.md Redesign documentation and sanitize network configuration 2026-09-27 09:51:02 +02:00
LICENSE Add MIT License and document the release terms in the README 2026-09-29 09:26:34 +02:00
README.md Polish repository for public access 2026-09-29 09:41:44 +02:00
requirements.txt Initial clean import of RIK Election Control Center v1.6.0 2026-09-26 15:00:41 +02:00
rik_db.py Initial clean import of RIK Election Control Center v1.6.0 2026-09-26 15:00:41 +02:00
SECURITY.md Polish repository for public access 2026-09-29 09:41:44 +02:00
start-2023-test.bat Redesign documentation and sanitize network configuration 2026-09-27 09:51:02 +02:00
start-2026-live.bat Redesign documentation and sanitize network configuration 2026-09-27 09:51:02 +02:00
start.bat Initial clean import of RIK Election Control Center v1.6.0 2026-09-26 15:00:41 +02:00

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

Python 3 JavaScript SQLite vMix OBS Studio Self-hosted

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 in data/ on first use.

QR tracker asset

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:

  1. Control center page with the scene selector and the current national dashboard.
  2. On-air national dashboard, top-5 bar chart and vote share donut.
  3. Lower third ticker and the waiting-for-RIK state.
  4. Region heatmap and region bars.
  5. Municipality result, polling station result and polling station detail.
  6. Abroad dashboard and embassy carousel.
  7. Actual vMix program output with a graphic on air.
  8. 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 1080 Web Browser input pointing at http://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.html and /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/control with 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 at http://127.0.0.1:8788/production that 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.

RIK Election Control Center architecture

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

  1. Clone the repository onto the production machine.
  2. Copy config.example.json to config.json and review the RIK base URL, the election type and the polling interval.
  3. Set advertise_host to 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.
  4. Start the service with the start script for the mode you need: start-2026-live.bat for the current parliamentary election or start-2023-test.bat for the December 2023 test data.
  5. Open http://127.0.0.1:8788/ to confirm the collector is reading RIK, then open http://127.0.0.1:8788/control.
  6. Add one 1920 x 1080 Web Browser input in vMix pointing at http://127.0.0.1:8788/overlay/vmix.html, or a Browser source in OBS pointing at http://127.0.0.1:8788/overlay/obs.html.
  7. If a second machine will drive the graphics, run tools/setup-lan-firewall.bat on the production machine and use tests/test-hermes-agent-lan.sh to verify access.
  8. 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.txt documents 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:8088 with TCP functions on 127.0.0.1:8099 only 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 1080 and 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 on 127.0.0.1:8099 for 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, sized 1920 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/status for taking over the on-air scene and releasing it again.
  • /api/analysis/start, /api/analysis/stop for deep analysis runs.
  • /api/refresh for 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.md describes the network path and the machine that may drive the API.
  • tests/test-hermes-agent-lan.sh verifies 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.py and 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.bat and tools/setup-lan-firewall.ps1 create an inbound rule for TCP 8788, 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, .env files, keys, the runtime database, logs and caches are all ignored by Git. Only config.example.json is 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.