DemografiEngine (Terminal) — Full Current Functional Flow

Dokumen ini memetakan perilaku current DemografiEngine Terminal dari semua intake data sampai persistensi, pencarian, pemakaian rule, dan scoring. Ini adalah baseline source saat ini, bukan desain target. Terminal memiliki subtype ATM, POS, dan ONLINE, tetapi penentuan subtype saat deserialisasi dan saat penyimpanan mempunyai ketidaksesuaian yang harus diuji runtime.

Status Verifikasi

Area Status Batas verifikasi
REST, scheduler XML, command, search, cache SOURCE-TRACED Ditelusuri dari controller, service, entity, repository.
Trigger dan algoritme score terminal SOURCE-TRACED Ditelusuri dari RunAction, BaseAlertServiceImpl, ScoringService, evaluator.
Schema/seed/config SOURCE-TRACED Ditelusuri dari Liquibase dan application profile.
Polymorphic JSON/XML dan JOINED persistence subtype NOT RUNTIME-VERIFIED Tidak menjalankan deserialisasi ataupun insert subtype.
Path/cron/data production NOT PRODUCTION-VERIFIED Profile dan filesystem produksi tidak diperiksa.

Peta Komponen Utama

Concern Komponen current Tanggung jawab
API TerminalController POST /terminal-data/save dan /search.
File intake TerminalServiceImpl.saveTerminalPath Cron memproses XML input directory.
Type resolution TerminalTypeMapper dan @JsonTypeInfo di Terminal Menerjemahkan 1/ATM, 2/POS, 3/ONLINE; lainnya GENERAL.
Persist TerminalRepository dan tiga subtype repository Menulis data_terminal serta table subtype JOINED.
Rule/cache TerminalCache, ResolverTypeTerminal Membaca data_terminal dengan cache 30 menit.
Scoring ScoringService, ScoringEvaluatorService Menulis score dan scoringLevel.

Ringkasan End-to-End

[Diagram]

Flow 1 — Intake: REST dan Scheduler XML

Ada dua cara aktif untuk memasukkan data terminal. REST menerima TerminalApps; scheduler @Scheduled(cron = "${demographic.terminal.cron.save}") membaca file .xml lalu memakai XmlMapper ke DTO yang sama.

[Diagram]
Jalur Input Outcome current
REST JSON list application String aggregate Processed/Created/Updated/Ignored/Failed; controller memberi HTTP 200 bila per-record error tertangkap.
Scheduler XML list application File corrupt dipindahkan ke error; error record ditulis sebagai file isolasi; source batch dipindah output bahkan bila ada record gagal.

[!DANGER] saveTerminalData mengiterasi data.getApplication() tanpa null check. application=null menghasilkan exception controller/HTTP 500.

[!DANGER] Variabel terminalId di jalur REST selalu "UNKNOWN" pada catch; log error REST tidak menampilkan id terminal yang sebenarnya.

[!DANGER] Scheduler hanya menerima suffix .xml case-sensitive. File .XML tidak diproses.

Flow 2 — Resolusi Tipe dan Command Persistensi

TerminalTypeMapper.resolve menerima 1/ATM → ATM, 2/POS → POS, 3/ONLINE → ONLINE, dan semua nilai lain/null → GENERAL. Lalu processSingleTerminal melakukan cast subtype berdasarkan hasil ini dan memakai repository subtype. Setiap nilai terminal dibersihkan ServiceHelper.sanitize(newValue, 255).

[Diagram]
Raw terminalType Resolved service type Expected Java class/repository Table hierarchy
1, ATM ATM TerminalATM / TerminalAtmRepository data_terminal + data_terminal_atm
2, POS POS TerminalPos / TerminalPosRepository data_terminal + data_terminal_pos
3, ONLINE ONLINE TerminalOnline / TerminalOnlineRepository data_terminal + data_terminal_online
null/other GENERAL Terminal / TerminalRepository data_terminal
Command Data ditemukan pada repository tipe yang dipilih Current write Cache
CREX Tidak Insert Evict.
CREX Ya Tidak ada Tidak berubah.
CRUP Tidak Insert Evict.
CRUP Ya Full replacement payload memakai id lama Evict.
IGNR Apa pun Tidak ada Tidak ada.

[!DANGER] Terminal.terminalType bertipe Integer, tetapi @JsonSubTypes juga mendeklarasikan discriminator bernama ATM, POS, ONLINE. JSON/XML yang memakai string nama subtype dapat gagal memetakan ke Integer; ini NOT RUNTIME-VERIFIED.

[!DANGER] Bila mapper memilih POS/ONLINE/ATM tetapi object hasil deserialisasi masih Terminal (bukan subtype), cast eksplisit (TerminalPos|TerminalOnline|TerminalATM) di processSingleTerminal melempar ClassCastException. Error menjadi counter failure/isolation file, bukan fallback GENERAL.

[!DANGER] Field terminal_type diberi @Column(insertable=false, updatable=false) walaupun juga discriminator JPA. TerminalTypeMapper.toDbValue hanya dipakai untuk filter search, bukan ketika persist. Source tidak secara eksplisit mengisi discriminator dari mapper; keberhasilan subtype insert bergantung Hibernate polymorphic persistence.

[!DANGER] terminal_id hanya memiliki kolom biasa di Liquibase, bukan unique constraint. Lookup per subtype memungkinkan id sama berada di hierarchy/subtype berbeda dan scoring/cache selalu memakai TerminalRepository general.

Flow 3 — Search dan Consumer Rule

Search memilih class table berdasarkan request terminalType; setelah key ini dihapus dari filter body, predicate tidak membatasi type pada table terpilih. GENERAL adalah default saat parameter tak ada atau tidak dikenal.

[Diagram]

[!DANGER] TerminalCache hanya memakai TerminalRepository general. Untuk entity subclass yang disimpan lewat JPA JOINED, apakah repository mengembalikan polymorphic subtype dapat bergantung runtime ORM; untuk terminal id duplicate lintas subtype hasilnya ambigu.

[!DANGER] order menjadi root.get(order) dan key filter default menjadi root.get(key) tanpa allowlist. Field salah menghasilkan exception/HTTP 500.

[!DANGER] installDate merupakan VARCHAR(255) dan filter date mengubah ISO Instant ke string yyMMddHHmmss memakai timezone sistem; source membandingkan string, bukan timestamp.

Flow 4 — Scoring Terminal

Input terminal tidak otomatis memanggil score engine. Ada tiga trigger score: action CREATE_SCORE, action UPDATE_SCORE, dan BaseAlertServiceImpl.triggerScoring untuk alert dengan alertStatus != AUTO_NEGATIVE serta isDuplicated == false.

[Diagram]
Trigger Formula current Catatan
createScore clamp(initialScore, 0..100) Skip bila existing score > 0.
updateScore clamp(previous + newScore, 0..100) Nama newScore bukan assignment absolut.
incrementScore clamp(previous + getAutoIncrement(TERMINAL, previous), 0..100) Skip bila previous ≥100.
Level First active threshold match ordered value1 DESC; else LOW Seed: LOW <40, MEDIUM 40..70, HIGH ≥70, increment 10.

[!DANGER] ScoringService.requireTerminal selalu memakai general TerminalRepository.findByTerminalId, bukan repository subtype yang dipakai saat insert/update. Jika subtype lookup/polymorphism tidak konsisten, scoring dapat gagal walaupun record dimasukkan lewat subtype repository.

[!DANGER] ScoringService tidak memanggil terminalCache.invalidateCache, sehingga resolver rule dapat melihat score atau scoringLevel stale hingga TTL 30 menit.

[!DANGER] Exception incrementScore pada alert ditangkap BaseAlertServiceImpl.tryScore dan hanya dilog; alert/case tetap dilanjutkan tanpa score terminal.

Matriks Skenario Operasional

Scenario Persist/response Cache/score Next step
REST/scheduler CREX GENERAL baru Insert data_terminal Cache evict; score hanya payload Rule dapat read terminal.
CREX typed subtype baru Expected JOINED save Cache evict Keberhasilan cast/persist runtime-verify.
CRUP typed existing Full payload save old id Cache evict Subtype association/data dapat terganti.
Unknown terminalType GENERAL repository Tidak ada validation warning Data bisa disimpan general.
Mismatch resolved type vs Java subtype Item error REST count failed / scheduler isolation XML Perbaiki source format.
XML corrupt Tidak ada DB write Source XML moved error Batch lanjut file lain.
Eligible alert terminal ada Increment 10/default threshold Cache tetap stale Save score/level.
Eligible alert terminal tidak ada Warning only Alert proceeds Tidak ada score.
UPDATE_SCORE +10 Adds 10, not set 10 Cache stale Recompute level.

State Ownership Current

State Authoritative source Projection/consumer
Base profile & score data_terminal General repository, cache, scoring.
ATM/POS/ONLINE fields data_terminal_atm / _pos / _online Subtype repositories/search classes.
Identity terminalId (tanpa unique constraint) Service/repositories/cache/score.
Type JPA discriminator terminal_type; service raw mapper Deserialization, repository selection, search class selection.
Cache CacheSupport key terminal:<id>, TTL 30 min ResolverTypeTerminal.
Threshold Active T_SCORING_THRESHOLD_SETTING rows for TERMINAL Evaluator.
Batch file state input/output/error configured paths Scheduler; no DB audit.

Known Current Gaps

Gap current Dampak
String subtype annotation vs Integer field Valid ATM/POS/ONLINE payload bisa gagal deserialisasi/cast.
Mapper does not set DB discriminator Service route and persisted type can diverge.
Non-unique terminal id Repository/cache/scoring result ambiguous.
Score uses base repository only Subtype persistence can be invisible/inconsistent to score lookup.
No score during intake New terminal tidak otomatis dinormalisasi score/level.
Score update does not invalidate cache Rule dapat memakai score stale.
Dynamic search order/filter Invalid client key produces HTTP 500.
Per-record REST failure returns 200 Caller tidak menerima structured item errors.

Endpoint Surface

Surface Endpoint family Capability
Terminal intake POST /terminal-data/save Batch command CREX/CRUP/IGNR.
Terminal read POST /terminal-data/search Search selected type, pagination, sort.
Score threshold /scoring-threshold/* Shared threshold update/read, including TERMINAL.
File intake ${demographic.terminal.cron.save} XML polling; bukan REST endpoint.

Source Trace

Flow/Area Primary source
Controller/service DemografiEngine/Terminal/TerminalController.java; DemografiEngine/Terminal/TerminalServiceImpl.java
DTO/type/domain DemografiEngine/Application/Terminal/TerminalApp.java; DemografiEngine/Application/Terminal/TerminalApps.java; DemografiEngine/Domain/Terminal*.java
Repositories/cache/rule DemografiEngine/Terminal/TerminalRepository.java; DemografiEngine/Terminal/Repository/Terminal*Repository.java; DemografiEngine/Cache/TerminalCache.java; Rule/Engine/CoreNew/Processor/Resolver/Component/NonAggregate/Type/Component/ResolverTypeTerminal.java
Score DemografiEngine/Scoring/ScoringService.java; DemografiEngine/Scoring/ScoringEvaluatorService.java; AlertManagement/Resource/ActionRequest/RunAction.java; AlertManagement/Abstract/BaseAlertServiceImpl.java
Schema/seed db/changelog/DataDemographic/20260409190000_alfin-wildan_create_data_terminal*.xml; db/changelog/DataDemographic/Seed/20260428000007_alfin-wildan_seed_scoring_threshold_setting.xml; db/changelog/DataDemographic/Seed/20260409000000_alfin-wildan_seed_terminal_demographic.xml

Catatan Verifikasi Lanjutan