Skip to content
AFM Studio
  • Pages

    AFM Studio
  • Work
  • Lab
  • About
  • CV
  • Contact
  • Projects

    AksaraA searchable library for Otorita IKN's knowledge, where the server checks every reader's access across three levels
  • Daily TaqwaTamper-resistant prayer attendance system for ~2,000 OIKN government employees at Masjid Negara IKN
  • Lantara v2A permit engine for Otorita IKN where administrators add new permit types from a screen, without a developer
  • eKiosk IKNTwo control rooms for Nusantara: kiosk content management and live bus tracking with arrival times that grade themselves
  • E-Monev IKNA monitoring platform for Nusantara's 23-year build, tracking every unit's quarterly progress against 24 national KPIs
  • JDIH OIKNOIKN's public legal document portal, with a controlled publishing workflow and AI summaries that never leave the server
  • Jejak PenanamanA check-in app for IKN tree-planting events that proves each participant was on site with a geofenced selfie
  • Dashboard SAKTIA nightly pipeline and dashboard that replaces OIKN's manual budget downloads for all six of its budget units
  • DoseRxA bedside dose calculator for 127 drugs that rounds each dose to tablets and syrups Indonesian pharmacies sell
  • CubiqA speedcubing trainer with competition-grade timing and a solver I wrote for each of the 8 major puzzles
  • ScimotionAn interactive science library of 86 articles, each explained next to animations the reader can control
  • WA Daily Scrum BotA WhatsApp bot that chases missing standup reports for a 14-person team and flags tasks stuck for days

↑ ↓ to move · Enter to open · Esc to close

eKiosk IKN

Two control rooms for Nusantara: kiosk content management and live bus tracking with arrival times that grade themselves

Screenshots
control rooms: kiosk content and bus fleet
2
between live bus position readings
~5 s
arrival accuracy bands, by hour and stop
1/2/3 min
pages across the two apps
24
On this page

The problem

IKN's smart city program puts information kiosks in public places across Nusantara. Each one runs an app that shows government content: maps, announcements and transport schedules. With no central control, changing what a kiosk shows meant touching that device. That doesn't scale as more kiosks go up. Operators also couldn't tell whether a kiosk was running, had crashed, or was showing old content.

IKN also runs a bus rapid transit fleet. An earlier in-house service tracked bus positions in memory only. It kept no history, and it couldn't tell riders or operators when the next bus would arrive. Two teams needed tools: content staff managing kiosk playlists, and transport staff watching the fleet.

What I built

Kiosks report in, and the server works out their state

Every kiosk checks in every five minutes with what it's playing, where it is, and how much storage and memory it has left. From that check-in, the server works out whether the kiosk is operational, stale, disconnected or in maintenance. Each playlist carries a fingerprint that changes when its content changes. A kiosk reports the fingerprint it last loaded, so the server knows at once whether that device is behind. Operators build playlists by dragging media into order, and changes reach the kiosks on their own.

Arrival times learned from the buses' own history

A background service reads every bus's GPS position about every five seconds and places it on its route. For arrival times, I didn't assume a fixed speed. Each night, a job studies past trips and learns how long each stretch between stops usually takes, by hour and by weekday or weekend. The estimate starts from where the bus is on the road, then adds those learned times stop by stop. Where history is thin, it falls back to the timetable, then distance, then stop count, so it always gives an answer.

The system grades its own predictions

I wanted to know whether the arrival times were any good, so the system checks itself. It samples its own predictions, then records the error the moment the bus reaches that stop. A report shows the average error and the share of arrivals within one, two or three minutes, broken down by hour and by stop. Staff can see exactly where the estimates are weakest.

Buses that change route are caught without flip-flopping

Buses sometimes get reassigned or leave their scheduled route. A separate check compares each bus's recent path with every route and suggests a correction when one route is clearly the best match. My first version switched a bus after a few readings near another route. At stops shared by overlapping routes, buses flipped back and forth. Now a switch only happens after the bus visits a stop unique to the new route, or at least 80% of that route's stops. Every correction is logged.

Two apps, one sign-in

What began as one dashboard with a fleet tab is now two separate apps, eKiosk CMS and Fleet Management, with a shared launcher and a switcher between them. Operators sign in once through IKN's single sign-on, and Fleet Management is limited to staff. Kiosks don't use that sign-in at all. They register with an ID assigned at first boot, since a kiosk has no person to log in as.

Result

Both apps run in production for IKN, across 24 pages. Content staff manage kiosk playlists and see device health, with trend charts and an email alert when a kiosk goes silent. Transport staff get a live bus map, a route and stop editor, fleet schedules, a journey planner with points of interest, and reports on headways, travel times and arrival accuracy. The daily fleet report used to time out under load, so I rewrote it as a single database query. The work ran from March to July 2026. The system tracks vehicles only; it has no passenger or fare data.

Under the hoodTechnical detail for engineers
  • Kiosk status (OPERATIONAL / STALE / DISCONNECTED / MAINTENANCE) is a model @property derived from last_heartbeat, heartbeat_interval, operational_status and the playlist hash, so it can't be filtered in the ORM. The summary endpoint filters in Python.
  • Each playlist stores a deterministic SHA-256 hash of its ordered items, recomputed on save. Sync status is an O(1) comparison.
  • Kiosk endpoints are AllowAny; kiosks identify by a UUID assigned at first boot. Dashboard users sign in through Keycloak via mozilla-django-oidc, with a JWT held in Zustand and NIP, jabatan and direktorat synced into UserProfile on each login.
  • poll_gps_data runs as its own transport-poller Docker service. Movement is bulk-inserted into VehicleTrack about every 60 seconds, only past a minimum distance.
  • RouteGraph is a hand-rolled directed graph with a terminal sentinel that closes the loop. It avoids a 30 MB networkx dependency.
  • compute_segment_stats runs nightly from host cron. It splits GPS history into trips, trims outliers above 3× the median, and stores median, p25 and p75 per (route, from-stop, to-stop, hour, weekday/weekend) bucket once at least three samples exist.
  • ETA projects the GPS fix onto the current polyline segment, then walks forward through SegmentTravelStat buckets. Fallbacks: timetable deltas, haversine distance over an assumed speed, stop count.
  • EtaPredictionLog samples at most once per vehicle and upcoming stop per 60 seconds, closed on geofence entry with signed error in seconds.
  • route_detector scores trails by perpendicular distance to the nearest segment, with a confidence margin over the runner-up and a spatial confirmation gate. Reverting needs several confirming ticks. Changes go to RouteCorrectionLog.
  • Fleet analytics is one PostgreSQL query using LAG(), inline haversine and FILTER clauses, which replaced a Python loop that timed out gunicorn workers.
  • No pandas, numpy, geopy, shapely, networkx, Celery or Redis. Statistics use the stdlib statistics module.
  • Frontend: React 19 and Vite, TanStack Query polling (vehicles every 5 s, performance panels every 30 s, no websockets), React Leaflet, @dnd-kit, Recharts, and a hand-rolled inline-style design system with no component library.
  • 4 Django apps, about 30 actions on TransportViewSet, about 7,500 lines of Python and 17,200 of JSX, 113 backend and 133 frontend commits.

Screenshots

eKiosk Dashboard
eKiosk List
eKiosk Detail
eKiosk Media
Fleet Management Dashboard
Fleet Management Analytics

Hiring, or building something similar?

I'm open to full-time roles and select freelance work.