DemografiEngine (Card) — Full Current Functional Flow

Dokumen ini memetakan perilaku current DemografiEngine Card dari intake sampai persistensi, pencarian, cache/rule consumer, dan scoring. Ini baseline source saat ini, bukan desain target. Menyimpan atau mengubah Card tidak otomatis menjalankan scoring; score hanya berubah dari action/alert yang terpisah.

Status Verifikasi

Area Status Batas verifikasi
REST, XML scheduler, command, persistensi, search/cache SOURCE-TRACED Controller/service/entity/repository ditelusuri.
Trigger dan algoritme scoring SOURCE-TRACED RunAction, BaseAlertServiceImpl, score/evaluator ditelusuri.
Schema, relasi dan threshold seed SOURCE-TRACED Liquibase ditelusuri.
Parsing XML, cascade nested entity, filesystem NOT RUNTIME-VERIFIED Aplikasi tidak dijalankan.
Profile/path/database production NOT PRODUCTION-VERIFIED Tidak memeriksa state produksi.

Peta Komponen Utama

Concern Komponen current Tanggung jawab
API CardController Batch intake dan search Card.
File intake CardServiceImpl.saveCardPath Memindai XML berdasarkan cron.
Command CardServiceImpl.processSingleCard CREX/CRUP/IGNR, sanitasi dan save.
Persistensi Card, CardRepository, data_card Profile kartu, relasi, score/level.
Rule consumer CardCache, ResolverTypeCard Mengambil field Card berdasarkan transaction hpan.
Score ScoringService, ScoringEvaluatorService Create/update/increment score dan level CARD.

Ringkasan End-to-End

[Diagram]

Flow 1 — Intake: Dua Cara

1.1 REST

POST /card-data/save menerima CardApps.application[]. Exception per record ditangkap dalam saveCardData, menaikkan counter error, dan method mengembalikan summary string. Controller lalu memberi HTTP 200 untuk summary tersebut.

1.2 Scheduler XML

@Scheduled(cron = "${demographic.card.cron.save}") memastikan directory, memindai hanya .xml, membaca CardApps memakai XmlMapper, mengisolasi error per record, dan memindahkan file asal ke output setelah seluruh item dicoba.

[Diagram]
Cara input Input Hasil per-record Hasil batch
REST JSON CardApps Counter created/updated/ignored/failed Response summary string.
Scheduler XML CardApps Failure ditulis sebagai XML satu record Parent corrupt → error; parent valid → output walaupun berisi error item.

[!DANGER] saveCardData mengakses data.getApplication() tanpa null check. List null menghasilkan HTTP 500 lewat catch controller.

[!DANGER] cardNumber di jalur REST tidak pernah diisi sebelum catch, sehingga log error selalu menyebut UNKNOWN.

[!DANGER] Scheduler menandai successCount ketika command IGNR selesai. Angka success bukan jumlah Card yang disimpan.

Flow 2 — Command, Relasi, Persistensi dan Cache

processSingleCard lookup berdasarkan cardNumber, membersihkan object hingga panjang 255, lalu menjalankan command. Card mempunyai ManyToOne(cascade = ALL) untuk Customer, CardHolder, Account, dan Limit; CRUP menggunakan keseluruhan payload sebagai pengganti entity lama setelah menyalin id.

[Diagram]
Command Existing card Persist current Cache
CREX Tidak ada Insert Card dan nested cascade Tidak di-evict.
CREX Ada Tidak ada Tidak berubah.
CRUP Tidak ada Insert Card dan nested cascade Tidak di-evict.
CRUP Ada Save seluruh payload dengan id lama Evict card:<cardNumber>.
IGNR Apa pun Tidak ada Tidak ada.
Field/state Current storage
Identity cardNumberdata_card.card_number; Liquibase hanya membuat index, bukan unique constraint.
Profile cardMask, issue/expiration date, sequence, status, PIN update, previous card, product.
Nested data customer, cardHolder, account, limit; semua CascadeType.ALL.
Score score Integer dan scoringLevel enum string. Payload CREX/CRUP dapat menulisnya langsung.

[!DANGER] cardNumber null/blank tidak divalidasi sebelum findByCardNumber; hasilnya bergantung JPA/database.

[!DANGER] Tidak ada unique constraint card_number. Data duplikat atau race dapat membuat findByCardNumber dan score memilih hasil ambigu/error.

[!DANGER] CRUP adalah full replacement, bukan patch. Karena nested relation memakai CascadeType.ALL, payload dapat menulis/update association Customer/CardHolder/Account/Limit yang dibawa, tanpa merge field-level.

[!DANGER] Cache hanya di-invalidasi untuk CRUP existing. CREX dan CRUP-create tidak menghapus kemungkinan cache miss/null yang sudah tersimpan; perilaku caching null perlu runtime verification.

Flow 3 — Search dan Rule Consumer

POST /card-data/search memakai Criteria API, dengan like case-insensitive hanya untuk cardNumber, cardMask, sequentialNumber, cardStatus; key lain menjadi equality pada field entity. Rule resolver mengambil hpan dari transaction prepared data dan menggunakannya sebagai cardNumber cache key.

[Diagram]

[!DANGER] order dan fallback key filter dipakai langsung sebagai root.get(...), tanpa allowlist. Field salah menyebabkan exception dan HTTP 500.

[!DANGER] Date range card memiliki nama parameter asimetris: dateFromCardIss berpasangan hanya dengan dateToCardIssDate; expiration memakai dateFromExpiration dan dateToCardExpiration. Jika caller memakai nama intuitif lain, date predicate tidak dibentuk atau default fallback mencoba field yang tidak ada.

[!DANGER] Tanggal disimpan sebagai string dan input ISO Instant diubah ke yyMMddHHmmss pada timezone sistem sebelum dibandingkan; bukan comparison timestamp database.

Flow 4 — Scoring Card

Score hanya dipanggil dari action CREATE_SCORE/UPDATE_SCORE untuk entity type CARD atau BaseAlertServiceImpl.triggerScoring, yang memanggil increment untuk hpan bila alert bukan AUTO_NEGATIVE dan tidak duplicated. ScoringService memakai transaction REQUIRES_NEW pada tiap score method.

[Diagram]
Trigger Formula Current special case
createScore clamp(initialScore, 0..100) Skip hanya bila score existing >0.
updateScore clamp(previous + newScore, 0..100) newScore adalah delta, bukan set nilai absolut.
incrementScore clamp(previous + autoIncrement, 0..100) Skip bila score ≥100.
Level Threshold active pertama yang match, order value1 DESC; fallback LOW Seed CARD: LOW <40, MEDIUM 40..70, HIGH ≥70; increment 10.

[!DANGER] ScoringService menyimpan Card score tetapi tidak memanggil CardCache.invalidateCache; rule yang menggunakan cache dapat melihat score/scoringLevel lama sampai 30 menit.

[!DANGER] Pada auto increment, BaseAlertServiceImpl.tryScore menelan semua exception dan hanya log warning. Jika Card tidak ditemukan, alert tetap selesai tanpa score.

[!DANGER] Evaluator memilih threshold pertama dari urutan value1 DESC; update threshold tidak mencegah overlap atau range terbalik, sehingga level/increment dapat berubah karena urutan bukan maksud bisnis.

Matriks Skenario Operasional

Scenario Persist/result Cache/score Next step
REST CREX baru Insert card/cascade Cache tidak evict; no auto score Summary 200.
REST CREX existing No write Unchanged Ignored.
CRUP existing Full payload replacement Cache evict Rule reloads next access.
Invalid command Per item failure No write expected REST still may return 200 summary.
Corrupt scheduler XML No item write Parent to error Continue next file.
One scheduler item failed Isolated XML Parent to output Manual retry isolation record.
CREATE_SCORE valid card Save initial normalized score/level Cache stale Rule might see old score.
UPDATE_SCORE +10 Adds 10 Cache stale Re-evaluate level.
Eligible alert has unknown hpan Warning only No score Alert/case continues.

State Ownership Current

State Authoritative source Projection/consumer
Card profile and association FKs data_card REST/search/cache/rule.
Card identity cardNumber indexed but non-unique Repository/cache/scoring.
Nested entities Customer, CardHolder, Account, Limit tables Cascade writes from Card.
Cached projection CacheSupport, card:<cardNumber>, TTL 30 min ResolverTypeCard.
Score/level data_card.score, data_card.scoring_level Score service; potential rule attributes.
Threshold active CARD rows in T_SCORING_THRESHOLD_SETTING Evaluator.
Batch lifecycle configured card input/output/error paths Scheduler only.

Known Current Gaps

Gap current Dampak
Intake never invokes scoring Card baru tidak otomatis punya level normalized.
Non-unique card number Lookup/score ambiguous under duplicates.
CRUP full cascade replacement Partial payload can mutate nested profile state.
Cache not evicted after insert/score Rule reads stale data.
REST 200 despite record failures Caller parses summary instead of structured errors.
Dynamic search keys/order Typo becomes 500.
String date filtering Timezone and lexical date issues.
Threshold overlap unvalidated Non-obvious level/increment selection.

Endpoint Surface

Surface Endpoint family Capability
Card intake POST /card-data/save Batch commands CREX/CRUP/IGNR.
Card query POST /card-data/search Search/pagination/order.
Scoring threshold /scoring-threshold/* Shared CARD threshold read/update.
File intake ${demographic.card.cron.save} Scheduled XML processing.

Source Trace

Flow/Area Primary source
API and intake/search DemografiEngine/Card/CardController.java; DemografiEngine/Card/CardServiceImpl.java
Model/repository/cache DemografiEngine/Domain/Card.java; DemografiEngine/Application/Card/CardApp.java; DemografiEngine/Application/Card/CardApps.java; DemografiEngine/Card/CardRepository.java; DemografiEngine/Cache/CardCache.java
Rule consumer Rule/Engine/CoreNew/Processor/Resolver/Component/NonAggregate/Type/Component/ResolverTypeCard.java
Scoring triggers/engine AlertManagement/Resource/ActionRequest/RunAction.java; AlertManagement/Abstract/BaseAlertServiceImpl.java; DemografiEngine/Scoring/ScoringService.java; DemografiEngine/Scoring/ScoringEvaluatorService.java
Schema and settings db/changelog/DataDemographic/20260409190000_alfin-wildan_create_data_card.xml; db/changelog/DataDemographic/Seed/20260401165730_alfin-wildan_seed_card-demographic.xml; db/changelog/DataDemographic/Seed/20260428000007_alfin-wildan_seed_scoring_threshold_setting.xml

Catatan Verifikasi Lanjutan