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

- 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_syncreads theSatkertable at runtime and uses.expand()for dynamic task mapping. Tasks:fetch_ref_sts,fetch_ref_admin_uraian, thenfetch_data_ang,fetch_spp_headerandfetch_capaian_roper satker, thennormalize_and_loadandlog_sync_result. - DAG tasks import Django models directly: the backend source is mounted into the Airflow container with
DJANGO_SETTINGS_MODULEset. Upserts useupdate_or_create. - The
mock_saktiDjango app keeps an in-memory token store with UUID rotation, mirrors SAKTI's single-use Bearer tokens and theresetTokenbootstrap endpoint, and serves deterministic JSON fixtures. SaktiClientsplits token state (auth.py) from request flow (client.py) and retries with exponential backoff viatenacity.SyncLogstores status, row count, duration and error per task. Anotify_failure_placeholderhook is wired into the DAG.- Excel export uses
openpyxl: bold headers, auto-width columns, currency format, frozen header row. Anggaranrows have no date column, so the chart's granularity toggle divides the total budget evenly. A datedrefStshistory 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.ymlusesrestart: 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
Hiring, or building something similar?
I'm open to full-time roles and select freelance work.