Parent
#30 — Unify OCR provider status mapping
Summary
Map Anthropic Claude Message Batches API lifecycle states to votecatcher MatchingStatus values.
Claude Message Batches API Lifecycle
Per the Claude Batch Processing docs:
- 50% cost discount vs real-time inference
- Up to 100,000 requests per batch or 256 MB, whichever is reached first
- Most batches complete within 1 hour; snip 24h max processing window before expiry
- Results available for 29 days after creation
- Supports all active models and all Messages API features (vision, tool use, system messages, multi-turn)
- Prompt caching supported (discounts stack with batch discount)
Batch Processing Status Flow
processing_status transitions: in_progress → ended (all requests finished or 24h elapsed).
Status Values
Claude Batch processing_status |
Description |
Proposed Mapping |
in_progress |
Batch is being processed |
OCR_IN_PROGRESS |
ended |
All requests finished (or 24h expired) |
OCR_COMPLETED |
Per-Request Result Types (after ended)
| Result Type |
Description |
Proposed Mapping |
succeeded |
Request completed successfully |
OCR_COMPLETED |
errored |
Request failed (invalid request or server error) |
OCR_FAILED |
canceled |
Batch cancelled before this request processed |
OCR_CANCELLED |
expired |
24h window elapsed before request processed |
OCR_TIMED_OUT |
Key API Operations
- Create:
client.messages.batches.create(requests=[...]) — list of {custom_id, params} objects
- Poll:
client.messages.batches.retrieve(batch_id) → check .processing_status
- Results: Stream results via
client.messages.batches.results(batch_id) — .jsonl format
- Cancel:
client.messages.batches.cancel(batch_id) — status transitions to canceling then ended
- List:
client.messages.batches.list(limit=20) — auto-paginating
Request Format
{
"custom_id": "my-request-1",
"params": {
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "..."}]
}
}
Result Format
Results streamed as .jsonl — order not guaranteed, match by custom_id:
{"custom_id":"request-2","result":{"type":"succeeded","message":{...}}}
{"custom_id":"request-1","result":{"type":"errored","error":{"type":"invalid_request_error",...}}}
Expiration Behavior
- Batches expire if not completed within 24 hours
- Unprocessed requests return result type
expired
- Completed requests within an expired batch are still billed
Prompt Caching
- Supports
cache_control: {"type": "ephemeral"} in batch requests
- Cache hits are best-effort due to concurrent async processing
- Typical hit rates: 30%–98% depending on traffic patterns
- Consider 1-hour cache duration for better hit rates with batches
Proposed Implementation
Since Claude uses a two-level status model (batch-level processing_status + per-request result types), the mapping needs to handle both:
- Batch-level polling:
in_progress → OCR_IN_PROGRESS, ended → check request_counts for final status
- Per-request results: Map each result type to the appropriate
MatchingStatus
- Cancel flow:
canceling intermediate state → OCR_CANCELLED
Status Mapping
STATUS_MAPPING: dict[str, MatchingStatus] = {
"in_progress": MatchingStatus.OCR_IN_PROGRESS,
"canceling": MatchingStatus.OCR_CANCELLED,
"ended": MatchingStatus.OCR_COMPLETED, # check request_counts for failures
}
RESULT_TYPE_MAPPING: dict[str, MatchingStatus] = {
"succeeded": MatchingStatus.OCR_COMPLETED,
"errored": MatchingStatus.OCR_FAILED,
"canceled": MatchingStatus.OCR_CANCELLED,
"expired": MatchingStatus.OCR_TIMED_OUT,
}
Tasks
References
Parent
#30 — Unify OCR provider status mapping
Summary
Map Anthropic Claude Message Batches API lifecycle states to votecatcher
MatchingStatusvalues.Claude Message Batches API Lifecycle
Per the Claude Batch Processing docs:
Batch Processing Status Flow
processing_statustransitions:in_progress→ended(all requests finished or 24h elapsed).Status Values
processing_statusin_progressOCR_IN_PROGRESSendedOCR_COMPLETEDPer-Request Result Types (after
ended)succeededOCR_COMPLETEDerroredOCR_FAILEDcanceledOCR_CANCELLEDexpiredOCR_TIMED_OUTKey API Operations
client.messages.batches.create(requests=[...])— list of{custom_id, params}objectsclient.messages.batches.retrieve(batch_id)→ check.processing_statusclient.messages.batches.results(batch_id)—.jsonlformatclient.messages.batches.cancel(batch_id)— status transitions tocancelingthenendedclient.messages.batches.list(limit=20)— auto-paginatingRequest Format
{ "custom_id": "my-request-1", "params": { "model": "claude-sonnet-4-20250514", "max_tokens": 1024, "messages": [{"role": "user", "content": "..."}] } }Result Format
Results streamed as
.jsonl— order not guaranteed, match bycustom_id:{"custom_id":"request-2","result":{"type":"succeeded","message":{...}}} {"custom_id":"request-1","result":{"type":"errored","error":{"type":"invalid_request_error",...}}}Expiration Behavior
expiredPrompt Caching
cache_control: {"type": "ephemeral"}in batch requestsProposed Implementation
Since Claude uses a two-level status model (batch-level
processing_status+ per-request result types), the mapping needs to handle both:in_progress→OCR_IN_PROGRESS,ended→ checkrequest_countsfor final statusMatchingStatuscancelingintermediate state →OCR_CANCELLEDStatus Mapping
Tasks
STATUS_MAPPINGfor batch-levelprocessing_statusvaluesRESULT_TYPE_MAPPINGfor per-request result typesOcrStatusMapper.register("claude", STATUS_MAPPING)once feat: unify OCR provider status mapping with generic fallback #30 mapper is builtSTATUS_MAPPING, result parsing usesRESULT_TYPE_MAPPINGrequest_countsfrom batch object for progress tracking duringin_progresserroredresult type — distinguishinvalid_request_error(fix needed) vs server error (retry)cancelingintermediate stateReferences