- Bucket Name:
spartan-ai-test-images-1767651941 - Region:
us-east-1 - Access: Public read (via bucket policy)
-
Anthony-FL.jpeg
https://spartan-ai-test-images-1767651941.s3.us-east-1.amazonaws.com/Anthony-FL.jpeg -
ArmedRobbery-MI.webp
https://spartan-ai-test-images-1767651941.s3.us-east-1.amazonaws.com/ArmedRobbery-MI.webp -
ASSAULT-NC2.webp
https://spartan-ai-test-images-1767651941.s3.us-east-1.amazonaws.com/ASSAULT-NC2.webp -
Burglary-OR.webp
https://spartan-ai-test-images-1767651941.s3.us-east-1.amazonaws.com/Burglary-OR.webp
# Test single image
./test-scan.sh "https://spartan-ai-test-images-1767651941.s3.us-east-1.amazonaws.com/Anthony-FL.jpeg"
# Test all images
for url in \
"https://spartan-ai-test-images-1767651941.s3.us-east-1.amazonaws.com/Anthony-FL.jpeg" \
"https://spartan-ai-test-images-1767651941.s3.us-east-1.amazonaws.com/ArmedRobbery-MI.webp" \
"https://spartan-ai-test-images-1767651941.s3.us-east-1.amazonaws.com/ASSAULT-NC2.webp" \
"https://spartan-ai-test-images-1767651941.s3.us-east-1.amazonaws.com/Burglary-OR.webp"; do
echo "Testing: $url"
./test-scan.sh "$url"
sleep 5
done- File:
upload-test-images-to-s3.sh - Usage:
./upload-test-images-to-s3.sh [bucket-name] - Creates bucket, sets public access, uploads all 4 images
1. Scan Handler
└─> Calls Captis API with async=true
└─> Gets immediate response with captisId
└─> If timedOutFlag=true → Triggers EventBridge → Poll Handler
└─> If timedOutFlag=false → Sets status=COMPLETED (but may not have results)
2. Poll Handler (if triggered)
└─> Polls Captis API until status=COMPLETED
└─> Extracts: topScore = result.matches[0].score
└─> Calculates: matchLevel based on thresholds
└─> Updates DynamoDB with topScore and matchLevel
File: functions/poll-handler/index.ts
Key Code:
// Line 40: Extract topScore from first match
const topScore = result.matches?.[0]?.score || 0;
// Lines 41-47: Calculate matchLevel
const matchLevel = topScore > thresholds.highThreshold
? 'HIGH'
: topScore > thresholds.mediumThreshold
? 'MEDIUM'
: topScore > thresholds.lowThreshold
? 'LOW'
: undefined;
// Lines 49-64: Update DynamoDB
UpdateExpression: 'SET #status = :status, topScore = :score, viewMatchesUrl = :url, updatedAt = :updated'interface CaptisResolveResponse {
id: string;
status: "COMPLETED" | "PROCESSING" | "PENDING";
matches?: Array<{
id: string;
score: number; // ← This becomes topScore
scoreLevel: 'HIGH' | 'MEDIUM' | 'LOW';
subject: {
id: string;
name: string;
...
};
}>;
viewMatchesUrl?: string;
timedOutFlag?: boolean; // ← Determines if polling needed
}Scan Handler Logic (lines 427, 476, 527):
// Line 427: Sets status based on timedOutFlag
status: captisResponse.timedOutFlag ? 'PENDING' : 'COMPLETED'
// Line 476: Only triggers poll handler if timedOutFlag is true
if (captisResponse.timedOutFlag) {
// Trigger EventBridge → Poll Handler
}
// Line 527: Tries to get topScore from immediate response
topScore: captisResponse.matches?.[0]?.score-
If Captis returns synchronously (no
timedOutFlag):- Status is set to
COMPLETEDimmediately - Poll handler is NOT triggered (only triggered if
timedOutFlag=true) topScoreis extracted from immediate response, but if Captis hasn't finished processing,matchesarray might be empty- Result:
topScore = undefined,matchLevel = null
- Status is set to
-
If Captis returns with
timedOutFlag=true:- Status is set to
PENDING - Poll handler IS triggered via EventBridge
- Poll handler extracts
topScoreandmatchLevelcorrectly - Result: ✅ Works correctly
- Status is set to
Scan ID: f3c6125b-4b95-4d39-863c-9b79837b56ca
Status: COMPLETED
topScore: null
matchLevel: null
timedOutFlag: null
pollingRequired: null
Analysis:
- Captis returned synchronously (no
timedOutFlag) - Poll handler was NOT triggered
topScoreandmatchLevelare null because they were never extracted
Modify scan handler to always trigger poll handler, even if timedOutFlag is false:
// Always trigger poll handler to ensure results are parsed
if (!captisResponse.timedOutFlag && captisResponse.status === 'COMPLETED') {
// Still trigger poll handler to extract topScore/matchLevel
// This ensures results are properly parsed even for synchronous responses
}If Captis returns synchronously with results, extract them in scan handler:
// If results are available immediately, extract them
if (captisResponse.matches && captisResponse.matches.length > 0) {
const topScore = captisResponse.matches[0].score;
const matchLevel = calculateMatchLevel(topScore, thresholds);
// Store in DynamoDB
}Only set status to COMPLETED if results are available:
// Only set COMPLETED if we have results or timedOutFlag is false
const hasResults = captisResponse.matches && captisResponse.matches.length > 0;
status: (captisResponse.timedOutFlag || !hasResults) ? 'PENDING' : 'COMPLETED'- S3 Upload: All 4 images uploaded, URLs generated
- Parsing Documentation: Complete explanation of how results are parsed
- Issue Identified: Why topScore/matchLevel are null
- S3 URLs work ✅ (tested successfully)
- Poll handler extracts topScore/matchLevel ✅ (when triggered)
- Issue: Poll handler not triggered for synchronous Captis responses
- Result: topScore/matchLevel are null for synchronous responses
- Fix scan handler to always trigger poll handler (or extract results immediately)
- Test with S3 URLs to verify results are parsed correctly
- Monitor poll handler logs to ensure it's being triggered
Files Created:
upload-test-images-to-s3.sh- Upload scriptCAPTIS_RESULTS_PARSING.md- Detailed parsing documentationS3_IMAGE_URLS.txt- Quick reference for URLsS3_AND_PARSING_SUMMARY.md- This summary