{"repo": "chakshugautam/stray-dog-capture-digit", "files": {"design-outputs/03-configs/mdms/FRAUD-DETECTION/ExternalValidators.json": {"b151dbd41efd0f8d0b83f96bce99b7016568171d": {"t": 1785596336.2926779, "n": 8371, "h": "a86d773213f1b585", "c": "{\n \"tenantId\": \"ncr\",\n \"moduleName\": \"FRAUD-DETECTION\",\n \"ExternalValidators\": [\n {\n \"id\": \"DOG_BREED_CLASSIFIER\",\n \"name\": \"Dog Breed Classification AI\",\n \"description\": \"Identifies dog breed from photo using CNN model\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${AI_SERVICE_URL}/api/v1/classify/dog-breed\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"X-API-Key\",\n \"authSecretKey\": \"AI_SERVICE_API_KEY\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[purpose=DOG_PHOTO].fileStoreId\",\n \"imageFormat\": \"BASE64\"\n },\n \"outputMapping\": {\n \"breedField\": \"predictions.breed\",\n \"confidenceField\": \"predictions.confidence\",\n \"isDogField\": \"predictions.is_dog\"\n },\n \"timeout\": {\n \"connectMs\": 5000,\n \"readMs\": 30000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 1000,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 60000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 86400,\n \"keyFields\": [\"imageHash\"]\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"OBJECT_DETECTOR\",\n \"name\": \"Object Detection (YOLO)\",\n \"description\": \"Detects objects in image - verifies dog presence and count\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${YOLO_SERVICE_URL}/detect\",\n \"method\": \"POST\",\n \"authType\": \"BEARER_TOKEN\",\n \"authSecretKey\": \"YOLO_SERVICE_TOKEN\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[purpose=DOG_PHOTO].fileStoreId\",\n \"imageFormat\": \"URL\",\n \"parameters\": {\n \"classes\": [\"dog\", \"person\", \"cat\"],\n \"minConfidence\": 0.5\n }\n },\n \"outputMapping\": {\n \"detectionsField\": \"detections\",\n \"dogCountField\": \"detections[class=dog].count\",\n \"personPresentField\": \"detections[class=person].exists\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 15000\n },\n \"async\": true,\n \"priority\": \"NORMAL\",\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 3600,\n \"keyFields\": [\"imageHash\", \"parameters\"]\n },\n \"fallback\": {\n \"action\": \"DEGRADE_TO_MANUAL\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"FACE_MATCHER\",\n \"name\": \"Face Recognition & Matching\",\n \"description\": \"Compares selfie with registered user photo\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${FACE_API_URL}/api/verify\",\n \"method\": \"POST\",\n \"authType\": \"OAUTH2\",\n \"tokenEndpoint\": \"${FACE_API_URL}/oauth/token\",\n \"clientIdKey\": \"FACE_API_CLIENT_ID\",\n \"clientSecretKey\": \"FACE_API_CLIENT_SECRET\"\n },\n \"inputMapping\": {\n \"sourceImage\": \"evidences[purpose=SELFIE].fileStoreId\",\n \"targetImage\": \"applicant.profilePhotoId\",\n \"threshold\": 0.85\n },\n \"outputMapping\": {\n \"matchField\": \"result.match\",\n \"similarityField\": \"result.similarity\",\n \"facesDetectedField\": \"result.faces_detected\"\n },\n \"timeout\": {\n \"connectMs\": 5000,\n \"readMs\": 20000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 1000,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 3,\n \"resetTimeoutMs\": 60000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 86400,\n \"keyFields\": [\"sourceImageHash\", \"targetImageHash\"]\n },\n \"fallback\": {\n \"action\": \"DEGRADE_TO_MANUAL\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"IMAGE_QUALITY_ANALYZER\",\n \"name\": \"Image Quality Assessment\",\n \"description\": \"Analyzes image blur, lighting, resolution\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${IQA_SERVICE_URL}/analyze\",\n \"method\": \"POST\",\n \"authType\": \"BASIC\",\n \"usernameKey\": \"IQA_USERNAME\",\n \"passwordKey\": \"IQA_PASSWORD\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[*].fileStoreId\",\n \"checkTypes\": [\"blur\", \"exposure\", \"resolution\", \"noise\"]\n },\n \"outputMapping\": {\n \"overallQuality\": \"quality.overall_score\",\n \"blurScore\": \"quality.blur_score\",\n \"exposureScore\": \"quality.exposure_score\",\n \"isAcceptable\": \"quality.acceptable\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 10000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 1.5\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 3600,\n \"keyFields\": [\"imageHash\"]\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"INFO\"\n }\n },\n {\n \"id\": \"ANOMALY_DETECTOR\",\n \"name\": \"Submission Anomaly Detection\",\n \"description\": \"ML model to detect unusual submission patterns\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${ANOMALY_SERVICE_URL}/predict\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"Authorization\",\n \"authSecretKey\": \"ANOMALY_API_KEY\"\n },\n \"inputMapping\": {\n \"features\": {\n \"submissionHour\": \"createdTime.hour\",\n \"dayOfWeek\": \"createdTime.dayOfWeek\",\n \"gpsLat\": \"locationData.latitude\",\n \"gpsLon\": \"locationData.longitude\",\n \"recentSubmissionCount\": \"applicant.recentCount\",\n \"avgTimeBetweenSubmissions\": \"applicant.avgIntervalMinutes\"\n }\n },\n \"outputMapping\": {\n \"anomalyScore\": \"prediction.anomaly_score\",\n \"isAnomaly\": \"prediction.is_anomaly\",\n \"anomalyType\": \"prediction.anomaly_type\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 5000\n },\n \"retry\": {\n \"maxAttempts\": 1,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 1.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": false\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"GPS_SPOOFING_DETECTOR\",\n \"name\": \"GPS Spoofing Detection\",\n \"description\": \"Detects GPS manipulation using ML signals analysis\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${GPS_VALIDATION_URL}/detect-spoofing\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"X-API-Key\",\n \"authSecretKey\": \"GPS_VALIDATION_API_KEY\"\n },\n \"inputMapping\": {\n \"gpsData\": {\n \"latitude\": \"locationData.latitude\",\n \"longitude\": \"locationData.longitude\",\n \"altitude\": \"locationData.altitude\",\n \"accuracy\": \"locationData.accuracy\",\n \"speed\": \"locationData.speed\",\n \"bearing\": \"locationData.bearing\",\n \"timestamp\": \"locationData.timestamp\"\n },\n \"deviceInfo\": \"evidences[*].metadata.deviceInfo\",\n \"historicalLocations\": \"applicant.recentLocations\"\n },\n \"outputMapping\": {\n \"isSpoofed\": \"prediction.is_spoofed\",\n \"spoofingConfidence\": \"prediction.confidence\",\n \"spoofingMethod\": \"prediction.method\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 8000\n },\n \"retry\": {\n \"maxAttempts\": 1,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 1.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": false\n },\n \"fallback\": {\n \"action\": \"APPLY_DEFAULT\",\n \"defaultValue\": { \"isSpoofed\": false },\n \"logLevel\": \"WARN\"\n }\n }\n ]\n}\n"}}, "design-outputs/08-fraud-detection-service.md": {"b151dbd41efd0f8d0b83f96bce99b7016568171d": {"t": 1785596336.3446352, "n": 74152, "h": "2856f18261396356", "c": "# Fraud Detection Service Design\n\n## Overview\n\nThe **Fraud Detection Service** is a configurable, reusable DIGIT platform service that identifies potential fraud, abuse, and quality issues in citizen applications. It operates asynchronously to analyze submissions, apply configurable fraud rules, and raise flags for human review or automated rejection.\n\n### Design Rationale\n\nInstead of embedding fraud detection logic directly in each domain service (PGR, SDCRS, Trade License, etc.), a centralized Fraud Detection Service provides:\n\n| Benefit | Description |\n|---------|-------------|\n| **Separation of Concerns** | Business logic remains in domain services; fraud logic is isolated |\n| **Reusability** | Same service can protect multiple DIGIT modules |\n| **Configurability** | Fraud rules defined in MDMS, no code changes needed |\n| **Scalability** | Can be scaled independently based on load |\n| **Async Processing** | Doesn't block the submission flow |\n| **Auditability** | Centralized fraud flag history for analytics |\n\n---\n\n## Table of Contents\n\n1. [Architecture](#1-architecture)\n2. [Fraud Rule Types](#2-fraud-rule-types)\n - [2.4 Condition Types](#24-condition-types)\n - [2.5 SpEL Expression Evaluator](#25-spel-expression-evaluator)\n3. [Data Model](#3-data-model)\n4. [API Specifications](#4-api-specifications)\n5. [Kafka Integration](#5-kafka-integration)\n6. [MDMS Configuration](#6-mdms-configuration)\n7. [Database Schema](#7-database-schema)\n8. [Integration Patterns](#8-integration-patterns)\n9. [Fraud Flag Lifecycle](#9-fraud-flag-lifecycle)\n10. [Dashboard & Analytics](#10-dashboard--analytics)\n11. [SDCRS-Specific Rules](#11-sdcrs-specific-rules)\n12. [External AI/ML Service Integration](#12-external-aiml-service-integration)\n13. [Implementation Phases](#13-implementation-phases)\n\n---\n\n## 1. Architecture\n\n### 1.1 High-Level Architecture\n\n```\n┌─────────────────────────────────────────────────────────────────────────────┐\n│ DIGIT Platform │\n├─────────────────────────────────────────────────────────────────────────────┤\n│ │\n│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │\n│ │ SDCRS │ │ PGR │ │ Trade License│ │\n│ │ Service │ │ Service │ │ Service │ │\n│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │\n│ │ │ │ │\n│ ▼ ▼ ▼ │\n│ ┌──────────────────────────────────────────────────────────────┐ │\n│ │ Kafka Topics │ │\n│ │ fraud-check-request fraud-flag-created fraud-resolved │ │\n│ └──────────────────────────────────────────────────────────────┘ │\n│ │ │\n│ ▼ │\n│ ┌────────────────────────────────────────────┐ │\n│ │ FRAUD DETECTION SERVICE │ │\n│ ├────────────────────────────────────────────┤ │\n│ │ ┌─────────────┐ ┌──────────────────────┐ │ │\n│ │ │ Rule Engine │ │ Duplicate Detector │ │ │\n│ │ │ (Drools) │ │ (pHash/SimHash) │ │ │\n│ │ └─────────────┘ └──────────────────────┘ │ │\n│ │ ┌─────────────┐ ┌──────────────────────┐ │ │\n│ │ │ Velocity │ │ Geospatial │ │ │\n│ │ │ Checker │ │ Validator │ │ │\n│ │ └─────────────┘ └──────────────────────┘ │ │\n│ │ ┌─────────────┐ ┌──────────────────────┐ │ │\n│ │ │ Pattern │ │ Network Analysis │ │ │\n│ │ │ Detector │ │ (Collusion) │ │ │\n│ │ └─────────────┘ └──────────────────────┘ │ │\n│ └────────────────────────────────────────────┘ │\n│ │ │\n│ ┌───────────────┼───────────────┐ │\n│ ▼ ▼ ▼ │\n│ ┌─────────-┐ ┌──────────┐ ┌──────────┐ │\n│ │PostgreSQL│ │ Redis │ │Elastic │ │\n│ │(Flags) │ │ (Cache) │ │(Hashes) │ │\n│ └─────────-┘ └──────────┘ └──────────┘ │\n│ │\n└─────────────────────────────────────────────────────────────────────────────┘\n```\n\n### 1.2 Component Responsibilities\n\n| Component | Responsibility |\n|-----------|----------------|\n| **Rule Engine** | Evaluates configurable business rules from MDMS |\n| **Duplicate Detector** | Computes and compares perceptual hashes (pHash) for images |\n| **Velocity Checker** | Detects abnormal submission rates by user/location/time |\n| **Geospatial Validator** | Validates GPS coordinates, distances, boundaries |\n| **Pattern Detector** | Identifies suspicious patterns (time, location, metadata) |\n| **Network Analysis** | Detects collusion between users (shared devices, locations) |\n\n---\n\n## 2. Fraud Rule Types\n\n### 2.1 Rule Categories\n\n| Category | Code | Description | Severity |\n|----------|------|-------------|----------|\n| **Data Quality** | `DQ` | Missing, malformed, or inconsistent data | LOW |\n| **Duplicate** | `DUP` | Same or similar submission already exists | MEDIUM |\n| **Location** | `LOC` | GPS spoofing, boundary violations | HIGH |\n| **Velocity** | `VEL` | Abnormal submission rate | MEDIUM |\n| **Temporal** | `TMP` | Timestamp manipulation, stale data | MEDIUM |\n| **Identity** | `IDN` | Account anomalies, impersonation | HIGH |\n| **Collusion** | `COL` | Coordinated fraud by multiple users | CRITICAL |\n| **Evidence** | `EVD` | Manipulated photos, metadata stripping | HIGH |\n\n### 2.2 Standard Rules (Applicable to All Modules)\n\n| Rule ID | Name | Category | Description |\n|---------|------|----------|-------------|\n| `STD-001` | Missing GPS | DQ | Application lacks GPS coordinates |\n| `STD-002` | GPS Outside Boundary | LOC | GPS not within tenant/jurisdiction |\n| `STD-003` | High Velocity | VEL | >N submissions in T hours by same user |\n| `STD-004` | Stale Timestamp | TMP | Evidence timestamp >N hours old |\n| `STD-005` | Exact Duplicate | DUP | Hash match with existing application |\n| `STD-006` | Near Duplicate | DUP | Similarity >N% with existing application |\n| `STD-007` | Rapid Fire | VEL | Submissions 10\"\n },\n \"action\": { \"type\": \"FLAG\" }\n}\n```\n\n**More expression examples:**\n\n| Use Case | SpEL Expression |\n|----------|-----------------|\n| Incomplete evidence | `#evidences == null \\|\\| #evidenceCount < 2` |\n| Restricted location zone | `#latitude > 28.55 && #latitude < 28.57 && #longitude > 77.08 && #longitude < 77.12` |\n| High-volume reporter | `#additionalData['lifetimeReportCount'] > 50` |\n| Emulator detection | `#deviceId != null && (#deviceId.contains('emulator') \\|\\| #deviceId.contains('sdk'))` |\n| School hours check | `T(java.time.LocalTime).now().isAfter(T(java.time.LocalTime).of(9, 0)) && T(java.time.LocalTime).now().isBefore(T(java.time.LocalTime).of(15, 0))` |\n\n#### 2.5.4 External Validator checkExpression Examples\n\nFor external ML/AI validators, the `#predictions` map contains model outputs:\n\n```json\n{\n \"id\": \"EXT-001\",\n \"code\": \"NO_DOG_DETECTED\",\n \"validatorId\": \"OBJECT_DETECTOR\",\n \"condition\": {\n \"type\": \"EXTERNAL_VALIDATOR\",\n \"checkExpression\": \"dogCount == 0\"\n }\n}\n```\n\n| Validator | Predictions Available | Example checkExpression |\n|-----------|----------------------|------------------------|\n| `OBJECT_DETECTOR` | `dogCount`, `confidence` | `\"dogCount == 0\"` |\n| `FACE_MATCHER` | `facesDetected`, `match`, `similarity` | `\"facesDetected == 0 \\|\\| (match == false && similarity < 0.7)\"` |\n| `IMAGE_QUALITY_ANALYZER` | `blurScore`, `overallQuality`, `isAcceptable` | `\"blurScore < 0.2 \\|\\| overallQuality < 0.4\"` |\n| `ANOMALY_DETECTOR` | `isAnomaly`, `anomalyScore` | `\"isAnomaly == true && anomalyScore > 0.85\"` |\n| `GPS_SPOOFING_DETECTOR` | `isSpoofed`, `spoofingConfidence` | `\"isSpoofed == true && spoofingConfidence > 0.6\"` |\n\n#### 2.5.5 SpEL Implementation Notes\n\n- **Parser**: `SpelExpressionParser` from Spring Framework\n- **Context**: `StandardEvaluationContext` with registered variables\n- **Null Safety**: Expressions should include null checks for optional fields\n- **Type Coercion**: SpEL handles automatic type conversion\n- **Error Handling**: Invalid expressions return false and log errors\n- **Validation**: `isValidExpression()` method validates syntax before evaluation\n\n---\n\n## 3. Data Model\n\n### 3.1 FraudCheckRequest\n\nThe input payload sent by domain services for fraud analysis.\n\n```json\n{\n \"FraudCheckRequest\": {\n \"requestInfo\": { /* Standard DIGIT RequestInfo */ },\n \"fraudCheck\": {\n \"id\": \"uuid\",\n \"tenantId\": \"ncr\",\n \"moduleCode\": \"SDCRS\",\n \"businessService\": \"SDCRS\",\n \"applicationId\": \"NCR-SDCRS-2024-000123\",\n \"applicationNumber\": \"NCR-SDCRS-2024-000123\",\n \"applicantId\": \"user-uuid-123\",\n \"applicantType\": \"TEACHER\",\n \"createdTime\": 1702300000000,\n \"checkType\": \"FULL\",\n \"priority\": \"NORMAL\",\n \"evidences\": [\n {\n \"type\": \"PHOTO\",\n \"fileStoreId\": \"fs-uuid-1\",\n \"purpose\": \"DOG_PHOTO\",\n \"metadata\": {\n \"gpsLatitude\": 28.6139,\n \"gpsLongitude\": 77.2090,\n \"timestamp\": 1702299000000,\n \"deviceId\": \"device-123\",\n \"exifPresent\": true\n }\n },\n {\n \"type\": \"PHOTO\",\n \"fileStoreId\": \"fs-uuid-2\",\n \"purpose\": \"SELFIE\",\n \"metadata\": {\n \"gpsLatitude\": 28.6140,\n \"gpsLongitude\": 77.2091,\n \"timestamp\": 1702299100000,\n \"deviceId\": \"device-123\",\n \"exifPresent\": true\n }\n }\n ],\n \"locationData\": {\n \"reportedLatitude\": 28.6139,\n \"reportedLongitude\": 77.2090,\n \"locality\": \"LOC-001\",\n \"ward\": \"WARD-A\",\n \"district\": \"CENTRAL\"\n },\n \"additionalData\": {\n \"dogCount\": 2,\n \"isAggressive\": true,\n \"breed\": \"MIXED\"\n }\n }\n }\n}\n```\n\n### 3.2 FraudFlag\n\nThe output when a fraud rule is triggered.\n\n```json\n{\n \"FraudFlag\": {\n \"id\": \"flag-uuid-1\",\n \"tenantId\": \"ncr\",\n \"moduleCode\": \"SDCRS\",\n \"applicationId\": \"NCR-SDCRS-2024-000123\",\n \"ruleId\": \"SDCRS-003\",\n \"ruleCode\": \"GPS_PHOTO_SELFIE_MISMATCH\",\n \"category\": \"LOC\",\n \"severity\": \"HIGH\",\n \"status\": \"OPEN\",\n \"score\": 85,\n \"details\": {\n \"message\": \"Dog photo GPS is 850m from selfie GPS\",\n \"threshold\": 500,\n \"actualValue\": 850,\n \"unit\": \"meters\",\n \"evidence\": {\n \"dogPhotoGps\": { \"lat\": 28.6139, \"lon\": 77.2090 },\n \"selfieGps\": { \"lat\": 28.6200, \"lon\": 77.2150 }\n }\n },\n \"recommendedAction\": \"MANUAL_REVIEW\",\n \"autoAction\": null,\n \"linkedFlags\": [],\n \"linkedApplications\": [],\n \"resolution\": null,\n \"auditDetails\": {\n \"createdBy\": \"SYSTEM\",\n \"createdTime\": 1702300500000,\n \"lastModifiedBy\": null,\n \"lastModifiedTime\": null\n }\n }\n}\n```\n\n### 3.3 FraudCheckResult\n\nThe complete result of fraud analysis.\n\n```json\n{\n \"FraudCheckResult\": {\n \"id\": \"result-uuid-1\",\n \"fraudCheckId\": \"check-uuid-1\",\n \"applicationId\": \"NCR-SDCRS-2024-000123\",\n \"status\": \"FLAGGED\",\n \"overallScore\": 72,\n \"riskLevel\": \"HIGH\",\n \"flagCount\": 2,\n \"flags\": [ /* Array of FraudFlag */ ],\n \"recommendation\": \"HOLD_FOR_REVIEW\",\n \"processingTime\": 1250,\n \"rulesEvaluated\": 15,\n \"rulesPassed\": 13,\n \"rulesFailed\": 2,\n \"checksPerformed\": {\n \"duplicateCheck\": true,\n \"velocityCheck\": true,\n \"geoCheck\": true,\n \"temporalCheck\": true,\n \"patternCheck\": true\n },\n \"hashes\": {\n \"imageHash\": \"a4b3c2d1e0f9...\",\n \"contentHash\": \"sha256:...\"\n }\n }\n}\n```\n\n---\n\n## 4. API Specifications\n\n### 4.1 Endpoint Summary\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `POST` | `/fraud-detection/v1/_check` | Submit application for fraud check |\n| `POST` | `/fraud-detection/v1/_checkAsync` | Async fraud check via Kafka |\n| `POST` | `/fraud-detection/v1/flags/_search` | Search fraud flags |\n| `GET` | `/fraud-detection/v1/flags/{id}` | Get flag by ID |\n| `POST` | `/fraud-detection/v1/flags/_resolve` | Resolve a fraud flag |\n| `POST` | `/fraud-detection/v1/flags/_escalate` | Escalate flag to higher authority |\n| `POST` | `/fraud-detection/v1/rules/_search` | Search configured rules |\n| `POST` | `/fraud-detection/v1/stats/_aggregate` | Get fraud statistics |\n| `POST` | `/fraud-detection/v1/applicant/_riskProfile` | Get applicant risk profile |\n\n### 4.2 Synchronous Check API\n\n**POST** `/fraud-detection/v1/_check`\n\nUsed for real-time fraud check during application submission.\n\n**Request:**\n```json\n{\n \"RequestInfo\": { /* Standard DIGIT RequestInfo */ },\n \"fraudCheck\": {\n \"tenantId\": \"ncr\",\n \"moduleCode\": \"SDCRS\",\n \"applicationId\": \"NCR-SDCRS-2024-000123\",\n \"applicantId\": \"user-uuid-123\",\n \"checkType\": \"QUICK\",\n \"evidences\": [ /* ... */ ],\n \"locationData\": { /* ... */ }\n }\n}\n```\n\n**Response:**\n```json\n{\n \"ResponseInfo\": { /* Standard DIGIT ResponseInfo */ },\n \"fraudCheckResult\": {\n \"status\": \"CLEAN\",\n \"overallScore\": 15,\n \"riskLevel\": \"LOW\",\n \"flagCount\": 0,\n \"flags\": [],\n \"recommendation\": \"ALLOW\",\n \"processingTime\": 450\n }\n}\n```\n\n### 4.3 Flag Search API\n\n**POST** `/fraud-detection/v1/flags/_search`\n\n```json\n{\n \"RequestInfo\": { /* ... */ },\n \"searchCriteria\": {\n \"tenantId\": \"ncr\",\n \"moduleCode\": \"SDCRS\",\n \"applicationIds\": [\"NCR-SDCRS-2024-000123\"],\n \"applicantIds\": [\"user-uuid-123\"],\n \"status\": [\"OPEN\", \"UNDER_REVIEW\"],\n \"severity\": [\"HIGH\", \"CRITICAL\"],\n \"category\": [\"LOC\", \"DUP\"],\n \"fromDate\": 1702000000000,\n \"toDate\": 1702400000000,\n \"offset\": 0,\n \"limit\": 50,\n \"sortBy\": \"createdTime\",\n \"sortOrder\": \"DESC\"\n }\n}\n```\n\n### 4.4 Resolve Flag API\n\n**POST** `/fraud-detection/v1/flags/_resolve`\n\n```json\n{\n \"RequestInfo\": { /* ... */ },\n \"flagResolution\": {\n \"flagId\": \"flag-uuid-1\",\n \"resolution\": \"FALSE_POSITIVE\",\n \"resolutionReason\": \"Photos taken in quick succession; GPS drift is within acceptable range\",\n \"action\": \"ALLOW_APPLICATION\",\n \"reviewerId\": \"verifier-uuid\"\n }\n}\n```\n\n**Resolution Types:**\n- `FALSE_POSITIVE` - Flag was incorrect, application is legitimate\n- `TRUE_POSITIVE` - Fraud confirmed, take action\n- `INCONCLUSIVE` - Cannot determine, escalate or allow with monitoring\n- `DUPLICATE_FLAG` - Already addressed by another flag\n\n---\n\n## 5. Kafka Integration\n\n### 5.1 Topics\n\n| Topic | Publisher | Consumer | Purpose |\n|-------|-----------|----------|---------|\n| `fraud-check-request` | Domain Services | Fraud Detection | Async check requests |\n| `fraud-check-result` | Fraud Detection | Domain Services | Check results |\n| `fraud-flag-created` | Fraud Detection | Notification, DSS | New flag alerts |\n| `fraud-flag-resolved` | Fraud Detection | DSS, Audit | Flag resolution events |\n| `fraud-applicant-risk` | Fraud Detection | User Service | Risk profile updates |\n\n### 5.2 Message Formats\n\n**fraud-check-request:**\n```json\n{\n \"topic\": \"fraud-check-request\",\n \"key\": \"NCR-SDCRS-2024-000123\",\n \"value\": {\n \"fraudCheck\": { /* FraudCheckRequest */ }\n }\n}\n```\n\n**fraud-flag-created:**\n```json\n{\n \"topic\": \"fraud-flag-created\",\n \"key\": \"flag-uuid-1\",\n \"value\": {\n \"flag\": { /* FraudFlag */ },\n \"application\": {\n \"id\": \"NCR-SDCRS-2024-000123\",\n \"moduleCode\": \"SDCRS\",\n \"applicantId\": \"user-uuid-123\"\n }\n }\n}\n```\n\n---\n\n## 6. MDMS Configuration\n\n### 6.1 Module: FRAUD-DETECTION\n\n#### FraudRules.json\n\n```json\n{\n \"tenantId\": \"ncr\",\n \"moduleName\": \"FRAUD-DETECTION\",\n \"FraudRules\": [\n {\n \"id\": \"STD-001\",\n \"code\": \"MISSING_GPS\",\n \"name\": \"Missing GPS Coordinates\",\n \"description\": \"Application evidence lacks GPS coordinates\",\n \"category\": \"DQ\",\n \"severity\": \"MEDIUM\",\n \"enabled\": true,\n \"applicableModules\": [\"SDCRS\", \"PGR\", \"TL\"],\n \"condition\": {\n \"type\": \"NULL_CHECK\",\n \"field\": \"evidences[*].metadata.gpsLatitude\"\n },\n \"action\": {\n \"type\": \"FLAG\",\n \"autoReject\": false,\n \"notifyApplicant\": false,\n \"notifyVerifier\": true\n }\n },\n {\n \"id\": \"STD-002\",\n \"code\": \"GPS_OUTSIDE_BOUNDARY\",\n \"name\": \"GPS Outside Tenant Boundary\",\n \"description\": \"GPS coordinates are outside the tenant jurisdiction\",\n \"category\": \"LOC\",\n \"severity\": \"HIGH\",\n \"enabled\": true,\n \"applicableModules\": [\"SDCRS\", \"PGR\"],\n \"condition\": {\n \"type\": \"GEO_BOUNDARY\",\n \"field\": \"locationData\",\n \"boundaryType\": \"TENANT\"\n },\n \"action\": {\n \"type\": \"FLAG\",\n \"autoReject\": true,\n \"rejectionReason\": \"LOC_OUTSIDE_BOUNDARY\",\n \"notifyApplicant\": true\n }\n },\n {\n \"id\": \"STD-003\",\n \"code\": \"HIGH_VELOCITY\",\n \"name\": \"High Submission Velocity\",\n \"description\": \"User submitted too many applications in short time\",\n \"category\": \"VEL\",\n \"severity\": \"MEDIUM\",\n \"enabled\": true,\n \"applicableModules\": [\"SDCRS\"],\n \"condition\": {\n \"type\": \"VELOCITY\",\n \"field\": \"applicantId\",\n \"threshold\": 5,\n \"windowHours\": 1\n },\n \"action\": {\n \"type\": \"FLAG\",\n \"autoReject\": false,\n \"cooldownMinutes\": 60\n }\n },\n {\n \"id\": \"STD-006\",\n \"code\": \"NEAR_DUPLICATE\",\n \"name\": \"Near-Duplicate Submission\",\n \"description\": \"Image is highly similar to a recent submission\",\n \"category\": \"DUP\",\n \"severity\": \"HIGH\",\n \"enabled\": true,\n \"applicableModules\": [\"SDCRS\"],\n \"condition\": {\n \"type\": \"IMAGE_SIMILARITY\",\n \"field\": \"evidences[purpose=DOG_PHOTO].fileStoreId\",\n \"algorithm\": \"pHash\",\n \"threshold\": 0.90,\n \"lookbackDays\": 7\n },\n \"action\": {\n \"type\": \"FLAG\",\n \"autoReject\": false,\n \"linkToOriginal\": true\n }\n }\n ]\n}\n```\n\n#### RiskScoreConfig.json\n\n```json\n{\n \"tenantId\": \"ncr\",\n \"moduleName\": \"FRAUD-DETECTION\",\n \"RiskScoreConfig\": {\n \"weights\": {\n \"DQ\": 10,\n \"DUP\": 30,\n \"LOC\": 40,\n \"VEL\": 20,\n \"TMP\": 25,\n \"IDN\": 50,\n \"COL\": 60,\n \"EVD\": 35\n },\n \"thresholds\": {\n \"LOW\": { \"min\": 0, \"max\": 25 },\n \"MEDIUM\": { \"min\": 26, \"max\": 50 },\n \"HIGH\": { \"min\": 51, \"max\": 75 },\n \"CRITICAL\": { \"min\": 76, \"max\": 100 }\n },\n \"autoRejectThreshold\": 80,\n \"escalationThreshold\": 60\n }\n}\n```\n\n#### ApplicantPenalties.json\n\n```json\n{\n \"tenantId\": \"ncr\",\n \"moduleName\": \"FRAUD-DETECTION\",\n \"ApplicantPenalties\": [\n {\n \"level\": 1,\n \"name\": \"WARNING\",\n \"triggerCondition\": { \"confirmedFrauds\": 1 },\n \"action\": \"NOTIFY_WARNING\",\n \"duration\": null\n },\n {\n \"level\": 2,\n \"name\": \"COOLDOWN\",\n \"triggerCondition\": { \"confirmedFrauds\": 2, \"withinDays\": 30 },\n \"action\": \"SUBMISSION_BLOCK\",\n \"duration\": { \"days\": 7 }\n },\n {\n \"level\": 3,\n \"name\": \"SUSPENSION\",\n \"triggerCondition\": { \"confirmedFrauds\": 3, \"withinDays\": 90 },\n \"action\": \"ACCOUNT_SUSPEND\",\n \"duration\": { \"days\": 30 }\n },\n {\n \"level\": 4,\n \"name\": \"BAN\",\n \"triggerCondition\": { \"confirmedFrauds\": 5 },\n \"action\": \"PERMANENT_BAN\",\n \"duration\": null,\n \"appealProcess\": true\n }\n ]\n}\n```\n\n---\n\n## 7. Database Schema\n\n### 7.1 Evidence-Based Duplicate Detection Architecture\n\nThe Fraud Detection Service is designed as a **query consumer**, not a data store for evidence. Evidence metadata (hashes, GPS coordinates, timestamps) is computed and persisted during the Dog Report submission workflow, and the Fraud Detection Service queries this data when evaluating duplicate detection rules.\n\n#### Design Rationale\n\n| Approach | Pros | Cons |\n|----------|------|------|\n| **Fraud service stores hashes** | Simple isolation | Data duplication, sync issues, lost on restart |\n| **Fraud service queries report DB** ✓ | Single source of truth, always current | Requires DB access, slightly higher latency |\n\n**Recommended: Query-based approach** - The Dog Report Service computes and stores evidence metadata (pHash, EXIF GPS, timestamps) at submission time. The Fraud Detection Service queries this data when evaluating HASH_MATCH, IMAGE_SIMILARITY, and GEO_CLUSTER rules.\n\n#### Evidence Data Flow\n\n```\nSUBMISSION TIME FRAUD EVALUATION TIME\n─────────────── ─────────────────────\nTeacher App Fraud Detection Service\n │ │\n │ POST /sdcrs/v1/report/_create │ Query existing evidence\n ▼ ▼\nSDCRS Service SELECT * FROM eg_sdcrs_report_evidence\n • Compute pHash from photo WHERE image_hash_hex = ?\n • Extract EXIF GPS coordinates OR bit_count(image_phash # ?) <= 10\n • Store evidence metadata OR ST_DWithin(gps_location, ?, 50m)\n │\n │ Kafka: save-sdcrs-report\n ▼\nPostgreSQL\n • eg_sdcrs_report (main report table)\n • eg_sdcrs_report_evidence (evidence metadata)\n```\n\n### 7.2 Evidence Table Schema\n\n```sql\n-- Evidence metadata for duplicate detection (populated by SDCRS Service at submission)\nCREATE TABLE eg_sdcrs_report_evidence (\n id VARCHAR(64) PRIMARY KEY,\n report_id VARCHAR(64) NOT NULL REFERENCES eg_sdcrs_report(id),\n tenant_id VARCHAR(64) NOT NULL,\n reporter_id VARCHAR(64) NOT NULL,\n evidence_type VARCHAR(32) NOT NULL, -- DOG_PHOTO, SELFIE, CAPTURE_PHOTO\n filestore_id VARCHAR(64) NOT NULL,\n\n -- Hash columns for duplicate detection\n image_phash BIGINT, -- Perceptual hash (64-bit)\n image_hash_hex VARCHAR(64), -- SHA-256 hex for exact match\n\n -- Location data from EXIF\n gps_location GEOGRAPHY(POINT, 4326), -- PostGIS point (lon, lat)\n gps_latitude DECIMAL(10, 8),\n gps_longitude DECIMAL(11, 8),\n\n -- Temporal data\n capture_timestamp BIGINT, -- EXIF timestamp (epoch ms)\n\n -- Device data\n device_id VARCHAR(128),\n device_model VARCHAR(128),\n exif_present BOOLEAN DEFAULT false,\n\n -- Audit\n created_time BIGINT NOT NULL,\n created_by VARCHAR(64) NOT NULL\n);\n\n-- Indexes optimized for fraud detection queries\nCREATE INDEX idx_evidence_report ON eg_sdcrs_report_evidence(report_id);\nCREATE INDEX idx_evidence_reporter ON eg_sdcrs_report_evidence(reporter_id);\nCREATE INDEX idx_evidence_tenant ON eg_sdcrs_report_evidence(tenant_id);\n\n-- B-tree for exact hash lookups: O(log n), <1ms\nCREATE INDEX idx_evidence_hash_hex ON eg_sdcrs_report_evidence(image_hash_hex);\n\n-- B-tree for pHash similarity (enables bit_count filtering): O(log n)\nCREATE INDEX idx_evidence_phash ON eg_sdcrs_report_evidence(image_phash);\n\n-- GiST spatial index for geo proximity: O(log n), 2-5ms\nCREATE INDEX idx_evidence_location ON eg_sdcrs_report_evidence\n USING GIST (gps_location);\n\n-- Composite for time-bounded queries\nCREATE INDEX idx_evidence_created ON eg_sdcrs_report_evidence(created_time DESC);\nCREATE INDEX idx_evidence_device ON eg_sdcrs_report_evidence(device_id);\n```\n\n### 7.3 Duplicate Detection Query Patterns\n\nThe Fraud Detection Service executes these queries against the evidence table:\n\n#### 7.3.1 Exact Duplicate Detection (HASH_MATCH rule)\n\n```sql\n-- O(log n) with B-tree index, latency <1ms\n-- Finds if the exact same photo was submitted before\n\nSELECT e.report_id, r.report_number, r.reporter_id, r.status\nFROM eg_sdcrs_report_evidence e\nJOIN eg_sdcrs_report r ON e.report_id = r.id\nWHERE e.tenant_id = :tenantId\n AND e.image_hash_hex = :submittedHashHex\n AND e.report_id != :currentReportId\n AND r.status NOT IN ('AUTO_REJECTED', 'REJECTED')\n AND e.created_time > :thirtyDaysAgo\nORDER BY e.created_time DESC\nLIMIT 5;\n```\n\n#### 7.3.2 Near-Duplicate Detection (IMAGE_SIMILARITY rule)\n\n```sql\n-- Uses bit_count() for Hamming distance on pHash\n-- PostgreSQL: bit_count(a # b) where # is XOR\n-- Hamming distance ≤ 10 means ~85% visual similarity\n\nSELECT e.report_id, r.report_number, r.reporter_id,\n bit_count(e.image_phash # :submittedPHash) AS hamming_distance\nFROM eg_sdcrs_report_evidence e\nJOIN eg_sdcrs_report r ON e.report_id = r.id\nWHERE e.tenant_id = :tenantId\n AND e.evidence_type = 'DOG_PHOTO'\n AND e.report_id != :currentReportId\n AND e.image_phash IS NOT NULL\n AND bit_count(e.image_phash # :submittedPHash) <= 10\n AND r.status NOT IN ('AUTO_REJECTED', 'REJECTED')\n AND e.created_time > :sevenDaysAgo\nORDER BY hamming_distance ASC\nLIMIT 10;\n```\n\n#### 7.3.3 Geo-Temporal Duplicate Detection (GEO_CLUSTER rule)\n\n```sql\n-- Finds reports from DIFFERENT reporters at same location within 24 hours\n-- Uses PostGIS ST_DWithin for spatial query with GiST index\n\nSELECT e.report_id, r.report_number, r.reporter_id,\n ST_Distance(e.gps_location::geography,\n ST_MakePoint(:lon, :lat)::geography) AS distance_meters\nFROM eg_sdcrs_report_evidence e\nJOIN eg_sdcrs_report r ON e.report_id = r.id\nWHERE e.tenant_id = :tenantId\n AND e.evidence_type = 'DOG_PHOTO'\n AND e.report_id != :currentReportId\n AND r.reporter_id != :currentReporterId -- Different reporter\n AND ST_DWithin(\n e.gps_location::geography,\n ST_MakePoint(:lon, :lat)::geography,\n 50 -- 50 meters radius\n )\n AND e.created_time > :twentyFourHoursAgo\n AND r.status NOT IN ('AUTO_REJECTED', 'REJECTED', 'DUPLICATE')\nORDER BY distance_meters ASC\nLIMIT 5;\n```\n\n#### 7.3.4 Device Collusion Detection (DEVICE_SHARING rule)\n\n```sql\n-- Finds multiple unique reporters using the same device\n\nSELECT e.device_id, COUNT(DISTINCT r.reporter_id) AS unique_reporters,\n ARRAY_AGG(DISTINCT r.reporter_id) AS reporter_ids\nFROM eg_sdcrs_report_evidence e\nJOIN eg_sdcrs_report r ON e.report_id = r.id\nWHERE e.tenant_id = :tenantId\n AND e.device_id = :submittedDeviceId\n AND e.device_id IS NOT NULL\n AND e.created_time > :sevenDaysAgo\nGROUP BY e.device_id\nHAVING COUNT(DISTINCT r.reporter_id) >= 2;\n```\n\n### 7.4 Query Performance Estimates\n\n| Check Type | Index Used | Complexity | Expected Latency |\n|------------|-----------|------------|------------------|\n| Exact hash match | B-tree on `image_hash_hex` | O(log n) | <1ms |\n| pHash similarity | B-tree + bit_count filter | O(n) filtered | 5-20ms |\n| Geo proximity | GiST spatial on `gps_location` | O(log n) | 2-5ms |\n| Device lookup | B-tree on `device_id` | O(log n) | <1ms |\n| **Total fraud evaluation** | | | **~10-30ms** |\n\n### 7.5 SDCRS Service Evidence Enrichment\n\nThe SDCRS Service computes and persists evidence metadata during report creation:\n\n```java\n// In SDCRSService.create() - called before persisting report\nprivate void enrichEvidenceMetadata(DogReport report) {\n for (Evidence evidence : report.getEvidences()) {\n // 1. Fetch file from FileStore\n FileStoreResponse file = fileStoreService.get(evidence.getFileStoreId());\n\n // 2. Compute perceptual hash (64-bit pHash)\n long pHash = imageHashService.computePHash(file.getBytes());\n evidence.setImagePHash(pHash);\n\n // 3. Compute SHA-256 for exact match\n String sha256 = DigestUtils.sha256Hex(file.getBytes());\n evidence.setImageHashHex(sha256);\n\n // 4. Extract EXIF GPS if present\n ExifData exif = exifService.extract(file.getBytes());\n if (exif != null && exif.hasGps()) {\n evidence.setGpsLatitude(exif.getLatitude());\n evidence.setGpsLongitude(exif.getLongitude());\n evidence.setCaptureTimestamp(exif.getTimestamp());\n evidence.setExifPresent(true);\n }\n\n // 5. Store device info\n evidence.setDeviceId(report.getDeviceInfo().getDeviceId());\n evidence.setDeviceModel(report.getDeviceInfo().getModel());\n }\n}\n```\n\n### 7.6 Fraud Detection Service Query Implementation\n\n```java\n// In InternalRuleEvaluator - queries evidence table for duplicate detection\n@Service\npublic class DuplicateDetectionService {\n\n @Autowired\n private JdbcTemplate jdbcTemplate;\n\n /**\n * Check for exact duplicate (same image hash)\n */\n public Optional findExactDuplicate(\n String tenantId, String imageHashHex, String excludeReportId) {\n\n String sql = \"\"\"\n SELECT e.report_id, r.report_number, r.reporter_id\n FROM eg_sdcrs_report_evidence e\n JOIN eg_sdcrs_report r ON e.report_id = r.id\n WHERE e.tenant_id = ?\n AND e.image_hash_hex = ?\n AND e.report_id != ?\n AND r.status NOT IN ('AUTO_REJECTED', 'REJECTED')\n AND e.created_time > ?\n ORDER BY e.created_time DESC\n LIMIT 1\n \"\"\";\n\n long thirtyDaysAgo = System.currentTimeMillis() - (30L * 24 * 60 * 60 * 1000);\n\n return jdbcTemplate.query(sql,\n new Object[]{tenantId, imageHashHex, excludeReportId, thirtyDaysAgo},\n rs -> rs.next() ? Optional.of(new DuplicateMatch(\n rs.getString(\"report_id\"),\n rs.getString(\"report_number\"),\n rs.getString(\"reporter_id\"),\n DuplicateType.EXACT\n )) : Optional.empty()\n );\n }\n\n /**\n * Check for near-duplicate using pHash Hamming distance\n */\n public List findSimilarImages(\n String tenantId, long pHash, String excludeReportId, int maxHammingDistance) {\n\n String sql = \"\"\"\n SELECT e.report_id, r.report_number, r.reporter_id,\n bit_count(e.image_phash # ?::bigint) AS hamming_distance\n FROM eg_sdcrs_report_evidence e\n JOIN eg_sdcrs_report r ON e.report_id = r.id\n WHERE e.tenant_id = ?\n AND e.evidence_type = 'DOG_PHOTO'\n AND e.report_id != ?\n AND e.image_phash IS NOT NULL\n AND bit_count(e.image_phash # ?::bigint) <= ?\n AND r.status NOT IN ('AUTO_REJECTED', 'REJECTED')\n AND e.created_time > ?\n ORDER BY hamming_distance ASC\n LIMIT 10\n \"\"\";\n\n long sevenDaysAgo = System.currentTimeMillis() - (7L * 24 * 60 * 60 * 1000);\n\n return jdbcTemplate.query(sql,\n new Object[]{pHash, tenantId, excludeReportId, pHash, maxHammingDistance, sevenDaysAgo},\n (rs, rowNum) -> new DuplicateMatch(\n rs.getString(\"report_id\"),\n rs.getString(\"report_number\"),\n rs.getString(\"reporter_id\"),\n DuplicateType.SIMILAR,\n rs.getInt(\"hamming_distance\")\n )\n );\n }\n}\n```\n\n---\n\n### 7.7 Fraud Service Tables\n\n```sql\n-- Fraud check requests and results\nCREATE TABLE eg_fraud_check (\n id VARCHAR(64) PRIMARY KEY,\n tenant_id VARCHAR(64) NOT NULL,\n module_code VARCHAR(32) NOT NULL,\n business_service VARCHAR(64),\n application_id VARCHAR(128) NOT NULL,\n applicant_id VARCHAR(64) NOT NULL,\n applicant_type VARCHAR(32),\n check_type VARCHAR(16) NOT NULL, -- QUICK, FULL, DEEP\n priority VARCHAR(16) DEFAULT 'NORMAL',\n status VARCHAR(32) NOT NULL, -- PENDING, PROCESSING, COMPLETED, FAILED\n overall_score INTEGER,\n risk_level VARCHAR(16),\n flag_count INTEGER DEFAULT 0,\n recommendation VARCHAR(32),\n processing_time_ms INTEGER,\n rules_evaluated INTEGER,\n rules_passed INTEGER,\n rules_failed INTEGER,\n image_hash VARCHAR(128),\n content_hash VARCHAR(128),\n additional_data JSONB,\n created_by VARCHAR(64) NOT NULL,\n created_time BIGINT NOT NULL,\n last_modified_by VARCHAR(64),\n last_modified_time BIGINT\n);\n\nCREATE INDEX idx_fraud_check_tenant ON eg_fraud_check(tenant_id);\nCREATE INDEX idx_fraud_check_module ON eg_fraud_check(module_code);\nCREATE INDEX idx_fraud_check_application ON eg_fraud_check(application_id);\nCREATE INDEX idx_fraud_check_applicant ON eg_fraud_check(applicant_id);\nCREATE INDEX idx_fraud_check_status ON eg_fraud_check(status);\nCREATE INDEX idx_fraud_check_created ON eg_fraud_check(created_time);\n\n-- Fraud flags raised during checks\nCREATE TABLE eg_fraud_flag (\n id VARCHAR(64) PRIMARY KEY,\n fraud_check_id VARCHAR(64) NOT NULL REFERENCES eg_fraud_check(id),\n tenant_id VARCHAR(64) NOT NULL,\n module_code VARCHAR(32) NOT NULL,\n application_id VARCHAR(128) NOT NULL,\n applicant_id VARCHAR(64) NOT NULL,\n rule_id VARCHAR(32) NOT NULL,\n rule_code VARCHAR(64) NOT NULL,\n category VARCHAR(8) NOT NULL,\n severity VARCHAR(16) NOT NULL,\n status VARCHAR(32) NOT NULL, -- OPEN, UNDER_REVIEW, RESOLVED, DISMISSED\n score INTEGER,\n details JSONB,\n recommended_action VARCHAR(32),\n auto_action VARCHAR(32),\n resolution VARCHAR(32),\n resolution_reason TEXT,\n resolver_id VARCHAR(64),\n resolved_time BIGINT,\n linked_flag_ids VARCHAR(256),\n linked_application_ids VARCHAR(512),\n created_by VARCHAR(64) NOT NULL,\n created_time BIGINT NOT NULL,\n last_modified_by VARCHAR(64),\n last_modified_time BIGINT\n);\n\nCREATE INDEX idx_fraud_flag_tenant ON eg_fraud_flag(tenant_id);\nCREATE INDEX idx_fraud_flag_module ON eg_fraud_flag(module_code);\nCREATE INDEX idx_fraud_flag_application ON eg_fraud_flag(application_id);\nCREATE INDEX idx_fraud_flag_applicant ON eg_fraud_flag(applicant_id);\nCREATE INDEX idx_fraud_flag_status ON eg_fraud_flag(status);\nCREATE INDEX idx_fraud_flag_severity ON eg_fraud_flag(severity);\nCREATE INDEX idx_fraud_flag_category ON eg_fraud_flag(category);\nCREATE INDEX idx_fraud_flag_created ON eg_fraud_flag(created_time);\n\n-- Applicant risk profiles\nCREATE TABLE eg_applicant_risk_profile (\n id VARCHAR(64) PRIMARY KEY,\n tenant_id VARCHAR(64) NOT NULL,\n applicant_id VARCHAR(64) NOT NULL UNIQUE,\n risk_score INTEGER DEFAULT 0,\n risk_level VARCHAR(16) DEFAULT 'LOW',\n total_applications INTEGER DEFAULT 0,\n flagged_applications INTEGER DEFAULT 0,\n confirmed_frauds INTEGER DEFAULT 0,\n false_positives INTEGER DEFAULT 0,\n penalty_level INTEGER DEFAULT 0,\n penalty_status VARCHAR(32),\n penalty_expiry BIGINT,\n last_application_time BIGINT,\n last_fraud_time BIGINT,\n additional_data JSONB,\n created_time BIGINT NOT NULL,\n last_modified_time BIGINT\n);\n\nCREATE UNIQUE INDEX idx_risk_profile_applicant ON eg_applicant_risk_profile(tenant_id, applicant_id);\nCREATE INDEX idx_risk_profile_score ON eg_applicant_risk_profile(risk_score);\nCREATE INDEX idx_risk_profile_level ON eg_applicant_risk_profile(risk_level);\n\n-- Image hashes for duplicate detection\nCREATE TABLE eg_image_hash (\n id VARCHAR(64) PRIMARY KEY,\n tenant_id VARCHAR(64) NOT NULL,\n module_code VARCHAR(32) NOT NULL,\n application_id VARCHAR(128) NOT NULL,\n file_store_id VARCHAR(64) NOT NULL,\n purpose VARCHAR(32), -- DOG_PHOTO, SELFIE, EVIDENCE\n hash_algorithm VARCHAR(16) NOT NULL, -- pHash, dHash, MD5\n hash_value VARCHAR(256) NOT NULL,\n hash_bits BYTEA, -- Binary hash for efficient comparison\n created_time BIGINT NOT NULL\n);\n\nCREATE INDEX idx_image_hash_tenant ON eg_image_hash(tenant_id);\nCREATE INDEX idx_image_hash_module ON eg_image_hash(module_code);\nCREATE INDEX idx_image_hash_value ON eg_image_hash(hash_value);\nCREATE INDEX idx_image_hash_created ON eg_image_hash(created_time);\n```\n\n### 7.2 Elasticsearch Index for Hash Similarity\n\n```json\n{\n \"settings\": {\n \"number_of_shards\": 3,\n \"number_of_replicas\": 1\n },\n \"mappings\": {\n \"properties\": {\n \"tenantId\": { \"type\": \"keyword\" },\n \"moduleCode\": { \"type\": \"keyword\" },\n \"applicationId\": { \"type\": \"keyword\" },\n \"fileStoreId\": { \"type\": \"keyword\" },\n \"purpose\": { \"type\": \"keyword\" },\n \"hashVector\": {\n \"type\": \"dense_vector\",\n \"dims\": 64,\n \"index\": true,\n \"similarity\": \"cosine\"\n },\n \"createdTime\": { \"type\": \"date\", \"format\": \"epoch_millis\" }\n }\n }\n}\n```\n\n---\n\n## 8. Integration Patterns\n\n### 8.1 Synchronous Check (Blocking)\n\nFor high-severity rules that must block submission:\n\n```\n┌─────────┐ ┌──────────────┐ ┌────────────────┐\n│ SDCRS │ │ Gateway │ │ Fraud Service │\n└────┬────┘ └──────┬───────┘ └───────┬────────┘\n │ │ │\n │ POST /_create │ │\n │────────────────────►│ │\n │ │ POST /_check (QUICK) │\n │ │────────────────────────►│\n │ │ │\n │ │ FraudCheckResult │\n │ │◄────────────────────────│\n │ │ │\n │ if CRITICAL: │ │\n │ reject │ │\n │◄────────────────────│ │\n │ │ │\n │ else: continue │ │\n │ with workflow │ │\n```\n\n### 8.2 Asynchronous Check (Non-Blocking)\n\nFor detailed analysis that can run post-submission:\n\n```\n┌─────────┐ ┌───────┐ ┌────────────────┐ ┌──────────┐\n│ SDCRS │ │ Kafka │ │ Fraud Service │ │ Workflow │\n└────┬────┘ └───┬───┘ └───────┬────────┘ └────┬─────┘\n │ │ │ │\n │ save-sdcrs- │ │ │\n │ service │ │ │\n │───────────────►│ │ │\n │ │ │ │\n │ │ fraud-check- │ │\n │ │ request │ │\n │ │──────────────────►│ │\n │ │ │ │\n │ │ │ (async analysis) │\n │ │ │ │\n │ │ fraud-flag- │ │\n │ │ created │ │\n │ │◄──────────────────│ │\n │ │ │ │\n │ │ │ if HIGH/CRITICAL: │\n │ │ │ update workflow │\n │ │ │─────────────────────►\n```\n\n### 8.3 Domain Service Integration Code\n\n```java\n// In SDCRS Service - Create endpoint\n@PostMapping(\"/_create\")\npublic ResponseEntity create(@RequestBody DogReportRequest request) {\n\n // 1. Quick synchronous fraud check (blocking critical issues)\n FraudCheckRequest fraudCheck = buildFraudCheckRequest(request, \"QUICK\");\n FraudCheckResult quickResult = fraudDetectionService.check(fraudCheck);\n\n if (quickResult.getRiskLevel() == RiskLevel.CRITICAL) {\n // Block submission immediately\n throw new FraudDetectedException(quickResult.getFlags());\n }\n\n // 2. Proceed with normal creation\n DogReport report = dogReportService.create(request);\n\n // 3. Trigger async deep fraud check\n FraudCheckRequest deepCheck = buildFraudCheckRequest(request, \"FULL\");\n kafkaTemplate.send(\"fraud-check-request\", report.getId(), deepCheck);\n\n return ResponseEntity.ok(new DogReportResponse(report));\n}\n```\n\n---\n\n## 9. Fraud Flag Lifecycle\n\n```\n ┌─────────────────┐\n │ Rule Engine │\n │ evaluates │\n └────────┬────────┘\n │\n ▼\n ┌──────────────────────────┐\n │ OPEN │\n │ (Flag just created) │\n └────────────┬─────────────┘\n │\n ┌──────────────────────┼──────────────────────┐\n │ │ │\n ▼ ▼ ▼\n ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐\n │ AUTO_RESOLVED │ │ UNDER_REVIEW │ │ ESCALATED │\n │ (Auto-action) │ │ (Human assigned) │ │ (To higher auth) │\n └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘\n │ │ │\n │ ▼ │\n │ ┌──────────────────┐ │\n │ │ Resolution │ │\n │ │ Decision │◄─────────────┘\n │ └────────┬─────────┘\n │ │\n │ ┌──────────────┼──────────────┐\n │ │ │ │\n ▼ ▼ ▼ ▼\n ┌─────────────────┐ ┌─────────────┐ ┌─────────────┐\n │ FALSE_POSITIVE │ │TRUE_POSITIVE│ │INCONCLUSIVE │\n │ (Allow app) │ │ (Take action│ │ (Monitor) │\n └─────────────────┘ └─────────────┘ └─────────────┘\n```\n\n---\n\n## 10. Dashboard & Analytics\n\n### 10.1 Fraud Metrics for DSS\n\n| Metric ID | Name | Aggregation | Drill-down |\n|-----------|------|-------------|------------|\n| `FRAUD_TOTAL_CHECKS` | Total Fraud Checks | Count | Module, Tenant, Date |\n| `FRAUD_FLAG_COUNT` | Flags Raised | Count | Category, Severity, Rule |\n| `FRAUD_FLAG_RATE` | Flag Rate (%) | Percentage | Module, Time Period |\n| `FRAUD_RESOLUTION_TIME` | Avg Resolution Time | Average | Severity, Resolver |\n| `FRAUD_FALSE_POSITIVE_RATE` | False Positive Rate | Percentage | Rule, Category |\n| `FRAUD_CONFIRMED_RATE` | Confirmed Fraud Rate | Percentage | Module, Applicant Type |\n| `FRAUD_BLOCKED_COUNT` | Auto-Blocked Submissions | Count | Rule, Date |\n| `FRAUD_RISK_DISTRIBUTION` | Risk Level Distribution | Distribution | Module, Date |\n\n### 10.2 Real-time Alerts\n\n| Alert | Condition | Channel | Recipients |\n|-------|-----------|---------|------------|\n| Spike in flags | >3x normal rate in 1 hour | SMS, Email | State Admin |\n| Critical flag | Severity = CRITICAL | SMS | District Admin |\n| Collusion detected | Category = COL | Email | State Admin, Fraud Team |\n| High false positive | Rule FP rate >50% | Email | System Admin |\n\n---\n\n## 11. SDCRS-Specific Rules\n\n### 11.1 Module-Specific Configuration\n\n```json\n{\n \"tenantId\": \"ncr\",\n \"moduleName\": \"FRAUD-DETECTION\",\n \"ModuleRules\": {\n \"SDCRS\": [\n {\n \"id\": \"SDCRS-001\",\n \"code\": \"DOG_PHOTO_SELFIE_TIME_GAP\",\n \"name\": \"Photo-Selfie Time Gap Too Large\",\n \"description\": \"Dog photo and selfie timestamps differ by more than 10 minutes\",\n \"category\": \"TMP\",\n \"severity\": \"MEDIUM\",\n \"condition\": {\n \"type\": \"TIMESTAMP_DIFF\",\n \"field1\": \"evidences[purpose=DOG_PHOTO].metadata.timestamp\",\n \"field2\": \"evidences[purpose=SELFIE].metadata.timestamp\",\n \"maxDiffMinutes\": 10\n }\n },\n {\n \"id\": \"SDCRS-002\",\n \"code\": \"AGGRESSIVE_DOG_NO_PHOTO\",\n \"name\": \"Aggressive Dog Without Adequate Evidence\",\n \"description\": \"Dog marked as aggressive but photo quality/angle is poor\",\n \"category\": \"EVD\",\n \"severity\": \"LOW\",\n \"condition\": {\n \"type\": \"CUSTOM\",\n \"expression\": \"additionalData.isAggressive == true && imageQualityScore < 0.5\"\n }\n },\n {\n \"id\": \"SDCRS-003\",\n \"code\": \"GPS_PHOTO_SELFIE_MISMATCH\",\n \"name\": \"GPS Mismatch Between Photos\",\n \"description\": \"Dog photo GPS and selfie GPS are too far apart\",\n \"category\": \"LOC\",\n \"severity\": \"HIGH\",\n \"condition\": {\n \"type\": \"GEO_DISTANCE\",\n \"point1\": \"evidences[purpose=DOG_PHOTO].metadata.gps\",\n \"point2\": \"evidences[purpose=SELFIE].metadata.gps\",\n \"maxDistanceMeters\": 500\n }\n },\n {\n \"id\": \"SDCRS-004\",\n \"code\": \"MULTIPLE_DOGS_SAME_LOCATION\",\n \"name\": \"Multiple Reports from Same Location\",\n \"description\": \"Multiple dog reports from nearly identical GPS within 24 hours\",\n \"category\": \"DUP\",\n \"severity\": \"MEDIUM\",\n \"condition\": {\n \"type\": \"GEO_CLUSTER\",\n \"field\": \"locationData\",\n \"radiusMeters\": 50,\n \"windowHours\": 24,\n \"minCount\": 3\n }\n },\n {\n \"id\": \"SDCRS-005\",\n \"code\": \"SCHOOL_HOURS_VIOLATION\",\n \"name\": \"Report Outside School Hours\",\n \"description\": \"Teacher submitting report outside typical school hours\",\n \"category\": \"TMP\",\n \"severity\": \"LOW\",\n \"condition\": {\n \"type\": \"TIME_WINDOW\",\n \"field\": \"createdTime\",\n \"allowedWindows\": [\n { \"start\": \"07:00\", \"end\": \"18:00\", \"days\": [\"MON\", \"TUE\", \"WED\", \"THU\", \"FRI\"] }\n ]\n },\n \"action\": {\n \"type\": \"FLAG\",\n \"autoReject\": false\n }\n },\n {\n \"id\": \"SDCRS-006\",\n \"code\": \"MONTHLY_CAP_APPROACHING\",\n \"name\": \"Monthly Submission Cap Warning\",\n \"description\": \"Teacher approaching monthly submission limit\",\n \"category\": \"VEL\",\n \"severity\": \"LOW\",\n \"condition\": {\n \"type\": \"AGGREGATE_COUNT\",\n \"field\": \"applicantId\",\n \"periodDays\": 30,\n \"threshold\": 15,\n \"capValue\": 20\n },\n \"action\": {\n \"type\": \"NOTIFY\",\n \"notifyApplicant\": true\n }\n },\n {\n \"id\": \"SDCRS-007\",\n \"code\": \"DEVICE_SHARED_MULTIPLE_TEACHERS\",\n \"name\": \"Device Used by Multiple Teachers\",\n \"description\": \"Same device ID used by different teacher accounts\",\n \"category\": \"COL\",\n \"severity\": \"HIGH\",\n \"condition\": {\n \"type\": \"DEVICE_SHARING\",\n \"field\": \"evidences[*].metadata.deviceId\",\n \"minUniqueUsers\": 2,\n \"windowDays\": 7\n }\n },\n {\n \"id\": \"SDCRS-008\",\n \"code\": \"DOG_PHOTO_DUPLICATE_EXACT\",\n \"name\": \"Exact Duplicate Dog Photo\",\n \"description\": \"Dog photo is byte-for-byte identical to previous submission\",\n \"category\": \"DUP\",\n \"severity\": \"CRITICAL\",\n \"condition\": {\n \"type\": \"HASH_MATCH\",\n \"field\": \"evidences[purpose=DOG_PHOTO].fileStoreId\",\n \"algorithm\": \"SHA256\",\n \"threshold\": 1.0\n },\n \"action\": {\n \"type\": \"AUTO_REJECT\",\n \"rejectionReason\": \"DUPLICATE_EVIDENCE\"\n }\n }\n ]\n }\n}\n```\n\n### 11.2 Integration with SDCRS Workflow\n\nThe Fraud Detection Service integrates with SDCRS workflow transitions:\n\n| Workflow State | Fraud Check | Action |\n|----------------|-------------|--------|\n| `null → PENDING_VALIDATION` | QUICK sync check | Block if CRITICAL |\n| `PENDING_VALIDATION` | FULL async check | Flags stored on report |\n| `PENDING_VERIFICATION` | Verifier sees flags | Manual review |\n| `VERIFIED → ASSIGNED` | No additional check | - |\n| `CAPTURED → RESOLVED` | Capture photo check | Validate resolution evidence |\n\n---\n\n## 12. External AI/ML Service Integration\n\n### 12.1 Architecture for External Validators\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│ FRAUD DETECTION SERVICE │\n├─────────────────────────────────────────────────────────────────────────────────┤\n│ │\n│ ┌──────────────────────────────────────────────────────────────────┐ │\n│ │ RULE EVALUATOR ENGINE │ │\n│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ │\n│ │ │ Internal │ │ External │ │ Hybrid │ │ │\n│ │ │ Validators │ │ Validators │ │ (Internal + External) │ │ │\n│ │ └─────────────┘ └──────┬──────┘ └─────────────────────────┘ │ │\n│ └──────────────────────────┼───────────────────────────────────────┘ │\n│ │ │\n│ ▼ │\n│ ┌──────────────────────────────────────────────────────────────────┐ │\n│ │ EXTERNAL SERVICE ORCHESTRATOR │ │\n│ │ ┌──────────┐ ┌───────────────┐ ┌─────────────┐ ┌──────────┐ │ │\n│ │ │ Circuit │ │ Retry with │ │ Timeout │ │ Response │ │ │\n│ │ │ Breaker │ │ Backoff │ │ Handler │ │ Cache │ │ │\n│ │ └──────────┘ └───────────────┘ └─────────────┘ └──────────┘ │ │\n│ └──────────────────────────┬───────────────────────────────────────┘ │\n│ │ │\n└─────────────────────────────┼────────────────────────────────────────────────────┘\n │\n ┌──────────────────┼──────────────────┬──────────────────┐\n │ │ │ │\n ▼ ▼ ▼ ▼\n ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐\n │ Dog Breed │ │ Object │ │ Face │ │ Anomaly │\n │ Classifier │ │ Detection │ │ Matching │ │ Detection │\n │ (AWS/GCP) │ │ (YOLO API) │ │ (Azure) │ │ (Custom ML) │\n └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘\n```\n\n### 12.2 External Validator Interface\n\n```java\n/**\n * Interface for external validation services (AI/ML models, third-party APIs)\n */\npublic interface ExternalValidator {\n\n /**\n * Unique identifier for this validator\n */\n String getValidatorId();\n\n /**\n * Check if this validator is available (circuit breaker status)\n */\n boolean isAvailable();\n\n /**\n * Synchronous validation (with timeout)\n */\n ExternalValidationResult validate(ExternalValidationRequest request, long timeoutMs);\n\n /**\n * Asynchronous validation (returns CompletableFuture)\n */\n CompletableFuture validateAsync(ExternalValidationRequest request);\n\n /**\n * Get cached result if available\n */\n Optional getCachedResult(String cacheKey);\n}\n\n/**\n * Request payload for external validation\n */\n@Data\npublic class ExternalValidationRequest {\n private String requestId;\n private String applicationId;\n private String validatorId;\n private String inputType; // IMAGE, TEXT, METADATA, COMBINED\n private List fileStoreIds; // Images to analyze\n private Map metadata;\n private Map parameters; // Validator-specific params\n}\n\n/**\n * Response from external validation\n */\n@Data\npublic class ExternalValidationResult {\n private String requestId;\n private String validatorId;\n private boolean success;\n private String errorCode;\n private String errorMessage;\n private double confidence; // 0.0 to 1.0\n private Map predictions;\n private Map metadata;\n private long processingTimeMs;\n private boolean fromCache;\n}\n```\n\n### 12.3 MDMS Configuration for External Services\n\n#### ExternalValidators.json\n\n```json\n{\n \"tenantId\": \"ncr\",\n \"moduleName\": \"FRAUD-DETECTION\",\n \"ExternalValidators\": [\n {\n \"id\": \"DOG_BREED_CLASSIFIER\",\n \"name\": \"Dog Breed Classification AI\",\n \"description\": \"Identifies dog breed from photo using CNN model\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${AI_SERVICE_URL}/api/v1/classify/dog-breed\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"X-API-Key\",\n \"authSecretKey\": \"AI_SERVICE_API_KEY\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[purpose=DOG_PHOTO].fileStoreId\",\n \"imageFormat\": \"BASE64\"\n },\n \"outputMapping\": {\n \"breedField\": \"predictions.breed\",\n \"confidenceField\": \"predictions.confidence\",\n \"isDogField\": \"predictions.is_dog\"\n },\n \"timeout\": {\n \"connectMs\": 5000,\n \"readMs\": 30000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 1000,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 60000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 86400,\n \"keyFields\": [\"imageHash\"]\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"OBJECT_DETECTOR\",\n \"name\": \"Object Detection (YOLO)\",\n \"description\": \"Detects objects in image - verifies dog presence and count\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${YOLO_SERVICE_URL}/detect\",\n \"method\": \"POST\",\n \"authType\": \"BEARER_TOKEN\",\n \"authSecretKey\": \"YOLO_SERVICE_TOKEN\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[purpose=DOG_PHOTO].fileStoreId\",\n \"imageFormat\": \"URL\",\n \"parameters\": {\n \"classes\": [\"dog\", \"person\", \"cat\"],\n \"minConfidence\": 0.5\n }\n },\n \"outputMapping\": {\n \"detectionsField\": \"detections\",\n \"dogCountField\": \"detections[class=dog].count\",\n \"personPresentField\": \"detections[class=person].exists\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 15000\n },\n \"async\": true,\n \"priority\": \"NORMAL\"\n },\n {\n \"id\": \"FACE_MATCHER\",\n \"name\": \"Face Recognition & Matching\",\n \"description\": \"Compares selfie with registered user photo\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${FACE_API_URL}/api/verify\",\n \"method\": \"POST\",\n \"authType\": \"OAUTH2\",\n \"tokenEndpoint\": \"${FACE_API_URL}/oauth/token\",\n \"clientIdKey\": \"FACE_API_CLIENT_ID\",\n \"clientSecretKey\": \"FACE_API_CLIENT_SECRET\"\n },\n \"inputMapping\": {\n \"sourceImage\": \"evidences[purpose=SELFIE].fileStoreId\",\n \"targetImage\": \"applicant.profilePhotoId\",\n \"threshold\": 0.85\n },\n \"outputMapping\": {\n \"matchField\": \"result.match\",\n \"similarityField\": \"result.similarity\",\n \"facesDetectedField\": \"result.faces_detected\"\n },\n \"timeout\": {\n \"connectMs\": 5000,\n \"readMs\": 20000\n }\n },\n {\n \"id\": \"IMAGE_QUALITY_ANALYZER\",\n \"name\": \"Image Quality Assessment\",\n \"description\": \"Analyzes image blur, lighting, resolution\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${IQA_SERVICE_URL}/analyze\",\n \"method\": \"POST\",\n \"authType\": \"BASIC\",\n \"usernameKey\": \"IQA_USERNAME\",\n \"passwordKey\": \"IQA_PASSWORD\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[*].fileStoreId\",\n \"checkTypes\": [\"blur\", \"exposure\", \"resolution\", \"noise\"]\n },\n \"outputMapping\": {\n \"overallQuality\": \"quality.overall_score\",\n \"blurScore\": \"quality.blur_score\",\n \"exposureScore\": \"quality.exposure_score\",\n \"isAcceptable\": \"quality.acceptable\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 10000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 3600\n }\n },\n {\n \"id\": \"ANOMALY_DETECTOR\",\n \"name\": \"Submission Anomaly Detection\",\n \"description\": \"ML model to detect unusual submission patterns\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${ANOMALY_SERVICE_URL}/predict\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"Authorization\",\n \"authSecretKey\": \"ANOMALY_API_KEY\"\n },\n \"inputMapping\": {\n \"features\": {\n \"submissionHour\": \"createdTime.hour\",\n \"dayOfWeek\": \"createdTime.dayOfWeek\",\n \"gpsLat\": \"locationData.latitude\",\n \"gpsLon\": \"locationData.longitude\",\n \"recentSubmissionCount\": \"applicant.recentCount\",\n \"avgTimeBetweenSubmissions\": \"applicant.avgIntervalMinutes\"\n }\n },\n \"outputMapping\": {\n \"anomalyScore\": \"prediction.anomaly_score\",\n \"isAnomaly\": \"prediction.is_anomaly\",\n \"anomalyType\": \"prediction.anomaly_type\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 5000\n }\n }\n ]\n}\n```\n\n### 12.4 AI-Enhanced Fraud Rules\n\n```json\n{\n \"tenantId\": \"ncr\",\n \"moduleName\": \"FRAUD-DETECTION\",\n \"AIFraudRules\": [\n {\n \"id\": \"AI-001\",\n \"code\": \"NO_DOG_IN_PHOTO\",\n \"name\": \"No Dog Detected in Photo\",\n \"description\": \"AI object detection found no dog in the submitted photo\",\n \"category\": \"EVD\",\n \"severity\": \"CRITICAL\",\n \"enabled\": true,\n \"condition\": {\n \"type\": \"EXTERNAL_VALIDATOR\",\n \"validatorId\": \"OBJECT_DETECTOR\",\n \"checkExpression\": \"predictions.dogCount == 0\",\n \"minConfidence\": 0.8\n },\n \"action\": {\n \"type\": \"AUTO_REJECT\",\n \"rejectionReason\": \"NO_DOG_DETECTED\",\n \"notifyApplicant\": true\n }\n },\n {\n \"id\": \"AI-002\",\n \"code\": \"DOG_COUNT_MISMATCH\",\n \"name\": \"Dog Count Mismatch\",\n \"description\": \"Reported dog count doesn't match AI detection\",\n \"category\": \"EVD\",\n \"severity\": \"MEDIUM\",\n \"enabled\": true,\n \"condition\": {\n \"type\": \"EXTERNAL_VALIDATOR\",\n \"validatorId\": \"OBJECT_DETECTOR\",\n \"checkExpression\": \"abs(predictions.dogCount - additionalData.dogCount) > 1\",\n \"minConfidence\": 0.7\n },\n \"action\": {\n \"type\": \"FLAG\",\n \"autoReject\": false,\n \"notifyVerifier\": true\n }\n },\n {\n \"id\": \"AI-003\",\n \"code\": \"SELFIE_FACE_MISMATCH\",\n \"name\": \"Selfie Doesn't Match Profile Photo\",\n \"description\": \"Face in selfie doesn't match registered user photo\",\n \"category\": \"IDN\",\n \"severity\": \"CRITICAL\",\n \"enabled\": true,\n \"condition\": {\n \"type\": \"EXTERNAL_VALIDATOR\",\n \"validatorId\": \"FACE_MATCHER\",\n \"checkExpression\": \"predictions.match == false && predictions.similarity < 0.7\",\n \"minConfidence\": 0.9\n },\n \"action\": {\n \"type\": \"FLAG\",\n \"autoReject\": false,\n \"escalate\": true,\n \"escalateToRole\": \"DISTRICT_ADMIN\"\n }\n },\n {\n \"id\": \"AI-004\",\n \"code\": \"LOW_IMAGE_QUALITY\",\n \"name\": \"Poor Image Quality\",\n \"description\": \"Submitted image is blurry, poorly lit, or low resolution\",\n \"category\": \"EVD\",\n \"severity\": \"LOW\",\n \"enabled\": true,\n \"condition\": {\n \"type\": \"EXTERNAL_VALIDATOR\",\n \"validatorId\": \"IMAGE_QUALITY_ANALYZER\",\n \"checkExpression\": \"predictions.overallQuality < 0.4 || predictions.isAcceptable == false\"\n },\n \"action\": {\n \"type\": \"FLAG\",\n \"autoReject\": false,\n \"notifyApplicant\": true,\n \"message\": \"Please submit a clearer photo\"\n }\n },\n {\n \"id\": \"AI-005\",\n \"code\": \"ANOMALOUS_SUBMISSION_PATTERN\",\n \"name\": \"Anomalous Submission Detected\",\n \"description\": \"ML model detected unusual submission pattern\",\n \"category\": \"VEL\",\n \"severity\": \"HIGH\",\n \"enabled\": true,\n \"condition\": {\n \"type\": \"EXTERNAL_VALIDATOR\",\n \"validatorId\": \"ANOMALY_DETECTOR\",\n \"checkExpression\": \"predictions.isAnomaly == true && predictions.anomalyScore > 0.85\"\n },\n \"action\": {\n \"type\": \"FLAG\",\n \"autoReject\": false,\n \"escalate\": true\n }\n },\n {\n \"id\": \"AI-006\",\n \"code\": \"PHOTO_NOT_DOG_RELATED\",\n \"name\": \"Photo Contains No Animals\",\n \"description\": \"Photo appears to be unrelated to dog sighting\",\n \"category\": \"EVD\",\n \"severity\": \"HIGH\",\n \"enabled\": true,\n \"condition\": {\n \"type\": \"EXTERNAL_VALIDATOR\",\n \"validatorId\": \"OBJECT_DETECTOR\",\n \"checkExpression\": \"predictions.dogCount == 0 && predictions.catCount == 0\",\n \"minConfidence\": 0.85\n },\n \"action\": {\n \"type\": \"FLAG\",\n \"autoReject\": false,\n \"notifyVerifier\": true\n }\n }\n ]\n}\n```\n\n### 12.5 External Service Orchestrator Implementation\n\n```java\n@Service\n@Slf4j\npublic class ExternalServiceOrchestrator {\n\n private final Map validators;\n private final CircuitBreakerRegistry circuitBreakerRegistry;\n private final CacheManager cacheManager;\n private final MeterRegistry meterRegistry;\n\n /**\n * Execute external validation with resilience patterns\n */\n public ExternalValidationResult executeValidation(\n String validatorId,\n ExternalValidationRequest request) {\n\n ExternalValidator validator = validators.get(validatorId);\n if (validator == null) {\n throw new ValidatorNotFoundException(validatorId);\n }\n\n // 1. Check cache first\n String cacheKey = buildCacheKey(validatorId, request);\n Optional cached = validator.getCachedResult(cacheKey);\n if (cached.isPresent()) {\n log.debug(\"Cache hit for validator: {}\", validatorId);\n meterRegistry.counter(\"fraud.external.cache.hit\", \"validator\", validatorId).increment();\n return cached.get();\n }\n\n // 2. Check circuit breaker\n CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(validatorId);\n if (circuitBreaker.getState() == CircuitBreaker.State.OPEN) {\n log.warn(\"Circuit breaker OPEN for validator: {}\", validatorId);\n meterRegistry.counter(\"fraud.external.circuit.open\", \"validator\", validatorId).increment();\n return buildFallbackResult(validatorId, request, \"CIRCUIT_BREAKER_OPEN\");\n }\n\n // 3. Execute with timeout and retry\n try {\n Supplier decorated = CircuitBreaker\n .decorateSupplier(circuitBreaker,\n () -> validator.validate(request, getTimeout(validatorId)));\n\n ExternalValidationResult result = Retry.of(validatorId, getRetryConfig(validatorId))\n .executeSupplier(decorated);\n\n // 4. Cache successful result\n if (result.isSuccess()) {\n cacheResult(cacheKey, result, validatorId);\n }\n\n meterRegistry.timer(\"fraud.external.latency\", \"validator\", validatorId)\n .record(result.getProcessingTimeMs(), TimeUnit.MILLISECONDS);\n\n return result;\n\n } catch (Exception e) {\n log.error(\"External validation failed for {}: {}\", validatorId, e.getMessage());\n meterRegistry.counter(\"fraud.external.error\", \"validator\", validatorId).increment();\n return buildFallbackResult(validatorId, request, e.getMessage());\n }\n }\n\n /**\n * Execute multiple validators in parallel\n */\n public CompletableFuture> executeParallel(\n List validatorIds,\n ExternalValidationRequest request) {\n\n List> futures = validatorIds.stream()\n .map(id -> CompletableFuture.supplyAsync(\n () -> executeValidation(id, request),\n executorService))\n .collect(Collectors.toList());\n\n return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))\n .thenApply(v -> {\n Map results = new HashMap<>();\n for (int i = 0; i < validatorIds.size(); i++) {\n results.put(validatorIds.get(i), futures.get(i).join());\n }\n return results;\n });\n }\n}\n```\n\n### 12.6 Async Processing Flow for External AI Calls\n\n```\n┌──────────────┐ ┌─────────────────────────────────────────────────────────────┐\n│ SDCRS │ │ FRAUD DETECTION SERVICE │\n│ Service │ │ │\n└──────┬───────┘ │ ┌─────────────────┐ ┌──────────────────────────────┐ │\n │ │ │ Quick Check │ │ Deep Check (Async) │ │\n │ _create │ │ (Sync, <500ms) │ │ (Background, <30s) │ │\n │ │ │ │ │ │ │\n ▼ │ │ • Hash match │ │ • AI Object Detection │ │\n ┌─────────┐ │ │ • Velocity │ │ • Face Matching │ │\n │ Request │────────┼──► • GPS boundary │ │ • Anomaly Detection │ │\n └─────────┘ │ │ • Daily limit │ │ • Image Quality Analysis │ │\n │ │ │ │ │ • Breed Classification │ │\n │ │ └────────┬────────┘ └──────────────┬───────────────┘ │\n │ │ │ │ │\n │ │ ▼ │ │\n │ │ ┌──────────┐ │ │\n │ │ │ALLOW or │ │ │\n │◄────────────┼─────│REJECT │ │ │\n │ │ └──────────┘ │ │\n │ │ │ │\n │ │ ┌──────────────▼───────────────┐ │\n │ │ │ Kafka: fraud-check- │ │\n │ │ │ deep-result │ │\n │ │ └──────────────┬───────────────┘ │\n │ │ │ │\n │ └────────────────────────────────────────┼───────────────────┘\n │ │\n │ ▼\n │ ┌───────────────┐\n │ │ If HIGH/ │\n │ │ CRITICAL flag │\n │◄─────────────────────────────────────────────│ Update report │\n │ │ workflow │\n └───────────────┘\n```\n\n### 12.7 Fallback Strategies\n\n| Strategy | When to Use | Behavior |\n|----------|-------------|----------|\n| `SKIP_RULE` | Non-critical AI rules | Proceed without this validation |\n| `USE_CACHED` | Recent cache available | Use last known result (stale) |\n| `DEGRADE_TO_MANUAL` | Critical validations | Flag for human review |\n| `QUEUE_FOR_RETRY` | Temporary outage | Persist and retry later |\n| `APPLY_DEFAULT` | Conservative approach | Assume suspicious (flag) |\n\n### 12.8 Monitoring & Observability\n\n```yaml\n# Prometheus metrics for external AI services\nfraud_external_request_total:\n type: counter\n labels: [validator_id, status]\n help: Total external validation requests\n\nfraud_external_latency_seconds:\n type: histogram\n labels: [validator_id]\n buckets: [0.1, 0.5, 1, 2, 5, 10, 30]\n help: External validation latency\n\nfraud_external_circuit_state:\n type: gauge\n labels: [validator_id]\n values: {CLOSED: 0, HALF_OPEN: 1, OPEN: 2}\n help: Circuit breaker state per validator\n\nfraud_external_cache_hit_ratio:\n type: gauge\n labels: [validator_id]\n help: Cache hit ratio for external validations\n```\n\n---\n\n## 13. Implementation Phases\n\n### Phase 1: Core Infrastructure (Week 1-2)\n- Database schema setup\n- Basic API endpoints\n- Kafka integration\n- MDMS configuration framework\n\n### Phase 2: Rule Engine (Week 3-4)\n- Standard rules (STD-001 to STD-010)\n- Velocity checking\n- Geospatial validation\n- Temporal checks\n\n### Phase 3: Duplicate Detection (Week 5-6)\n- Image hash computation (pHash)\n- Elasticsearch hash index\n- Similarity search\n- Duplicate linking\n\n### Phase 4: SDCRS Integration (Week 7-8)\n- SDCRS-specific rules\n- Workflow integration\n- Verifier UI for flags\n- Notification setup\n\n### Phase 5: Analytics & Monitoring (Week 9-10)\n- DSS dashboard charts\n- Real-time alerts\n- Applicant risk profiles\n- Admin reporting\n\n---\n\n*Document Version: 1.0*\n*Last Updated: December 2025*\n"}}, "design-outputs/04b-no-ccrs/data/ncr/FRAUD-DETECTION/ExternalValidators.json": {"b151dbd41efd0f8d0b83f96bce99b7016568171d": {"t": 1785596337.1934397, "n": 8371, "h": "a86d773213f1b585", "c": "{\n \"tenantId\": \"ncr\",\n \"moduleName\": \"FRAUD-DETECTION\",\n \"ExternalValidators\": [\n {\n \"id\": \"DOG_BREED_CLASSIFIER\",\n \"name\": \"Dog Breed Classification AI\",\n \"description\": \"Identifies dog breed from photo using CNN model\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${AI_SERVICE_URL}/api/v1/classify/dog-breed\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"X-API-Key\",\n \"authSecretKey\": \"AI_SERVICE_API_KEY\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[purpose=DOG_PHOTO].fileStoreId\",\n \"imageFormat\": \"BASE64\"\n },\n \"outputMapping\": {\n \"breedField\": \"predictions.breed\",\n \"confidenceField\": \"predictions.confidence\",\n \"isDogField\": \"predictions.is_dog\"\n },\n \"timeout\": {\n \"connectMs\": 5000,\n \"readMs\": 30000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 1000,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 60000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 86400,\n \"keyFields\": [\"imageHash\"]\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"OBJECT_DETECTOR\",\n \"name\": \"Object Detection (YOLO)\",\n \"description\": \"Detects objects in image - verifies dog presence and count\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${YOLO_SERVICE_URL}/detect\",\n \"method\": \"POST\",\n \"authType\": \"BEARER_TOKEN\",\n \"authSecretKey\": \"YOLO_SERVICE_TOKEN\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[purpose=DOG_PHOTO].fileStoreId\",\n \"imageFormat\": \"URL\",\n \"parameters\": {\n \"classes\": [\"dog\", \"person\", \"cat\"],\n \"minConfidence\": 0.5\n }\n },\n \"outputMapping\": {\n \"detectionsField\": \"detections\",\n \"dogCountField\": \"detections[class=dog].count\",\n \"personPresentField\": \"detections[class=person].exists\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 15000\n },\n \"async\": true,\n \"priority\": \"NORMAL\",\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 3600,\n \"keyFields\": [\"imageHash\", \"parameters\"]\n },\n \"fallback\": {\n \"action\": \"DEGRADE_TO_MANUAL\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"FACE_MATCHER\",\n \"name\": \"Face Recognition & Matching\",\n \"description\": \"Compares selfie with registered user photo\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${FACE_API_URL}/api/verify\",\n \"method\": \"POST\",\n \"authType\": \"OAUTH2\",\n \"tokenEndpoint\": \"${FACE_API_URL}/oauth/token\",\n \"clientIdKey\": \"FACE_API_CLIENT_ID\",\n \"clientSecretKey\": \"FACE_API_CLIENT_SECRET\"\n },\n \"inputMapping\": {\n \"sourceImage\": \"evidences[purpose=SELFIE].fileStoreId\",\n \"targetImage\": \"applicant.profilePhotoId\",\n \"threshold\": 0.85\n },\n \"outputMapping\": {\n \"matchField\": \"result.match\",\n \"similarityField\": \"result.similarity\",\n \"facesDetectedField\": \"result.faces_detected\"\n },\n \"timeout\": {\n \"connectMs\": 5000,\n \"readMs\": 20000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 1000,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 3,\n \"resetTimeoutMs\": 60000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 86400,\n \"keyFields\": [\"sourceImageHash\", \"targetImageHash\"]\n },\n \"fallback\": {\n \"action\": \"DEGRADE_TO_MANUAL\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"IMAGE_QUALITY_ANALYZER\",\n \"name\": \"Image Quality Assessment\",\n \"description\": \"Analyzes image blur, lighting, resolution\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${IQA_SERVICE_URL}/analyze\",\n \"method\": \"POST\",\n \"authType\": \"BASIC\",\n \"usernameKey\": \"IQA_USERNAME\",\n \"passwordKey\": \"IQA_PASSWORD\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[*].fileStoreId\",\n \"checkTypes\": [\"blur\", \"exposure\", \"resolution\", \"noise\"]\n },\n \"outputMapping\": {\n \"overallQuality\": \"quality.overall_score\",\n \"blurScore\": \"quality.blur_score\",\n \"exposureScore\": \"quality.exposure_score\",\n \"isAcceptable\": \"quality.acceptable\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 10000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 1.5\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 3600,\n \"keyFields\": [\"imageHash\"]\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"INFO\"\n }\n },\n {\n \"id\": \"ANOMALY_DETECTOR\",\n \"name\": \"Submission Anomaly Detection\",\n \"description\": \"ML model to detect unusual submission patterns\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${ANOMALY_SERVICE_URL}/predict\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"Authorization\",\n \"authSecretKey\": \"ANOMALY_API_KEY\"\n },\n \"inputMapping\": {\n \"features\": {\n \"submissionHour\": \"createdTime.hour\",\n \"dayOfWeek\": \"createdTime.dayOfWeek\",\n \"gpsLat\": \"locationData.latitude\",\n \"gpsLon\": \"locationData.longitude\",\n \"recentSubmissionCount\": \"applicant.recentCount\",\n \"avgTimeBetweenSubmissions\": \"applicant.avgIntervalMinutes\"\n }\n },\n \"outputMapping\": {\n \"anomalyScore\": \"prediction.anomaly_score\",\n \"isAnomaly\": \"prediction.is_anomaly\",\n \"anomalyType\": \"prediction.anomaly_type\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 5000\n },\n \"retry\": {\n \"maxAttempts\": 1,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 1.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": false\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"GPS_SPOOFING_DETECTOR\",\n \"name\": \"GPS Spoofing Detection\",\n \"description\": \"Detects GPS manipulation using ML signals analysis\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${GPS_VALIDATION_URL}/detect-spoofing\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"X-API-Key\",\n \"authSecretKey\": \"GPS_VALIDATION_API_KEY\"\n },\n \"inputMapping\": {\n \"gpsData\": {\n \"latitude\": \"locationData.latitude\",\n \"longitude\": \"locationData.longitude\",\n \"altitude\": \"locationData.altitude\",\n \"accuracy\": \"locationData.accuracy\",\n \"speed\": \"locationData.speed\",\n \"bearing\": \"locationData.bearing\",\n \"timestamp\": \"locationData.timestamp\"\n },\n \"deviceInfo\": \"evidences[*].metadata.deviceInfo\",\n \"historicalLocations\": \"applicant.recentLocations\"\n },\n \"outputMapping\": {\n \"isSpoofed\": \"prediction.is_spoofed\",\n \"spoofingConfidence\": \"prediction.confidence\",\n \"spoofingMethod\": \"prediction.method\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 8000\n },\n \"retry\": {\n \"maxAttempts\": 1,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 1.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": false\n },\n \"fallback\": {\n \"action\": \"APPLY_DEFAULT\",\n \"defaultValue\": { \"isSpoofed\": false },\n \"logLevel\": \"WARN\"\n }\n }\n ]\n}\n"}}, "design-outputs/04c-ccrs-fork/data/ncr/FRAUD-DETECTION/ExternalValidators.json": {"b151dbd41efd0f8d0b83f96bce99b7016568171d": {"t": 1785596338.6972852, "n": 8371, "h": "a86d773213f1b585", "c": "{\n \"tenantId\": \"ncr\",\n \"moduleName\": \"FRAUD-DETECTION\",\n \"ExternalValidators\": [\n {\n \"id\": \"DOG_BREED_CLASSIFIER\",\n \"name\": \"Dog Breed Classification AI\",\n \"description\": \"Identifies dog breed from photo using CNN model\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${AI_SERVICE_URL}/api/v1/classify/dog-breed\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"X-API-Key\",\n \"authSecretKey\": \"AI_SERVICE_API_KEY\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[purpose=DOG_PHOTO].fileStoreId\",\n \"imageFormat\": \"BASE64\"\n },\n \"outputMapping\": {\n \"breedField\": \"predictions.breed\",\n \"confidenceField\": \"predictions.confidence\",\n \"isDogField\": \"predictions.is_dog\"\n },\n \"timeout\": {\n \"connectMs\": 5000,\n \"readMs\": 30000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 1000,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 60000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 86400,\n \"keyFields\": [\"imageHash\"]\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"OBJECT_DETECTOR\",\n \"name\": \"Object Detection (YOLO)\",\n \"description\": \"Detects objects in image - verifies dog presence and count\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${YOLO_SERVICE_URL}/detect\",\n \"method\": \"POST\",\n \"authType\": \"BEARER_TOKEN\",\n \"authSecretKey\": \"YOLO_SERVICE_TOKEN\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[purpose=DOG_PHOTO].fileStoreId\",\n \"imageFormat\": \"URL\",\n \"parameters\": {\n \"classes\": [\"dog\", \"person\", \"cat\"],\n \"minConfidence\": 0.5\n }\n },\n \"outputMapping\": {\n \"detectionsField\": \"detections\",\n \"dogCountField\": \"detections[class=dog].count\",\n \"personPresentField\": \"detections[class=person].exists\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 15000\n },\n \"async\": true,\n \"priority\": \"NORMAL\",\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 3600,\n \"keyFields\": [\"imageHash\", \"parameters\"]\n },\n \"fallback\": {\n \"action\": \"DEGRADE_TO_MANUAL\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"FACE_MATCHER\",\n \"name\": \"Face Recognition & Matching\",\n \"description\": \"Compares selfie with registered user photo\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${FACE_API_URL}/api/verify\",\n \"method\": \"POST\",\n \"authType\": \"OAUTH2\",\n \"tokenEndpoint\": \"${FACE_API_URL}/oauth/token\",\n \"clientIdKey\": \"FACE_API_CLIENT_ID\",\n \"clientSecretKey\": \"FACE_API_CLIENT_SECRET\"\n },\n \"inputMapping\": {\n \"sourceImage\": \"evidences[purpose=SELFIE].fileStoreId\",\n \"targetImage\": \"applicant.profilePhotoId\",\n \"threshold\": 0.85\n },\n \"outputMapping\": {\n \"matchField\": \"result.match\",\n \"similarityField\": \"result.similarity\",\n \"facesDetectedField\": \"result.faces_detected\"\n },\n \"timeout\": {\n \"connectMs\": 5000,\n \"readMs\": 20000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 1000,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 3,\n \"resetTimeoutMs\": 60000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 86400,\n \"keyFields\": [\"sourceImageHash\", \"targetImageHash\"]\n },\n \"fallback\": {\n \"action\": \"DEGRADE_TO_MANUAL\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"IMAGE_QUALITY_ANALYZER\",\n \"name\": \"Image Quality Assessment\",\n \"description\": \"Analyzes image blur, lighting, resolution\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${IQA_SERVICE_URL}/analyze\",\n \"method\": \"POST\",\n \"authType\": \"BASIC\",\n \"usernameKey\": \"IQA_USERNAME\",\n \"passwordKey\": \"IQA_PASSWORD\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[*].fileStoreId\",\n \"checkTypes\": [\"blur\", \"exposure\", \"resolution\", \"noise\"]\n },\n \"outputMapping\": {\n \"overallQuality\": \"quality.overall_score\",\n \"blurScore\": \"quality.blur_score\",\n \"exposureScore\": \"quality.exposure_score\",\n \"isAcceptable\": \"quality.acceptable\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 10000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 1.5\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 3600,\n \"keyFields\": [\"imageHash\"]\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"INFO\"\n }\n },\n {\n \"id\": \"ANOMALY_DETECTOR\",\n \"name\": \"Submission Anomaly Detection\",\n \"description\": \"ML model to detect unusual submission patterns\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${ANOMALY_SERVICE_URL}/predict\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"Authorization\",\n \"authSecretKey\": \"ANOMALY_API_KEY\"\n },\n \"inputMapping\": {\n \"features\": {\n \"submissionHour\": \"createdTime.hour\",\n \"dayOfWeek\": \"createdTime.dayOfWeek\",\n \"gpsLat\": \"locationData.latitude\",\n \"gpsLon\": \"locationData.longitude\",\n \"recentSubmissionCount\": \"applicant.recentCount\",\n \"avgTimeBetweenSubmissions\": \"applicant.avgIntervalMinutes\"\n }\n },\n \"outputMapping\": {\n \"anomalyScore\": \"prediction.anomaly_score\",\n \"isAnomaly\": \"prediction.is_anomaly\",\n \"anomalyType\": \"prediction.anomaly_type\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 5000\n },\n \"retry\": {\n \"maxAttempts\": 1,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 1.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": false\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"GPS_SPOOFING_DETECTOR\",\n \"name\": \"GPS Spoofing Detection\",\n \"description\": \"Detects GPS manipulation using ML signals analysis\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${GPS_VALIDATION_URL}/detect-spoofing\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"X-API-Key\",\n \"authSecretKey\": \"GPS_VALIDATION_API_KEY\"\n },\n \"inputMapping\": {\n \"gpsData\": {\n \"latitude\": \"locationData.latitude\",\n \"longitude\": \"locationData.longitude\",\n \"altitude\": \"locationData.altitude\",\n \"accuracy\": \"locationData.accuracy\",\n \"speed\": \"locationData.speed\",\n \"bearing\": \"locationData.bearing\",\n \"timestamp\": \"locationData.timestamp\"\n },\n \"deviceInfo\": \"evidences[*].metadata.deviceInfo\",\n \"historicalLocations\": \"applicant.recentLocations\"\n },\n \"outputMapping\": {\n \"isSpoofed\": \"prediction.is_spoofed\",\n \"spoofingConfidence\": \"prediction.confidence\",\n \"spoofingMethod\": \"prediction.method\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 8000\n },\n \"retry\": {\n \"maxAttempts\": 1,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 1.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": false\n },\n \"fallback\": {\n \"action\": \"APPLY_DEFAULT\",\n \"defaultValue\": { \"isSpoofed\": false },\n \"logLevel\": \"WARN\"\n }\n }\n ]\n}\n"}}, "design-outputs/04a-ccrs/data/ncr/FRAUD-DETECTION/ExternalValidators.json": {"b151dbd41efd0f8d0b83f96bce99b7016568171d": {"t": 1785596339.159738, "n": 8371, "h": "a86d773213f1b585", "c": "{\n \"tenantId\": \"ncr\",\n \"moduleName\": \"FRAUD-DETECTION\",\n \"ExternalValidators\": [\n {\n \"id\": \"DOG_BREED_CLASSIFIER\",\n \"name\": \"Dog Breed Classification AI\",\n \"description\": \"Identifies dog breed from photo using CNN model\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${AI_SERVICE_URL}/api/v1/classify/dog-breed\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"X-API-Key\",\n \"authSecretKey\": \"AI_SERVICE_API_KEY\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[purpose=DOG_PHOTO].fileStoreId\",\n \"imageFormat\": \"BASE64\"\n },\n \"outputMapping\": {\n \"breedField\": \"predictions.breed\",\n \"confidenceField\": \"predictions.confidence\",\n \"isDogField\": \"predictions.is_dog\"\n },\n \"timeout\": {\n \"connectMs\": 5000,\n \"readMs\": 30000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 1000,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 60000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 86400,\n \"keyFields\": [\"imageHash\"]\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"OBJECT_DETECTOR\",\n \"name\": \"Object Detection (YOLO)\",\n \"description\": \"Detects objects in image - verifies dog presence and count\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${YOLO_SERVICE_URL}/detect\",\n \"method\": \"POST\",\n \"authType\": \"BEARER_TOKEN\",\n \"authSecretKey\": \"YOLO_SERVICE_TOKEN\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[purpose=DOG_PHOTO].fileStoreId\",\n \"imageFormat\": \"URL\",\n \"parameters\": {\n \"classes\": [\"dog\", \"person\", \"cat\"],\n \"minConfidence\": 0.5\n }\n },\n \"outputMapping\": {\n \"detectionsField\": \"detections\",\n \"dogCountField\": \"detections[class=dog].count\",\n \"personPresentField\": \"detections[class=person].exists\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 15000\n },\n \"async\": true,\n \"priority\": \"NORMAL\",\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 3600,\n \"keyFields\": [\"imageHash\", \"parameters\"]\n },\n \"fallback\": {\n \"action\": \"DEGRADE_TO_MANUAL\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"FACE_MATCHER\",\n \"name\": \"Face Recognition & Matching\",\n \"description\": \"Compares selfie with registered user photo\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${FACE_API_URL}/api/verify\",\n \"method\": \"POST\",\n \"authType\": \"OAUTH2\",\n \"tokenEndpoint\": \"${FACE_API_URL}/oauth/token\",\n \"clientIdKey\": \"FACE_API_CLIENT_ID\",\n \"clientSecretKey\": \"FACE_API_CLIENT_SECRET\"\n },\n \"inputMapping\": {\n \"sourceImage\": \"evidences[purpose=SELFIE].fileStoreId\",\n \"targetImage\": \"applicant.profilePhotoId\",\n \"threshold\": 0.85\n },\n \"outputMapping\": {\n \"matchField\": \"result.match\",\n \"similarityField\": \"result.similarity\",\n \"facesDetectedField\": \"result.faces_detected\"\n },\n \"timeout\": {\n \"connectMs\": 5000,\n \"readMs\": 20000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 1000,\n \"backoffMultiplier\": 2.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 3,\n \"resetTimeoutMs\": 60000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 86400,\n \"keyFields\": [\"sourceImageHash\", \"targetImageHash\"]\n },\n \"fallback\": {\n \"action\": \"DEGRADE_TO_MANUAL\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"IMAGE_QUALITY_ANALYZER\",\n \"name\": \"Image Quality Assessment\",\n \"description\": \"Analyzes image blur, lighting, resolution\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${IQA_SERVICE_URL}/analyze\",\n \"method\": \"POST\",\n \"authType\": \"BASIC\",\n \"usernameKey\": \"IQA_USERNAME\",\n \"passwordKey\": \"IQA_PASSWORD\"\n },\n \"inputMapping\": {\n \"imageField\": \"evidences[*].fileStoreId\",\n \"checkTypes\": [\"blur\", \"exposure\", \"resolution\", \"noise\"]\n },\n \"outputMapping\": {\n \"overallQuality\": \"quality.overall_score\",\n \"blurScore\": \"quality.blur_score\",\n \"exposureScore\": \"quality.exposure_score\",\n \"isAcceptable\": \"quality.acceptable\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 10000\n },\n \"retry\": {\n \"maxAttempts\": 2,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 1.5\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": true,\n \"ttlSeconds\": 3600,\n \"keyFields\": [\"imageHash\"]\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"INFO\"\n }\n },\n {\n \"id\": \"ANOMALY_DETECTOR\",\n \"name\": \"Submission Anomaly Detection\",\n \"description\": \"ML model to detect unusual submission patterns\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${ANOMALY_SERVICE_URL}/predict\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"Authorization\",\n \"authSecretKey\": \"ANOMALY_API_KEY\"\n },\n \"inputMapping\": {\n \"features\": {\n \"submissionHour\": \"createdTime.hour\",\n \"dayOfWeek\": \"createdTime.dayOfWeek\",\n \"gpsLat\": \"locationData.latitude\",\n \"gpsLon\": \"locationData.longitude\",\n \"recentSubmissionCount\": \"applicant.recentCount\",\n \"avgTimeBetweenSubmissions\": \"applicant.avgIntervalMinutes\"\n }\n },\n \"outputMapping\": {\n \"anomalyScore\": \"prediction.anomaly_score\",\n \"isAnomaly\": \"prediction.is_anomaly\",\n \"anomalyType\": \"prediction.anomaly_type\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 5000\n },\n \"retry\": {\n \"maxAttempts\": 1,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 1.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": false\n },\n \"fallback\": {\n \"action\": \"SKIP_RULE\",\n \"logLevel\": \"WARN\"\n }\n },\n {\n \"id\": \"GPS_SPOOFING_DETECTOR\",\n \"name\": \"GPS Spoofing Detection\",\n \"description\": \"Detects GPS manipulation using ML signals analysis\",\n \"type\": \"AI_MODEL\",\n \"enabled\": true,\n \"endpoint\": {\n \"url\": \"${GPS_VALIDATION_URL}/detect-spoofing\",\n \"method\": \"POST\",\n \"authType\": \"API_KEY\",\n \"authHeader\": \"X-API-Key\",\n \"authSecretKey\": \"GPS_VALIDATION_API_KEY\"\n },\n \"inputMapping\": {\n \"gpsData\": {\n \"latitude\": \"locationData.latitude\",\n \"longitude\": \"locationData.longitude\",\n \"altitude\": \"locationData.altitude\",\n \"accuracy\": \"locationData.accuracy\",\n \"speed\": \"locationData.speed\",\n \"bearing\": \"locationData.bearing\",\n \"timestamp\": \"locationData.timestamp\"\n },\n \"deviceInfo\": \"evidences[*].metadata.deviceInfo\",\n \"historicalLocations\": \"applicant.recentLocations\"\n },\n \"outputMapping\": {\n \"isSpoofed\": \"prediction.is_spoofed\",\n \"spoofingConfidence\": \"prediction.confidence\",\n \"spoofingMethod\": \"prediction.method\"\n },\n \"timeout\": {\n \"connectMs\": 3000,\n \"readMs\": 8000\n },\n \"retry\": {\n \"maxAttempts\": 1,\n \"backoffMs\": 500,\n \"backoffMultiplier\": 1.0\n },\n \"circuitBreaker\": {\n \"failureThreshold\": 5,\n \"resetTimeoutMs\": 30000\n },\n \"cache\": {\n \"enabled\": false\n },\n \"fallback\": {\n \"action\": \"APPLY_DEFAULT\",\n \"defaultValue\": { \"isSpoofed\": false },\n \"logLevel\": \"WARN\"\n }\n }\n ]\n}\n"}}}}