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

Dashboard SAKTI

A nightly pipeline and dashboard that replaces OIKN's manual budget downloads for all six of its budget units

Screenshots
budget units synced every night
6
kinds of SAKTI data fetched per run
6
WIB nightly sync, no manual downloads
02:00
from empty repository to working pipeline
2 days
On this page

The problem

Otorita IKN has six budget units, called satker, and each one's budget and spending data lives in SAKTI, the Ministry of Finance's system. Whenever the planning bureau needed figures for a report, an executive review or Power BI, someone logged into SAKTI, downloaded an Excel file for each unit, and stitched them together by hand.

That meant no history and no single view. Nobody could see how spending moved over time. Every other tool that needed the numbers got them by copying and pasting. The data team wanted the figures collected automatically, kept over time, and shown in one place.

What I built

A nightly pipeline that fetches every unit

A scheduled job runs every night at 02:00 WIB. It pulls six kinds of SAKTI data for every active unit, in parallel, and saves them to a database. The list of units comes from the database when the job starts. An administrator adds or switches off a unit in the admin panel, and the next run follows. Nothing about the units is hardcoded. Every run is safe to repeat, because saving the same record twice updates it and never duplicates it.

I simulated SAKTI so work never waited on access

Real SAKTI credentials weren't available yet. SAKTI's login is unusual: every response hands back a new one-time key, and the previous one stops working at once. I built a small stand-in server that behaves the same way, including expired-key responses, and serves sample data for all six data types. The whole pipeline runs against it. Moving to the real system means changing two settings, with no code changes.

One place for the save logic

The scheduled jobs write to the database through the same data models the web app uses. I chose this over putting a web API between them, so the rules for saving a record live in one place. The cost is that the scheduler depends on the web app's code. At six units, that trade is fine.

A dashboard with a sync badge and Excel export

The dashboard has three pages. The overview shows total budget, total spending and the share absorbed, with a trend chart and a gauge. A detail page breaks one unit down by program and activity, with date filters. A management page lists the units. Each nightly run records its status, row count, duration and any error, and the dashboard shows when data was last synced. Any view can be exported to a formatted Excel file.

Result

I built the platform in two days in June 2026, from an empty repository. It works end to end: nightly job, SAKTI client, database, API, dashboard and Excel export. Today it runs on the simulated SAKTI server with sample data. The production setup is ready and tested to start cleanly. Real data waits on the Ministry of Finance providing credentials. The dashboard has no login of its own in this first version, and is meant for OIKN's internal network.

Under the hoodTechnical detail for engineers
  • The Airflow DAG sakti_daily_sync reads the Satker table at runtime and uses .expand() for dynamic task mapping. Tasks: fetch_ref_sts, fetch_ref_admin_uraian, then fetch_data_ang, fetch_spp_header and fetch_capaian_ro per satker, then normalize_and_load and log_sync_result.
  • DAG tasks import Django models directly: the backend source is mounted into the Airflow container with DJANGO_SETTINGS_MODULE set. Upserts use update_or_create.
  • The mock_sakti Django app keeps an in-memory token store with UUID rotation, mirrors SAKTI's single-use Bearer tokens and the resetToken bootstrap endpoint, and serves deterministic JSON fixtures.
  • SaktiClient splits token state (auth.py) from request flow (client.py) and retries with exponential backoff via tenacity.
  • SyncLog stores status, row count, duration and error per task. A notify_failure_placeholder hook is wired into the DAG.
  • Excel export uses openpyxl: bold headers, auto-width columns, currency format, frozen header row.
  • Anggaran rows have no date column, so the chart's granularity toggle divides the total budget evenly. A dated refSts history would replace it.
  • App data and Airflow metadata share one MySQL 8 container, as separate databases with separate users.
  • Five Docker services with multi-stage builds and healthchecks; docker-compose.prod.yml uses restart: unless-stopped.
  • 8 Django apps, 9 REST endpoints including health and sync status, 10 React components, about 5,000 lines of Python and JSX, 10 commits across nine phases.

Screenshots

Login Page
Overview
Detail Satker
Kelola Satker
Log Aktivitas

Hiring, or building something similar?

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