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.
| 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. |
| 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. |
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.
@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.
| 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]
saveCardDatamengaksesdata.getApplication()tanpa null check. List null menghasilkan HTTP 500 lewat catch controller.
[!DANGER]
cardNumberdi jalur REST tidak pernah diisi sebelum catch, sehingga log error selalu menyebutUNKNOWN.
[!DANGER] Scheduler menandai
successCountketika commandIGNRselesai. Angka success bukan jumlah Card yang disimpan.
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.
| 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 | cardNumber → data_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]
cardNumbernull/blank tidak divalidasi sebelumfindByCardNumber; hasilnya bergantung JPA/database.
[!DANGER] Tidak ada unique constraint
card_number. Data duplikat atau race dapat membuatfindByCardNumberdan 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.
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.
[!DANGER]
orderdan fallback key filter dipakai langsung sebagairoot.get(...), tanpa allowlist. Field salah menyebabkan exception dan HTTP 500.
[!DANGER] Date range card memiliki nama parameter asimetris:
dateFromCardIssberpasangan hanya dengandateToCardIssDate; expiration memakaidateFromExpirationdandateToCardExpiration. 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
yyMMddHHmmsspada timezone sistem sebelum dibandingkan; bukan comparison timestamp database.
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.
| 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]
ScoringServicemenyimpan Card score tetapi tidak memanggilCardCache.invalidateCache; rule yang menggunakan cache dapat melihatscore/scoringLevellama sampai 30 menit.
[!DANGER] Pada auto increment,
BaseAlertServiceImpl.tryScoremenelan 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.
| 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 | 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. |
| 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. |
| 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. |
| 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 |
cardNumber dan score lookup untuk membuktikan behavior repository.