Alert & Case Management — Full Current Functional Flow

Dokumen ini memetakan alur yang benar-benar berjalan pada source saat ini untuk Alert & Case Management, mulai dari alert generation, triage, AUTO_NEGATIVE, deduplication, clustering, case creation, auto-assignment, HOLDING queue, investigator presence, lock, maker-checker, classification, SLA, sampai case closure dan reopen.

Dokumen ini adalah baseline perilaku current, bukan desain target. Rancangan rework yang sudah disepakati—termasuk penghapusan lock, whole-case ownership ketika alert dipilih, dynamic clustering dengan fallback hpan/merchantId, dan action tetap alert-scoped—berada di docs/ALERT_CASE_MANAGEMENT_REWORK_ANALYSIS.md.

Status verifikasi

Area Status Batas verifikasi
Service flow dan branching SOURCE-TRACED Ditelusuri dari implementation aktif
Endpoint dan scheduler SOURCE-TRACED Ditelusuri dari controller dan annotation
Database behavior SOURCE-TRACED Berdasarkan entity, repository, dan Liquibase
Runtime behavior NOT RUNTIME-VERIFIED Tidak menjalankan aplikasi atau integration test
Production behavior NOT PRODUCTION-VERIFIED Tidak memeriksa data atau konfigurasi environment production

[!DANGER] Current vs target

Source current masih memiliki single-alert lock dan case bulk lock. Source current juga memindahkan satu alert ke standalone case ketika alert tersebut dipilih dari multi-alert case. Keduanya bukan perilaku target rework.

Peta Komponen Utama

Concern Komponen current Tanggung jawab
Alert generation BaseAlertServiceImpl Same-UTRN guard, dedup, triage, alert persistence, case assignment, post-save action
Triage ThresholdPolicy, PriorityService, system parameter ALERT_TRIAGE_ACTIVE Menghitung score, priority, dan eligibility AUTO_NEGATIVE
Clustering ClusteringKeyBuilder, T_ALERT_CLUSTERING_SETTING Membentuk dynamic clustering key dari field transaksi
Case aggregate AlertCase, CaseServiceImpl Assignment, reassign, lifecycle, bulk operation, link/unlink, note, export
Distribution AlertDistributionService Memilih group dan investigator untuk case baru
Holding queue HoldingQueueService Retry distribution, notify, force-assign
Presence PresenceService, ReAssignmentService Status ACTIVE/IDLE/AWAY/OFFLINE/BUSY dan respons saat investigator unavailable
Alert action Issuer/Acquirer alert service Lock, unlock, direct classification, fraud flag, alert SLA
Maker-checker Issuer/Acquirer ActionRequest dan BulkActionRequest Submission, approval, decline, discussion, action execution
SLA AlertSlaLifecycleService, SlaCheckerServiceImpl Clock alert dan case, warning, breach, escalation
Queue AlertQueueService, CaseServiceImpl Available, assigned, reassignable, active, historical views
Audit/notification AuditTrailService, NotificationService, SSE Rekam perubahan dan dorong notification ke investigator/group

Ringkasan End-to-End

[Diagram]

Flow 1 — Intake, Same UTRN, Dedup, dan AUTO_NEGATIVE

1.1 Same UTRN guard

Sebelum membuat alert, service mencari alert non-duplicate dengan utrnno yang sama. Jika ditemukan, alert kedua tidak dibuat dan transaksi hanya ditandai sudah pernah menghasilkan alert.

Same-UTRN guard berbeda dari deduplication:

[Diagram]

1.2 Deduplication branch

Jika active dedup configuration menghasilkan key yang sama dengan alert canonical berstatus UNASSIGNED atau IN_REVIEW:

  1. duplicateCount alert canonical dinaikkan;
  2. sistem menyimpan duplicate shadow;
  3. shadow memiliki isDuplicated=true, isClassified=true, alertStatus=UNASSIGNED;
  4. shadow langsung locked=true dan memiliki lockedAt;
  5. shadow tidak masuk case assignment dan post-save orchestration normal.
[Diagram]

Dedup key assembly detail

[Diagram]

Aturan pembentukan key current:

Step Current behavior Contoh
Configuration scope active=true dan (applyBoth=true atau alertType sama) Setting global ikut Issuer dan Acquirer
Field cleanup null dibuang, lalu trim, exact distinct, dan sort ascending merchantId menjadi merchantId
Direct lookup Transaction exact → transaction camelCase → context exact → context camelCase dest_acc_number mencoba destAccNumber
rule alias ruleId/bindingId dari transaction lalu context Rule-triggered dedup
customer alias customerIdhpanmpan Fallback customer identity
Destination alias destAccNumber Tujuan transfer
Channel alias transactionTypetransactionCode Kanal/transaksi
Missing value Tetap menghasilkan field= merchantId=
Final format Komponen digabung menggunakan | merchantId=M1|terminalId=T1

[!DANGER] Empty-value collision

Selama setting tersedia, field yang tidak ditemukan tidak membatalkan dedup key. Ia menjadi field=. Jika seluruh configured values kosong, transaksi berbeda dapat menghasilkan key kosong-nilai yang sama dan masuk dedup branch.

Canonical lookup dan duplicate shadow detail

[Diagram]

Field duplicate shadow:

Field Value current
utrnno, ruleId, versionNo Disalin dari GenerateAlertContext
rawRiskValue, triageScore, priority Hasil triage transaksi duplicate itu sendiri
classificationType Hasil resolveAlertDecision: dapat SUSPICIOUS atau NEGATIVE
alertStatus Selalu UNASSIGNED
isDuplicated true
isAutoNegative Default false dari BaseAlert
isClassified true
classifiedDate Timestamp generation
classifiedBy Tidak diisi pada builder ini
isLocked true
lockedAt Timestamp generation
lockedBy Tidak diisi
caseId Tidak diisi
duplicateCount 0
Assignment/SLA Tidak diinisialisasi

[!DANGER] Canonical selection current

Repository lookup hanya memfilter deduplicationKey dan status UNASSIGNED/IN_REVIEW; tidak ada predicate isDuplicated=false dan tidak ada explicit ordering. Karena duplicate shadow sendiri disimpan sebagai UNASSIGNED, shadow sebelumnya tetap eligible dipilih oleh lookup berikutnya. Pemilihan row juga tidak deterministik ketika lebih dari satu row memenuhi filter.

[!DANGER] Concurrent increment risk

Increment duplicateCount dilakukan dengan read-modify-save tanpa pessimistic lock atau atomic update pada jalur yang ditelusuri. Dua duplicate concurrent dapat membaca count sama dan salah satu increment berpotensi hilang.

1.3 Triage dan AUTO_NEGATIVE

[Diagram]

GenerateAlertContext field assembly

Semua keputusan triage dan dedup dihitung lebih dulu di buildGenerateAlertContext() sebelum same-UTRN guard dan dedup early return dijalankan.

Context field Source dan fallback current
timestamp Waktu aplikasi saat generation dimulai
utrnno transDetails.utrnno, default 0L
rawRiskValue transDetails.riskValue, default 0; missing key menulis warning log
Threshold Active ThresholdPolicy sesuai AlertType; tidak ada row menjadi null
minThreshold String minimum policy diparse integer; default 0
maxThreshold String maximum policy diparse integer; default 0
triageScore Normalisasi rawRiskValue terhadap maxThreshold
priority CRITICAL untuk STOP_LIST; selain itu hasil AlertTriageEvaluatorService
decision Kombinasi alertStatus dan classificationType
ruleName, alertName Untuk binding RULE, keduanya dari data.ruleName; tipe lain string kosong
dedupKey Hasil active dedup settings dan field resolver
ruleId transDetails.ruleId; nilai <=0 menjadi null
versionNo transDetails.ruleVersionNo; nilai <=0 menjadi null
[Diagram]

Threshold parsing dan triage normalization

[Diagram]

Normalization current memakai integer arithmetic:

Priority matching detail

[Diagram]
operatorType Match current
GREATER_THAN score > value1
LESS_THAN score < value1
GREATER_THAN_OR_EQUAL score >= value1
LESS_THAN_OR_EQUAL score <= value1
EQUAL score.equals(value1)
BETWEEN value1 dan value2 wajib non-null; kedua batas inclusive

Setting diurutkan berdasarkan value1 DESC, bukan berdasarkan enum priority. Jika range overlap, setting pertama yang match menjadi hasil. Untuk operator selain BETWEEN, source mengandalkan value1 tidak null.

AUTO_NEGATIVE decision gate

isAutoNegative hanya true ketika seluruh precondition kiri terpenuhi dan salah satu kondisi hasil kanan terpenuhi:

ALERT_TRIAGE_ACTIVE
AND thresholdPolicy exists
AND rawRiskValue != 0
AND (priority == LOW OR triageScore < minThreshold)
[Diagram]
Triage toggle Policy Raw risk Priority/score Decision current
Missing parameter Ada Non-zero LOW atau score di bawah minimum AUTO_NEGATIVE; missing parameter default true
false Apa pun Apa pun Apa pun UNASSIGNED + SUSPICIOUS
true Tidak ada Non-zero LOW UNASSIGNED + SUSPICIOUS
true Ada 0 LOW/score rendah UNASSIGNED + SUSPICIOUS
true Ada Non-zero LOW AUTO_NEGATIVE + NEGATIVE
true Ada Non-zero Bukan LOW, score < minThreshold AUTO_NEGATIVE + NEGATIVE
true Ada Non-zero Bukan LOW, score >= minThreshold UNASSIGNED + SUSPICIOUS

Ketika policy ada tetapi threshold string invalid, object policy tetap dianggap ada. Hasil decision kemudian memakai nilai threshold yang berhasil diparse atau default 0, ditambah priority hasil evaluator.

Persisted alert fields setelah triage

Field Normal managed alert AUTO_NEGATIVE
alertStatus UNASSIGNED AUTO_NEGATIVE
classificationType SUSPICIOUS NEGATIVE
isClassified Default false true
classifiedDate Tidak diisi Timestamp generation
isDuplicated false false
isAutoNegative false true
isLocked false false
caseId Diisi oleh clustering flow Tidak diisi
Alert SLA Dimulai setelah memperoleh assignee Tidak dimulai
Post-save scoring Dijalankan Dilewati
Scenario Current result
ALERT_TRIAGE_ACTIVE=false Tidak ada auto-negative; alert mengikuti flow normal
Triage aktif, threshold tidak ada Tidak auto-negative
Triage aktif, raw risk 0 Tidak auto-negative
Priority LOW Auto-negative jika precondition lain terpenuhi
Score di bawah minThreshold Auto-negative jika precondition lain terpenuhi
STOP_LIST Priority dipaksa CRITICAL, lalu mengikuti evaluasi berikutnya

ALERT_DISTRIBUTION_ACTIVE tidak mengontrol AUTO_NEGATIVE. Toggle tersebut hanya mengontrol auto-distribution case baru.

[!INFO] AUTO_NEGATIVE current

Alert AUTO_NEGATIVE tetap dipersist sebagai historical evidence dengan classification NEGATIVE, tetapi tidak memiliki case, assignment, distribution, scoring post-save, atau alert SLA. Ini berbeda dari sekadar membuang event.

Flow 2 — Clustering dan Case Creation

2.1 Dynamic clustering key current

ClusteringKeyBuilder membaca active clustering settings sesuai AlertType, mengambil field dari transaction details, mengurutkan field, lalu menghasilkan key berbentuk field=value|field=value.

Pada source current belum ada fallback bawaan hpan untuk Issuer atau merchantId untuk Acquirer. Jika configuration/value tidak menghasilkan key, alert dibuatkan standalone case.

[Diagram]

2.2 Case identity dan lifecycle boundary

[Diagram]

Closed cases tidak termasuk CaseStatus.ACTIVE, sehingga lookup tidak mengembalikan closed case. Jika key sama muncul kembali setelah closure, service mencoba membuat active case baru.

2.3 Post-save side effects

Setelah managed alert disimpan, service menjalankan side effects berikut:

[Diagram]

Flow 3 — Auto-Assignment dan HOLDING Queue

Auto-distribution hanya dijalankan untuk case baru yang masih UNASSIGNED ketika ALERT_DISTRIBUTION_ACTIVE=true. Alert yang bergabung ke case existing tidak memicu redistribusi case.

3.1 Pemilihan group dan investigator

[Diagram]

Supported condition operators adalah EQUALS, NOT_EQUALS, GREATER_THAN, LESS_THAN, IN, dan LIKE. Group tanpa condition dianggap match. Pada tie workload, service menggunakan waktu assignment untuk memilih kandidat yang lebih lama tidak menerima assignment.

3.2 Tidak ada investigator: HOLDING

Jika group cocok tetapi investigator tidak tersedia, atau tidak ada group/catch-all yang dapat menerima case:

  1. case menjadi HOLDING;
  2. holdingSince diisi satu kali;
  3. scheduler berjalan setiap 30 detik;
  4. distribution dicoba kembali, dengan preferensi group sebelumnya bila tersedia;
  5. jika tetap HOLDING, policy per priority menentukan notify, force-assign, dan critical notification.
[Diagram]

Fallback configuration jika row database tidak ada:

Priority Notify Force-assign Critical
CRITICAL 1 minute 3 minutes 5 minutes
HIGH 2 minutes 5 minutes 10 minutes
MEDIUM 5 minutes 15 minutes 30 minutes
LOW 10 minutes 30 minutes 60 minutes

Notification HOLDING normal memiliki cooldown 5 menit; critical notification memiliki cooldown 10 menit.

Flow 4 — Presence dan Offline Reassignment

Presence dihitung ulang setiap 30 detik. Manual BUSY memiliki precedence, kemudian focus, SSE/heartbeat, activity, dan akhirnya ACTIVE.

Saat status berubah ke AWAY atau OFFLINE:

[Diagram]

Current behavior sengaja tidak auto-reassign case IN_REVIEW; group hanya diberi notification. Auto-reroute hanya diterapkan pada case ESCALATED dan dapat diblokir oleh alert lock.

Flow 5 — Manual Assign, Pick Alert, Bulk Assign, dan Reassign

5.1 Assign case

Manual case assignment:

  1. memvalidasi target user;
  2. mengambil pessimistic lock pada case;
  3. menolak closed case;
  4. mengisi assignee, assignment date, first response, dan status IN_REVIEW;
  5. me-reset case SLA deadlines;
  6. menyinkronkan assignment dan initial alert SLA ke seluruh member alert;
  7. menulis audit dan notification.
[Diagram]

5.2 Pick/assign satu alert current

[Diagram]

[!DANGER] Current split behavior

Memilih satu alert dari multi-alert case memecah hasil clustering dan membuat standalone case baru. Target rework yang telah disepakati akan mengubah pick alert menjadi whole-case ownership, tanpa split.

5.3 Bulk operation overview

5.4 Reassign case

Reassign menulis previousAssignee, isReassigned, target assignee, dan status IN_REVIEW, kemudian menyinkronkan seluruh alert. Berbeda dari initial/manual assign, source current tidak me-reset SLA deadline pada method reassign.

[Diagram]

Current reassignCase() tidak mengubah assignedDate, firstResponseAt, atau case SLA deadline. Karena synchronization memakai state case, member alert menerima owner baru tetapi tidak memperoleh SLA start baru jika SLA sebelumnya sudah dimulai.

5.5 Bulk assign alerts — detailed flow

Bulk assign dari endpoint Issuer/Acquirer lebih dulu mengelompokkan selected alert berdasarkan caseId.

[Diagram]

[!INFO] Scope bulk alert assignment

Jika hanya satu alert dipilih tetapi alert tersebut sudah berada dalam sebuah case, service memanggil assignCase(). Akibatnya seluruh member alert pada case ikut menerima assignment, walaupun response succeeded hanya berisi selected alert IDs.

5.6 Bulk assign cases dan bulk reassign cases

[Diagram]

Bulk case operations sengaja tidak all-or-nothing. Case yang sukses tetap commit walaupun case lain gagal karena missing case, closed case, invalid user, atau same-assignee validation.

Controller current tetap mengembalikan response HTTP sukses dengan BulkOperationResult; caller harus membaca failed, bukan hanya HTTP status.

5.7 Manual escalation

[Diagram]

Candidate dipilih dari membership group secara role-agnostic dan tanpa filter presence/workload yang dipakai auto-distribution. Pada method current yang ditelusuri, manual escalation tidak memanggil syncAlertAssignment(), sehingga assignee case dapat berubah tanpa langsung memperbarui projection assignee pada member alert.

Flow 6 — Current Lock dan Ownership Guard

Walaupun ownership utama sudah berada di case, source current masih memakai alert lock sebagai syarat editing/action.

Single-alert lock

[Diagram]

Case bulk lock

/case/{id}/bulk-lock mengunci seluruh member Issuer dan Acquirer. Current bulk path mengisi isLocked dan lockedAt, tetapi tidak menyamakan lockedBy seperti single-alert lock. Bulk unlock melepas lock semua member.

[Diagram]

Current bulk lock/unlock tidak memvalidasi bahwa caller adalah case.assignedTo, tidak mengisi/membersihkan lockedBy, dan tidak menghasilkan audit per alert. Karena berjalan dalam transaction service yang sama, unexpected database exception akan menggagalkan operation tersebut sebagai satu transaction.

Assignment synchronization

Ketika case assignment disinkronkan:

[Diagram]

Flow 7 — Maker-Checker, Classification, dan Bulk Classification

7.1 Alert action request

[Diagram]

Classification final adalah POSITIVE dan NEGATIVE. Keduanya:

SUSPICIOUS dan POSTPONED bukan final. POSTPONED membutuhkan durasi dan menyimpan actor/timestamp/until. Temporary classification dapat membuka kembali alert SLA.

7.2 Direct classification

Endpoint direct classify pada Issuer/Acquirer juga tersedia. Jalur ini tetap membutuhkan editing lock dan case ownership, tetapi tidak melalui lifecycle ActionRequest approval.

[Diagram]

7.3 Direct case bulk classification

/case/{id}/bulk-classify current:

  1. mengambil pessimistic lock case;
  2. memuat seluruh Issuer/Acquirer member alerts;
  3. memproses hanya open alerts;
  4. menerapkan classification yang sama ke semua target;
  5. resolve/reopen SLA per alert;
  6. mencoba update fraud flag per final alert;
  7. menulis satu bulk audit pada case;
  8. mencoba auto-close case.

Jalur ini tidak membuat ActionRequest per alert. Kegagalan update fraud flag pada satu alert dicatat dan loop lanjut ke alert berikutnya.

[Diagram]

7.4 Bulk ActionRequest

Issuer dan Acquirer juga memiliki BulkActionRequest. Request menyimpan satu record bulk berisi kumpulan alert IDs, lalu membuat discussion detail per alert.

[Diagram]

Untuk action non-assignment, approval loop berada dalam transaction service bulk; exception yang tidak ditangani dari salah satu runAction dapat membatalkan approval transaction. Untuk assignment action, executor memanggil bulk assign/reassign yang memang mengembalikan partial result.

7.5 Perbandingan jalur bulk classification

Area Direct case bulk classify Bulk ActionRequest classify
Entry point /case/{id}/bulk-classify /bulk-*-action-request/add lalu /approved/{id}
Target resolution Seluruh open alerts pada satu case Explicit alertIds pada bulk record
Lock requirement Tidak terlihat divalidasi Submission membutuhkan lock dan case ownership
Maker-checker Tidak Ya
Audit/history Satu case-level bulk audit Bulk history + discussion per alert
Fraud flag failure Dicatat per alert, loop lanjut Mengikuti exception behavior RunAction
Partial result Tidak ada BulkOperationResult Assignment action dapat membawa partial result

Flow 8 — SLA Alert dan Case

Current source menjalankan dua clock SLA: satu pada case dan satu pada alert.

8.1 Alert SLA

[Diagram]
[Diagram]

Alert checker hanya mengambil ON_TRACK, AT_RISK, dan BREACHED. Transition tidak pernah diturunkan karena evaluator membandingkan ordinal current dan target. WARNING_50 masih memetakan SlaStatus.ON_TRACK; status baru menjadi AT_RISK mulai WARNING_20.

8.2 Case SLA

[Diagram]

Escalation level bersifat naik; scheduler tidak menurunkan level yang sudah lebih tinggi. Pada case SLA breach, pemilihan assignee lain menggunakan membership group dan tidak memakai workload/presence filter selengkap distribution service.

[!DANGER] Dual SLA current

Case dan alert dapat sama-sama menghasilkan warning/breach notification. Ini adalah current behavior, bukan rekomendasi target.

Flow 9 — Case Close, Auto-Close, dan Reopen

9.1 Auto-close

Setelah final classification, closeCaseIfAllResolved:

  1. mengabaikan case missing atau sudah closed;
  2. memuat seluruh member alert;
  3. tidak menutup empty case;
  4. tidak menutup bila masih ada open alert;
  5. menutup jika seluruh alert sudah final.
[Diagram]

9.2 Closed status derivation

Kombinasi hasil member alert Case result
Ada POSITIVE dan NEGATIVE CLOSED_MIXED
Ada POSITIVE, tidak mixed CLOSED_FRAUD
Tidak ada POSITIVE CLOSED_FALSE_POSITIVE

Manual close memakai derivation yang sama, tetapi method current tidak mensyaratkan seluruh alert sudah final.

[Diagram]

9.3 Reopen

Reopen mengubah case menjadi REOPENED, menyimpan actor/date, membersihkan close metadata, mengembalikan case SLA ke ON_TRACK, dan menghitung ulang deadline. Pada source yang ditelusuri, reopen case tidak otomatis mengubah classification/status atau reopen SLA seluruh member alert.

[Diagram]
[Diagram]

Flow 10 — Link, Unlink, Query, dan Historical View

10.1 Link/unlink current

[Diagram]

Current link tidak menyinkronkan target case assignment/status/SLA ke alert yang baru dipindahkan. Current unlink juga tidak membersihkan assignment/status/SLA lama pada alert; ia hanya mengganti caseId ke standalone case baru.

10.2 Unified queue

Unified queue dibangun dari case, bukan raw alert:

Queue Filter utama
Available UNASSIGNED, HOLDING
Assigned assignedTo=username dan IN_REVIEW, ESCALATED, REOPENED
Reassignable Active case statuses
Historical Closed case statuses

Queue ordering current adalah case_score DESC, created_date ASC. Case search umum menggunakan case_score DESC, created_date DESC, sehingga tie ordering keduanya berbeda. Alert count pada queue tidak menghitung duplicate shadow.

Historical alert provider memasukkan alert RESOLVED dan AUTO_NEGATIVE, sehingga AUTO_NEGATIVE tetap dapat muncul sebagai evidence historis walaupun tidak masuk managed case.

[Diagram]

Flow 11 — Notes, Export, dan Notification

Case note access dan persistence

[Diagram]

Case SLA export

[Diagram]

Notification persistence dan SSE

[Diagram]

Group notification lebih dulu mengambil group members, membuang username null/blank, menghapus duplicate username menggunakan LinkedHashSet, menerapkan optional excluded username, lalu memanggil notify() untuk setiap penerima.

Matriks Skenario Operasional

Scenario Alert result Case result Assignment/SLA Next step
Same UTRN sudah ada Tidak membuat alert baru Tidak berubah Tidak berubah Stop
Dedup match aktif Canonical count naik + shadow disimpan Tidak ada assignment baru Shadow locked/classified Historical evidence
AUTO_NEGATIVE ON dan eligible AUTO_NEGATIVE, NEGATIVE Tidak ada case Tidak ada SLA Historical evidence
AUTO_NEGATIVE toggle OFF UNASSIGNED, SUSPICIOUS Masuk clustering/case Mengikuti distribution Investigation
Clustering key null Managed alert Standalone case Optional auto-assign Investigation
Active matching case unassigned Alert join case Count/score naik Tetap unassigned Queue/distribution existing tidak dipicu
Active matching case assigned Alert IN_REVIEW Existing case Inherit assignee + alert SLA Investigation
Closed match