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.
| 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. |
| 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. |
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.
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.
| 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]
saveMerchantDatamelakukanfor (MerchantApp merchantApp : data.getApplication())tanpa null check untuk list. Payload denganapplication=nullmenghasilkan exception di level controller dan HTTP 500, bukan ringkasan gagal.
[!DANGER] Dalam jalur REST, variabel
merchantNumberselalu bernilai"UNKNOWN"dan tidak diisi sebelum catch. Log error tidak mengidentifikasi merchant yang benar.
[!DANGER]
moveFileToErrormembentuk target denganSystem.getProperty("user.dir") + errorPath, sedangkan isolation file memakainew File(errorPath). JikaerrorPathsudah absolut, dua mekanisme memakai lokasi berbeda/berpotensi invalid.
[!DANGER] Scheduler menghitung item sebagai
successCount++setelahprocessSingleMerchantselesai, termasuk commandIGNR; counter ini bukan jumlah write database.
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.
| 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 | merchantNumber → data_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]
processSingleMerchanttidak mengecekmerchantNumberblank/null sebelum repository lookup. Kualitas/hasil query bergantung implementasi JPA/database.
[!DANGER]
CRUPmenggantikan 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/CRUPketika data belum ada tidak mengevict cache. Jika key tersebut pernah dicache sebagainullatau ada race dengan load cache, konsumen dapat melihat state stale sampai TTL 30 menit; behavior cache-null spesifikCacheSupportNOT RUNTIME-VERIFIED.
[!DANGER]
MerchantServiceImpltidak diberi@Transactional. Kegagalan setelah cascade persistence/di tengah batch tidak memiliki boundary transaksi per batch yang terlihat di class ini.
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.
[!DANGER]
searchmeneruskanordermenjadiroot.get(order)dan key filter default menjadiroot.get(key)tanpa allowlist. Nama field tidak valid menyebabkan exception lalu controller mengembalikan HTTP 500.
[!DANGER] Filter
dateFrom/dateToharus dapat di-cast langsung keStringdan diparseInstant; sementararegistrationDatedisimpan sebagai stringyyMMddHHmmssdengan timezone sistem. Format/zone yang tidak sesuai menghasilkan error atau perbandingan string, bukan tipe waktu database.
[!INFO]
fetchMerchantAttributetidak memakaiMerchantCache; jalur ini langsung query repository. HanyaResolverTypeMerchantmemakai cache.
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.
| 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_SCOREmemakai namanewScore, tetapi implementasi menambahkannya ke score lama, bukan meng-set nilai absolut.
[!DANGER]
createScoretidak mencegah score 0/negatif existing dari ditimpa. Hanyascore > 0yang membuat skip.
[!DANGER] Alert auto-score hanya dipanggil bila
alertStatus != AUTO_NEGATIVE && !isDuplicated. Kegagalan merchant tidak ditemukan ditangkap olehtryScore, hanya dilog warning, dan tidak membatalkan alert/case.
[!DANGER]
ScoringServicemenyimpan score langsung ke repository, tetapi tidak meng-invalidasiMerchantCache. Rule consumer berbasis cache dapat membacascore/scoringLevellama sampai TTL 30 menit.
[!DANGER] Threshold yang overlap dapat memberi hasil tak intuitif karena evaluator memilih row pertama berdasarkan
value1 DESC, bukan berdasarkanScoringLevel.ScoringThresholdSettingServiceImpl.updatetidak memvalidasi overlap/rangevalue1 <= value2.
| 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 | 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. |
| 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. |
| 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. |
| 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 |
CREX pada merchantNumber sama karena schema tidak menyatakan unique constraint.createScore, updateScore, dan incrementScore untuk mengukur stale-read rule.