Repository navigation
detect_fraud crashes on two-claim batches and scores unscaled mixed features: fraud flagging is numerically unsound #432
Description
Problem
detect_fraud is numerically unsound and crashes on a small, legitimate batch size. In app/ai-service/services/fraud_detection.py:
n_neighbors = min(20, max(2, len(claims) // 2))
lof = LocalOutlierFactor(n_neighbors=n_neighbors, contamination="auto")
lof.fit_predict(X_noise)scikit-learn's LocalOutlierFactor requires n_neighbors < n_samples. For len(claims) == 2, n_neighbors is 2 and fit raises ValueError (Expected n_neighbors <= n_samples_fit, but n_neighbors = 2, n_samples_fit = 2). Only len(claims) == 1 is special-cased earlier, so a two-claim batch — a realistic batch for a small campaign — crashes the endpoint.
Separately, the feature matrix mixes incomparable scales:
return np.column_stack([
ip_enc.transform(ips), # label-encoded integer category codes
hash_enc.transform(hashes),
loc_enc.transform(locs),
amounts, # raw float token amounts (up to 1e38)
]).astype(float)LabelEncoder assigns arbitrary integers to distinct IPs/hashes/locations, so Euclidean distance in the LOF neighbourhood is dominated by whichever column has the largest code range and by raw amount magnitude, not by any meaningful similarity. Finally, _OUTLIER_THRESHOLD = -1.5 compares the raw negative_outlier_factor_ to a constant that has no calibration against this data, so is_flagged can flip with the batch composition.
Consequence: the fraud-risk signal that downstream flows use to flag claims either throws a 500 (two-claim batch) or produces scores driven by arbitrary category codes and magnitude rather than by anomalous behaviour. Flagging can be all-or-nothing depending on how many claims happen to be in the batch, which is a correctness problem for a disbursement-gating signal.
Root cause
The scorer was built around LOF defaults without a per-batch neighbour guard, without feature normalization/encoding design, and with a hardcoded raw-score threshold.
Why this is architecturally hard
- This is a scoring-pipeline design, not a parameter tweak. The fix must choose a feature representation (one-hot / target encoding / distance-appropriate encoding for IP-hash-location, and normalization or a per-feature distance metric for
amount), not just clampn_neighbors. - Small-batch behaviour must be defined. For 2–3 claims, LOF is either undefined or degenerate; the pipeline must specify a fallback (e.g. a rule-based or threshold-on-features path) and return a defined score instead of crashing.
- The threshold must be calibrated and versioned. A defensible
is_flaggedneeds a documented calibration (percentile, contamination, or an explicit rule) and amodel_versionso that score meaning does not drift silently; closed issue Add amodel_versionfield to all AI responses #272 already requires amodel_versionfield on AI responses, which this must populate. - It is a trust boundary with no regression oracle. There is no golden set of known-fraudulent claims, so tests must at minimum pin: batch sizes 1, 2, 3 do not crash, and a constructed outlier is flagged while a homogeneous batch is not.
Proposed design
Guard n_neighbors to < n_samples (and define a fallback for tiny batches), encode categoricals with a distance-meaningful scheme and normalize/normalize-or-scale amount, and replace the raw -1.5 threshold with a calibrated, documented decision rule. Surface the chosen model_version in the response.
Acceptance criteria
Service
-
detect_fraudreturns a result (never raises) for batch sizes 1, 2, and 3. - A constructed outlier in a batch is flagged, and a homogeneous batch is not, independent of the magnitude of the
amountcolumn. - The response includes a
model_versionconsistent with the scoring pipeline version.
Tests
- Tests pin the no-crash behaviour for sizes 1/2/3 and the outlier-flagging behaviour.
Out of scope
Proof-of-life liveness policy and OCR degradation handling are separate issues.
Getting started
Files: app/ai-service/services/fraud_detection.py, app/ai-service/schemas/fraud.py, app/ai-service/tests/.
cd app/ai-service
pytestGood first files to read: services/fraud_detection.py (_vectorize/detect_fraud) and schemas/fraud.py for the response shape.
Activity
Opulencechuks commentedon Aug 19, 2026
I am confident that I can implement this issue having worked on similar issues in the past.
Opulencechuks commentedon Aug 19, 2026
I am confident that I can implement this issue having worked on similar issues in the past.
amberly-d commentedon Aug 20, 2026
can i handle this?
grantfox-oss commentedon Aug 20, 2026
🦊 GrantFox — @amberly-d has been assigned to this issue as part of the Third Campaign campaign!
Next steps:
Closes #432)Good luck! Track your progress on GrantFox.
fix: handle two-claim batches and normalise features in fraud detecti…
grantfox-oss commentedon Aug 22, 2026
🎉 This issue has been marked as completed on GrantFox as part of the Third Campaign campaign!
@amberly-d's PR #440 was approved and merged by @kilodesodiq-arch.
🏆 @amberly-d: You earned 40 FoxPoints for this contribution! Your current tier: Builder (1,065 total points). Track your full progress on GrantFox.
👏 Great work, @amberly-d! Keep contributing to ChainForgee.