DemografiEngine (Merchant) — Full Current Functional Flow

Dokumen ini memetakan perilaku current DemografiEngine Merchant dari seluruh intake aktif, penyimpanan, cache/consumer rule, sampai scoring merchant dan efek akhirnya. Ini baseline current, bukan desain target. Tidak ada asumsi bahwa setiap data merchant otomatis mendapat score: jalur ingest merchant dan jalur scoring terpisah di source.

Status Verifikasi

Area Status Batas verifikasi
REST, scheduler XML, persist, query dan cache SOURCE-TRACED Ditelusuri dari controller/service/entity/repository aktif.
Trigger scoring melalui alert dan action SOURCE-TRACED Ditelusuri dari BaseAlertServiceImpl, RunAction, dan ScoringService.
Konfigurasi threshold/seed SOURCE-TRACED Ditelusuri dari Liquibase.
Parsing XML, filesystem dan transaksi DB nyata NOT RUNTIME-VERIFIED Aplikasi dan scheduler tidak dijalankan.
Data/cron/path produksi NOT PRODUCTION-VERIFIED Tidak memeriksa profile aktif dan filesystem produksi.

Peta Komponen Utama

Concern Komponen current Tanggung jawab
REST intake MerchantController Menerima POST /merchant-data/save berupa MerchantApps.
File intake MerchantServiceImpl.saveMerchantPath Scheduler memindai XML dan memproses satu file/batch.
Command processor MerchantServiceImpl.processSingleMerchant Menjalankan CREX, CRUP, atau IGNR.
Persistensi Merchant, MerchantRepository, table data_merchant Menyimpan demografi serta score/scoringLevel.
Rule consumer MerchantCache, ResolverTypeMerchant, RuleConditionProcessor Memberi atribut merchant ke evaluasi rule.
Score engine ScoringService, ScoringEvaluatorService Menulis score dan level berdasar threshold aktif.
Score triggers RunAction, BaseAlertServiceImpl Memanggil create/update score dari action dan auto-increment saat alert eligible.
Threshold administration ScoringThresholdSettingController Membaca/mengubah T_SCORING_THRESHOLD_SETTING.

Ringkasan End-to-End

[Diagram]

Flow 1 — Intake Merchant: Dua Cara Aktif

1.1 POST JSON

POST /merchant-data/save menerima MerchantApps, yaitu list application tanpa wrapper XML khusus pada DTO. Controller mengembalikan HTTP 200 bahkan jika record individual gagal, karena saveMerchantData menangkap exception per item lalu hanya mengembalikan ringkasan teks.

[Diagram]

1.2 Scheduler XML

saveMerchantPath memakai cron ${demographic.merchant.cron.save}. Ia membuat direktori bila belum ada, membaca hanya nama file berakhiran huruf kecil .xml, lalu deserialisasi dengan XmlMapper ke DTO yang sama.

[Diagram]
Cara Input Per-record result Batch/file side effect
REST JSON MerchantApps.application[] Counter agregat pada string response Tidak ada file.
Scheduler XML yang dipetakan ke MerchantApps.application[] Error item ditulis sebagai XML isolasi File valid dipindahkan ke output; file parent corrupt dipindahkan ke error.

[!DANGER] saveMerchantData melakukan for (MerchantApp merchantApp : data.getApplication()) tanpa null check untuk list. Payload dengan application=null menghasilkan exception di level controller dan HTTP 500, bukan ringkasan gagal.

[!DANGER] Dalam jalur REST, variabel merchantNumber selalu bernilai "UNKNOWN" dan tidak diisi sebelum catch. Log error tidak mengidentifikasi merchant yang benar.

[!DANGER] moveFileToError membentuk target dengan System.getProperty("user.dir") + errorPath, sedangkan isolation file memakai new File(errorPath). Jika errorPath sudah absolut, dua mekanisme memakai lokasi berbeda/berpotensi invalid.

[!DANGER] Scheduler menghitung item sebagai successCount++ setelah processSingleMerchant selesai, termasuk command IGNR; counter ini bukan jumlah write database.

Flow 2 — Command, Sanitasi, Persistensi, dan Cache

Setelah mengambil merchantNumber dan applicationCommand, source mencari data existing memakai findByMerchantNumber. Tidak ada unique constraint untuk merchant_number di Liquibase; yang ada hanya index non-unique.

[Diagram]
Command Existing merchantNumber Write Cache Counter
CREX Tidak ada Insert payload merchant Tidak di-invalidasi createdData++
CREX Ada Tidak ada Tidak di-invalidasi ignoredData++
CRUP Tidak ada Insert payload merchant Tidak di-invalidasi createdData++
CRUP Ada Save payload dengan id lama, termasuk associations cascade Evict key merchant updatedData++
IGNR Apa pun Tidak ada Tidak ada ignoredData++
Field merchant Penyimpanan current
Identity lookup merchantNumberdata_merchant.merchant_number; index ada tetapi constraint unique tidak ada.
Profile merchantName, merchantType, merchantCurrency, mcc, registrationDate.
Nested associations contact (ManyToOne), address (OneToOne), merchantCard (ManyToOne), limit (ManyToOne), seluruhnya CascadeType.ALL.
Score score Integer; scoringLevel enum string. Nilai dari input dapat langsung tersimpan pada CREX/CRUP.

[!DANGER] processSingleMerchant tidak mengecek merchantNumber blank/null sebelum repository lookup. Kualitas/hasil query bergantung implementasi JPA/database.

[!DANGER] CRUP menggantikan seluruh entity dari payload (dengan id lama), bukan patch. Association cascade ALL dapat menyimpan/mengubah data nested yang dibawa payload; source tidak melakukan merge field-level.

[!DANGER] Insert CREX/CRUP ketika data belum ada tidak mengevict cache. Jika key tersebut pernah dicache sebagai null atau ada race dengan load cache, konsumen dapat melihat state stale sampai TTL 30 menit; behavior cache-null spesifik CacheSupport NOT RUNTIME-VERIFIED.

[!DANGER] MerchantServiceImpl tidak diberi @Transactional. Kegagalan setelah cascade persistence/di tengah batch tidak memiliki boundary transaksi per batch yang terlihat di class ini.

Flow 3 — Read/Consumer: Search dan Rule Engine

Data merchant dapat dibaca langsung oleh POST /merchant-data/search, serta dipakai oleh rule engine dengan dua cara: RuleConditionProcessor memanggil DB lewat fetchMerchantAttribute; ResolverTypeMerchant memakai MerchantCache TTL 30 menit.

[Diagram]

[!DANGER] search meneruskan order menjadi root.get(order) dan key filter default menjadi root.get(key) tanpa allowlist. Nama field tidak valid menyebabkan exception lalu controller mengembalikan HTTP 500.

[!DANGER] Filter dateFrom/dateTo harus dapat di-cast langsung ke String dan diparse Instant; sementara registrationDate disimpan sebagai string yyMMddHHmmss dengan timezone sistem. Format/zone yang tidak sesuai menghasilkan error atau perbandingan string, bukan tipe waktu database.

[!INFO] fetchMerchantAttribute tidak memakai MerchantCache; jalur ini langsung query repository. Hanya ResolverTypeMerchant memakai cache.

Flow 4 — Scoring Merchant: Tiga Pemicu dan Evaluasi

Ingest CREX/CRUP tidak memanggil ScoringService. Score merchant berubah hanya bila salah satu pemicu berikut berlangsung: action CREATE_SCORE, action UPDATE_SCORE, atau triggerScoring saat alert diproses dan memenuhi condition.

[Diagram]
Jalur scoring Input Rumus current Skip/failure
createScore initialScore, default action = 0 clamp(initialScore) → evaluate Skip bila existing score > 0; throw bila merchant tidak ada.
updateScore newScore, default action = 0 clamp(previousScore + newScore) Skip hanya bila previous ≥100 dan newScore > 0; throw bila tidak ada.
incrementScore merchantId dari transDetails clamp(previousScore + autoIncrement) Skip bila previous ≥100; alert caller menelan exception.
evaluate score 0..100 first active threshold (sort value1 DESC) yang operatornya match; otherwise LOW No active threshold → LOW.

Default seed MERCHANT: LOW score < 40, MEDIUM 40..70, HIGH >= 70, masing-masing autoIncrement=10. Karena sort desc memeriksa HIGH lalu MEDIUM lalu LOW, boundary 70 menghasilkan HIGH dan 40 menghasilkan MEDIUM. Bila batas atas MEDIUM inklusif, rentang MEDIUM dan HIGH beririsan di 70; hasilnya ditentukan oleh urutan sort, bukan oleh rentang.

[!DANGER] Action UPDATE_SCORE memakai nama newScore, tetapi implementasi menambahkannya ke score lama, bukan meng-set nilai absolut.

[!DANGER] createScore tidak mencegah score 0/negatif existing dari ditimpa. Hanya score > 0 yang membuat skip.

[!DANGER] Alert auto-score hanya dipanggil bila alertStatus != AUTO_NEGATIVE && !isDuplicated. Kegagalan merchant tidak ditemukan ditangkap oleh tryScore, hanya dilog warning, dan tidak membatalkan alert/case.

[!DANGER] ScoringService menyimpan score langsung ke repository, tetapi tidak meng-invalidasi MerchantCache. Rule consumer berbasis cache dapat membaca score/scoringLevel lama sampai TTL 30 menit.

[!DANGER] Threshold yang overlap dapat memberi hasil tak intuitif karena evaluator memilih row pertama berdasarkan value1 DESC, bukan berdasarkan ScoringLevel. ScoringThresholdSettingServiceImpl.update tidak memvalidasi overlap/range value1 <= value2.

Matriks Skenario Operasional

Scenario Hasil persist Cache/state Next step
POST CREX merchant baru Insert merchant Score hanya dari payload; cache tidak di-evict Response summary 200.
POST CREX merchant existing Tidak ada write Cache tak berubah Count ignored.
POST CRUP merchant existing Full save memakai id lama Cache merchant di-evict Consumer berikutnya reload DB.
POST command invalid Tidak ada write untuk item Error count naik HTTP tetap 200 bila batch masih berjalan.
Scheduler XML corrupt Tidak ada item diproses File dipindah error via path berbeda Batch lanjut file lain.
Scheduler satu item gagal Item failure diisolasi Original file tetap dipindah output Perbaiki reprocess isolation file manual.
Rule lookup cache miss DB read Cache 30 min Rule memakai field hasil map.
CREATE_SCORE MERCHANT valid score 0 Save score/level Cache tidak di-evict Rule cached data mungkin stale.
UPDATE_SCORE MERCHANT +10 Score lama +10, clamp 100 Cache tidak di-evict Level dievaluasi ulang.
Eligible non-duplicate alert merchantId valid Increment sesuai threshold Cache tidak di-evict Alert/case tetap lanjut.
Eligible alert merchant tidak ada Tidak ada write Warning only Alert/case tetap lanjut.

State Ownership Current

State Authoritative source Projection/consumer
Merchant profile data_merchant / Merchant REST search, fetchMerchantAttribute, rule resolver/cache.
Identity lookup merchantNumber (non-unique index) MerchantRepository.findByMerchantNumber, scoring.
Cache CacheSupport key merchant:<merchantNumber>, TTL 30 min ResolverTypeMerchant.
Score/level data_merchant.score, data_merchant.scoring_level ScoringService, merchant rule attributes.
Threshold T_SCORING_THRESHOLD_SETTING active rows ScoringEvaluatorService.
Input file lifecycle Configured input/output/error paths Scheduler only; no database audit found.

Known Current Gaps

Gap current Dampak
Ingest tidak invoke scoring Merchant baru tidak otomatis memiliki normalized score/level.
merchant_number tidak unique Concurrent/duplicate input dapat membuat ambiguous lookup/scoring.
Full replacement CRUP + cascade ALL Payload partial dapat mengubah/recreate associations.
Cache tidak di-evict on insert/score update Rule dapat membaca profile/score stale.
REST batch returns 200 for individual failures Caller harus parse string summary; tidak menerima per-item failure detail.
Error path inconsistent File corrupt dapat dipindahkan ke lokasi berbeda dari isolated error records.
Dynamic search field/order Input field salah menjadi HTTP 500.
Overlap threshold not validated Level/auto increment dipengaruhi urutan value1 DESC.

Endpoint Surface

Surface Endpoint family Capability
Merchant intake POST /merchant-data/save Simpan batch MerchantApps dengan command CREX/CRUP/IGNR.
Merchant read POST /merchant-data/search Filter/pagination/sort merchant.
Threshold admin POST /scoring-threshold/update/{id} Update threshold termasuk active/operator/increment.
Threshold read GET /scoring-threshold/type/{demographyType}, POST /scoring-threshold/search Membaca threshold.
Scheduled intake ${demographic.merchant.cron.save} Memindai dan memproses file XML; bukan endpoint REST.

Source Trace

Flow/Area Primary source
REST/API merchant DemografiEngine/Merchant/MerchantController.java
Intake XML, commands, search DemografiEngine/Merchant/MerchantServiceImpl.java
DTO/entity/repository DemografiEngine/Application/Merchant/MerchantApp.java; DemografiEngine/Application/Merchant/MerchantApps.java; DemografiEngine/Domain/Merchant.java; DemografiEngine/Merchant/MerchantRepository.java
Cache & rule consumption DemografiEngine/Cache/MerchantCache.java; Rule/Engine/CoreNew/Processor/Resolver/Component/NonAggregate/Type/Component/ResolverTypeMerchant.java; Rule/Engine/CoreNew/Processor/Rule/Component/RuleConditionProcessor.java
Score engine DemografiEngine/Scoring/ScoringService.java; DemografiEngine/Scoring/ScoringEvaluatorService.java
Score trigger AlertManagement/Resource/ActionRequest/RunAction.java; AlertManagement/Abstract/BaseAlertServiceImpl.java
Threshold config DemografiEngine/Scoring/ScoringThresholdSetting*.java
Schema/seed db/changelog/DataDemographic/20260406100017_fadhiilabiyyi_create_data_merchant.xml; db/changelog/DataDemographic/Seed/20260428000007_alfin-wildan_seed_scoring_threshold_setting.xml

Catatan Verifikasi Lanjutan