diff --git a/.env.example b/.env.example index 02556bb..58821a8 100644 --- a/.env.example +++ b/.env.example @@ -29,6 +29,8 @@ DAILY_TOKEN_LIMIT=100000 LOG_EACH_QUERY=false RESERVATION_TTL_SECONDS=600 EMBEDDING_MODEL=text-embedding-3-small +MAX_QUERY_DOCUMENTS=10 +MAX_QUERY_TOTAL_PAGES=100 # ===== Ingestion Limits ===== MAX_FILE_SIZE_BYTES=10485760 @@ -39,6 +41,9 @@ EMBEDDING_BATCH_SIZE=32 OPENAI_EMBEDDING_TIMEOUT_SECONDS=300 INGEST_EXTRACT_JOB_TIMEOUT_SECONDS=900 INGEST_INDEX_JOB_TIMEOUT_SECONDS=1800 +INGEST_STALE_UPLOADED_SECONDS=3600 +INGEST_STALE_EXTRACTING_SECONDS=1800 +INGEST_STALE_INDEXING_SECONDS=3600 # ===== Client (React) ===== VITE_API_URL=http://localhost:8000 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..770ac2d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + - package-ecosystem: "docker" + directory: "/.github/trivy" + schedule: + interval: "weekly" + day: "monday" + time: "06:00" + timezone: "America/Los_Angeles" + commit-message: + prefix: "chore" + include: "scope" + labels: + - "dependencies" + - "security" + - "trivy" + open-pull-requests-limit: 5 diff --git a/.github/trivy/Dockerfile b/.github/trivy/Dockerfile new file mode 100644 index 0000000..2028c59 --- /dev/null +++ b/.github/trivy/Dockerfile @@ -0,0 +1 @@ +FROM aquasec/trivy:0.70.0 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6df6ce3..ac51b8b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -23,32 +23,28 @@ jobs: trivy_scan: name: Trivy Vulnerability Scan runs-on: ubuntu-latest - env: - TRIVY_REPORT_FORMAT: table - TRIVY_SCAN_TYPE: fs - TRIVY_SCAN_PATH: . - TRIVY_EXIT_CODE: '1' - TRIVY_VULN_TYPE: os,library - TRIVY_SEVERITY: CRITICAL,HIGH steps: - uses: actions/checkout@v4 - name: Create report directory run: mkdir -p trivy-reports - + + - name: Build pinned Trivy runner + run: docker build -t local-trivy -f .github/trivy/Dockerfile .github/trivy + - name: Run Trivy FS Scan - uses: aquasecurity/trivy-action@v0.36.0 - with: - scan-type: 'fs' - scan-ref: '.' - version: 'v0.70.0' - scanners: 'vuln,misconfig,secret,license' - ignore-unfixed: true - format: 'table' - exit-code: '1' - output: 'trivy-reports/trivy_scan_report.txt' - vuln-type: 'os,library' - severity: 'CRITICAL,HIGH' + run: | + docker run --rm \ + -v "${{ github.workspace }}:/workspace" \ + -w /workspace \ + local-trivy fs . \ + --scanners vuln,misconfig,secret,license \ + --ignore-unfixed \ + --format table \ + --exit-code 1 \ + --output trivy-reports/trivy_scan_report.txt \ + --vuln-type os,library \ + --severity CRITICAL,HIGH - name: Upload Trivy Report uses: actions/upload-artifact@v4 diff --git a/artifacts/ingestion-load/ingestion-load-valid-10-20260608T081032Z.json b/artifacts/ingestion-load/ingestion-load-valid-10-20260608T081032Z.json new file mode 100644 index 0000000..be56bf5 --- /dev/null +++ b/artifacts/ingestion-load/ingestion-load-valid-10-20260608T081032Z.json @@ -0,0 +1,643 @@ +{ + "generated_at": "2026-06-08T08:09:28.032939+00:00", + "api_base": "http://localhost:8000", + "scenario": "valid", + "requested_count": 10, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "prepare": { + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "bucket": "documents", + "expires_in": 600, + "accepted_count": 10, + "rejected_count": 0, + "items": [ + { + "index": 0, + "filename": "load-valid-001.pdf", + "client_file_id": "load-valid-001", + "status": "prepared", + "document_id": "db415368-a778-471d-9124-19b37289e59e", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/db415368-a778-471d-9124-19b37289e59e/load-valid-001.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 1, + "filename": "load-valid-002.pdf", + "client_file_id": "load-valid-002", + "status": "prepared", + "document_id": "f71d14a2-2517-49e9-b0b0-8cc90d11c793", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/f71d14a2-2517-49e9-b0b0-8cc90d11c793/load-valid-002.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 2, + "filename": "load-valid-003.pdf", + "client_file_id": "load-valid-003", + "status": "prepared", + "document_id": "5c049412-054f-42e1-9e13-023823e8aad2", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/5c049412-054f-42e1-9e13-023823e8aad2/load-valid-003.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 3, + "filename": "load-valid-004.pdf", + "client_file_id": "load-valid-004", + "status": "prepared", + "document_id": "9232b00d-0b56-4eca-a273-e569546850c4", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/9232b00d-0b56-4eca-a273-e569546850c4/load-valid-004.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 4, + "filename": "load-valid-005.pdf", + "client_file_id": "load-valid-005", + "status": "prepared", + "document_id": "dd32e7ab-cd98-4324-8787-5750afe6f34e", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/dd32e7ab-cd98-4324-8787-5750afe6f34e/load-valid-005.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 5, + "filename": "load-valid-006.pdf", + "client_file_id": "load-valid-006", + "status": "prepared", + "document_id": "f5e94ccb-2c27-4772-8c19-652dad8bbb73", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/f5e94ccb-2c27-4772-8c19-652dad8bbb73/load-valid-006.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 6, + "filename": "load-valid-007.pdf", + "client_file_id": "load-valid-007", + "status": "prepared", + "document_id": "d91d171e-bb4e-42b3-8d5c-3a7135fd8134", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/d91d171e-bb4e-42b3-8d5c-3a7135fd8134/load-valid-007.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 7, + "filename": "load-valid-008.pdf", + "client_file_id": "load-valid-008", + "status": "prepared", + "document_id": "2a79108e-dc8e-41f6-b08f-9932d2836602", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/2a79108e-dc8e-41f6-b08f-9932d2836602/load-valid-008.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 8, + "filename": "load-valid-009.pdf", + "client_file_id": "load-valid-009", + "status": "prepared", + "document_id": "b9073464-28ec-476a-bac8-0971193d9f2f", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/b9073464-28ec-476a-bac8-0971193d9f2f/load-valid-009.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 9, + "filename": "load-valid-010.pdf", + "client_file_id": "load-valid-010", + "status": "prepared", + "document_id": "148116bf-a6c2-4935-a2fa-48ad7a0c31ee", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/148116bf-a6c2-4935-a2fa-48ad7a0c31ee/load-valid-010.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + } + ] + }, + "complete": { + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "accepted_count": 10, + "failed_count": 0, + "items": [ + { + "index": 0, + "document_id": "db415368-a778-471d-9124-19b37289e59e", + "status": "uploaded", + "job_id": "1ad608cb-fbb5-4880-aff9-fd4aa6e172a1", + "error": null + }, + { + "index": 1, + "document_id": "f71d14a2-2517-49e9-b0b0-8cc90d11c793", + "status": "uploaded", + "job_id": "71dd15ca-f398-4581-b4ef-7715e437325e", + "error": null + }, + { + "index": 2, + "document_id": "5c049412-054f-42e1-9e13-023823e8aad2", + "status": "uploaded", + "job_id": "ec7732a5-631c-4053-a548-f6a4a7293ebc", + "error": null + }, + { + "index": 3, + "document_id": "9232b00d-0b56-4eca-a273-e569546850c4", + "status": "uploaded", + "job_id": "fdefd8b1-aed4-45b6-9622-820a8c275cbc", + "error": null + }, + { + "index": 4, + "document_id": "dd32e7ab-cd98-4324-8787-5750afe6f34e", + "status": "uploaded", + "job_id": "f6d690c6-8a2a-4b73-977a-ab236550082b", + "error": null + }, + { + "index": 5, + "document_id": "f5e94ccb-2c27-4772-8c19-652dad8bbb73", + "status": "uploaded", + "job_id": "23f87c3e-a3d2-412f-9254-e9b5b2234007", + "error": null + }, + { + "index": 6, + "document_id": "d91d171e-bb4e-42b3-8d5c-3a7135fd8134", + "status": "uploaded", + "job_id": "09fcb24a-36cd-4693-b529-e41a814ad390", + "error": null + }, + { + "index": 7, + "document_id": "2a79108e-dc8e-41f6-b08f-9932d2836602", + "status": "uploaded", + "job_id": "ea351d34-4de4-4c36-ace3-fa018115f6e9", + "error": null + }, + { + "index": 8, + "document_id": "b9073464-28ec-476a-bac8-0971193d9f2f", + "status": "uploaded", + "job_id": "ecc1b7f6-ba30-41f1-9d14-7c6200f2d8c7", + "error": null + }, + { + "index": 9, + "document_id": "148116bf-a6c2-4935-a2fa-48ad7a0c31ee", + "status": "uploaded", + "job_id": "3134b26c-1334-4bce-bf67-ef2eb93a4469", + "error": null + } + ] + }, + "final_run": { + "id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "name": "load-valid-10-2026-06-08T08:09:28.032939+00:00", + "status": "completed", + "total_documents": 10, + "accepted_documents": 10, + "rejected_documents": 0, + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 0, + "ready": 0, + "indexed": 10, + "failed": 0, + "total": 10 + }, + "created_at": "2026-06-08T08:09:37.392806Z", + "updated_at": "2026-06-08T08:10:29.829994Z" + }, + "documents": { + "items": [ + { + "id": "f5e94ccb-2c27-4772-8c19-652dad8bbb73", + "filename": "load-valid-006.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:09.552363Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:52.473267Z", + "extract_enqueued_at": "2026-06-08T08:09:52.473267Z", + "extract_started_at": "2026-06-08T08:09:55.219272Z", + "extract_finished_at": "2026-06-08T08:09:56.375791Z", + "index_enqueued_at": "2026-06-08T08:09:56.959675Z", + "index_started_at": "2026-06-08T08:10:05.404586Z", + "index_finished_at": "2026-06-08T08:10:09.552363Z" + } + }, + { + "id": "d91d171e-bb4e-42b3-8d5c-3a7135fd8134", + "filename": "load-valid-007.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:14.689817Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:53.646986Z", + "extract_enqueued_at": "2026-06-08T08:09:53.646986Z", + "extract_started_at": "2026-06-08T08:09:57.032920Z", + "extract_finished_at": "2026-06-08T08:09:58.187896Z", + "index_enqueued_at": "2026-06-08T08:09:58.770841Z", + "index_started_at": "2026-06-08T08:10:10.883507Z", + "index_finished_at": "2026-06-08T08:10:14.689817Z" + } + }, + { + "id": "2a79108e-dc8e-41f6-b08f-9932d2836602", + "filename": "load-valid-008.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:15.138869Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:54.860222Z", + "extract_enqueued_at": "2026-06-08T08:09:54.860222Z", + "extract_started_at": "2026-06-08T08:09:58.013677Z", + "extract_finished_at": "2026-06-08T08:09:59.006307Z", + "index_enqueued_at": "2026-06-08T08:09:59.561883Z", + "index_started_at": "2026-06-08T08:10:11.239976Z", + "index_finished_at": "2026-06-08T08:10:15.138869Z" + } + }, + { + "id": "db415368-a778-471d-9124-19b37289e59e", + "filename": "load-valid-001.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:09:59.396961Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:46.485228Z", + "extract_enqueued_at": "2026-06-08T08:09:46.485228Z", + "extract_started_at": "2026-06-08T08:09:48.753480Z", + "extract_finished_at": "2026-06-08T08:09:49.999550Z", + "index_enqueued_at": "2026-06-08T08:09:50.564392Z", + "index_started_at": "2026-06-08T08:09:53.081145Z", + "index_finished_at": "2026-06-08T08:09:59.396961Z" + } + }, + { + "id": "f71d14a2-2517-49e9-b0b0-8cc90d11c793", + "filename": "load-valid-002.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:09:59.714413Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:47.692083Z", + "extract_enqueued_at": "2026-06-08T08:09:47.692083Z", + "extract_started_at": "2026-06-08T08:09:50.183154Z", + "extract_finished_at": "2026-06-08T08:09:51.147375Z", + "index_enqueued_at": "2026-06-08T08:09:51.755669Z", + "index_started_at": "2026-06-08T08:09:54.242010Z", + "index_finished_at": "2026-06-08T08:09:59.714413Z" + } + }, + { + "id": "5c049412-054f-42e1-9e13-023823e8aad2", + "filename": "load-valid-003.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:02.158083Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:48.931801Z", + "extract_enqueued_at": "2026-06-08T08:09:48.931801Z", + "extract_started_at": "2026-06-08T08:09:51.423905Z", + "extract_finished_at": "2026-06-08T08:09:52.743102Z", + "index_enqueued_at": "2026-06-08T08:09:53.324472Z", + "index_started_at": "2026-06-08T08:09:56.669129Z", + "index_finished_at": "2026-06-08T08:10:02.158083Z" + } + }, + { + "id": "b9073464-28ec-476a-bac8-0971193d9f2f", + "filename": "load-valid-009.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:16.558621Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:56.108341Z", + "extract_enqueued_at": "2026-06-08T08:09:56.108341Z", + "extract_started_at": "2026-06-08T08:09:59.054110Z", + "extract_finished_at": "2026-06-08T08:10:00.153835Z", + "index_enqueued_at": "2026-06-08T08:10:00.722025Z", + "index_started_at": "2026-06-08T08:10:12.572818Z", + "index_finished_at": "2026-06-08T08:10:16.558621Z" + } + }, + { + "id": "148116bf-a6c2-4935-a2fa-48ad7a0c31ee", + "filename": "load-valid-010.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:22.237615Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:57.430426Z", + "extract_enqueued_at": "2026-06-08T08:09:57.430426Z", + "extract_started_at": "2026-06-08T08:10:00.700838Z", + "extract_finished_at": "2026-06-08T08:10:01.996813Z", + "index_enqueued_at": "2026-06-08T08:10:02.578208Z", + "index_started_at": "2026-06-08T08:10:17.718603Z", + "index_finished_at": "2026-06-08T08:10:22.237615Z" + } + }, + { + "id": "9232b00d-0b56-4eca-a273-e569546850c4", + "filename": "load-valid-004.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:07.515406Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:50.121905Z", + "extract_enqueued_at": "2026-06-08T08:09:50.121905Z", + "extract_started_at": "2026-06-08T08:09:52.669554Z", + "extract_finished_at": "2026-06-08T08:09:53.822766Z", + "index_enqueued_at": "2026-06-08T08:09:54.406964Z", + "index_started_at": "2026-06-08T08:10:03.105459Z", + "index_finished_at": "2026-06-08T08:10:07.515406Z" + } + }, + { + "id": "dd32e7ab-cd98-4324-8787-5750afe6f34e", + "filename": "load-valid-005.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:07.940086Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:51.290543Z", + "extract_enqueued_at": "2026-06-08T08:09:51.290543Z", + "extract_started_at": "2026-06-08T08:09:53.873697Z", + "extract_finished_at": "2026-06-08T08:09:55.047888Z", + "index_enqueued_at": "2026-06-08T08:09:55.650679Z", + "index_started_at": "2026-06-08T08:10:03.140619Z", + "index_finished_at": "2026-06-08T08:10:07.940086Z" + } + }, + { + "id": "81ac5061-3791-4176-857d-96ce7fb97c92", + "filename": "terms-and-conditions-template.pdf", + "content_type": "application/pdf", + "file_size_bytes": 50399, + "page_count": 2, + "ingestion_run_id": "841b78f4-4233-4bf9-9cfe-e2945c32d0f4", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-07T19:05:02.193118Z", + "updated_at": "2026-06-07T19:05:22.315615Z", + "timing": { + "upload_completed_at": "2026-06-07T19:05:06.456797Z", + "extract_enqueued_at": "2026-06-07T19:05:06.456797Z", + "extract_started_at": "2026-06-07T19:05:10.155645Z", + "extract_finished_at": "2026-06-07T19:05:11.932408Z", + "index_enqueued_at": "2026-06-07T19:05:12.620190Z", + "index_started_at": "2026-06-07T19:05:15.937121Z", + "index_finished_at": "2026-06-07T19:05:22.315615Z" + } + } + ], + "limit": 100, + "offset": 0, + "total": 11 + }, + "queue_snapshots": [ + { + "elapsed_seconds": 32.573, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 2, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 3, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 1, + "extracting": 1, + "indexing": 8, + "ready": 0, + "indexed": 0, + "failed": 0, + "total": 10 + } + }, + { + "elapsed_seconds": 40.179, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 3, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 7, + "ready": 0, + "indexed": 3, + "failed": 0, + "total": 10 + } + }, + { + "elapsed_seconds": 47.647, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 0, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 4, + "ready": 0, + "indexed": 6, + "failed": 0, + "total": 10 + } + }, + { + "elapsed_seconds": 55.148, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 1, + "ready": 0, + "indexed": 9, + "failed": 0, + "total": 10 + } + }, + { + "elapsed_seconds": 62.683, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "completed", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 0, + "ready": 0, + "indexed": 10, + "failed": 0, + "total": 10 + } + } + ], + "durations_seconds": { + "prepare_upload_complete": 30.026, + "total": 63.991 + } +} diff --git a/artifacts/ingestion-load/ingestion-load-valid-25-20260608T081409Z.json b/artifacts/ingestion-load/ingestion-load-valid-25-20260608T081409Z.json new file mode 100644 index 0000000..6556f2c --- /dev/null +++ b/artifacts/ingestion-load/ingestion-load-valid-25-20260608T081409Z.json @@ -0,0 +1,1552 @@ +{ + "generated_at": "2026-06-08T08:12:03.126850+00:00", + "api_base": "http://localhost:8000", + "scenario": "valid", + "requested_count": 25, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "prepare": { + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "bucket": "documents", + "expires_in": 600, + "accepted_count": 25, + "rejected_count": 0, + "items": [ + { + "index": 0, + "filename": "load-valid-001.pdf", + "client_file_id": "load-valid-001", + "status": "prepared", + "document_id": "437741a4-34e9-498e-b1ba-df76e228d460", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/437741a4-34e9-498e-b1ba-df76e228d460/load-valid-001.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 1, + "filename": "load-valid-002.pdf", + "client_file_id": "load-valid-002", + "status": "prepared", + "document_id": "0b8a0ee4-fe82-4bbc-af7e-0fbe6cd26c04", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/0b8a0ee4-fe82-4bbc-af7e-0fbe6cd26c04/load-valid-002.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 2, + "filename": "load-valid-003.pdf", + "client_file_id": "load-valid-003", + "status": "prepared", + "document_id": "ab9b5224-5f2c-4c1f-821e-0a0f3867d82b", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/ab9b5224-5f2c-4c1f-821e-0a0f3867d82b/load-valid-003.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 3, + "filename": "load-valid-004.pdf", + "client_file_id": "load-valid-004", + "status": "prepared", + "document_id": "79cea03e-1db7-4143-b361-070f8eae1504", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/79cea03e-1db7-4143-b361-070f8eae1504/load-valid-004.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 4, + "filename": "load-valid-005.pdf", + "client_file_id": "load-valid-005", + "status": "prepared", + "document_id": "bd67791c-f2cc-4377-b43c-e8cfe1ac5e21", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/bd67791c-f2cc-4377-b43c-e8cfe1ac5e21/load-valid-005.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 5, + "filename": "load-valid-006.pdf", + "client_file_id": "load-valid-006", + "status": "prepared", + "document_id": "5338251f-afa2-42cf-80f8-b149c14d26dc", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/5338251f-afa2-42cf-80f8-b149c14d26dc/load-valid-006.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 6, + "filename": "load-valid-007.pdf", + "client_file_id": "load-valid-007", + "status": "prepared", + "document_id": "570e6989-fd09-4a60-bcf1-671ff0d5b471", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/570e6989-fd09-4a60-bcf1-671ff0d5b471/load-valid-007.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 7, + "filename": "load-valid-008.pdf", + "client_file_id": "load-valid-008", + "status": "prepared", + "document_id": "67753764-a6c3-46ab-85a2-1dd5c725b8e0", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/67753764-a6c3-46ab-85a2-1dd5c725b8e0/load-valid-008.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 8, + "filename": "load-valid-009.pdf", + "client_file_id": "load-valid-009", + "status": "prepared", + "document_id": "f107ad72-6542-4433-a9fb-b48e53ea4720", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/f107ad72-6542-4433-a9fb-b48e53ea4720/load-valid-009.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 9, + "filename": "load-valid-010.pdf", + "client_file_id": "load-valid-010", + "status": "prepared", + "document_id": "3f917b1a-ea97-4070-9e01-c9a3a8e17217", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/3f917b1a-ea97-4070-9e01-c9a3a8e17217/load-valid-010.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 10, + "filename": "load-valid-011.pdf", + "client_file_id": "load-valid-011", + "status": "prepared", + "document_id": "956ba011-8d02-492f-92bb-59a2819e334b", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/956ba011-8d02-492f-92bb-59a2819e334b/load-valid-011.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 11, + "filename": "load-valid-012.pdf", + "client_file_id": "load-valid-012", + "status": "prepared", + "document_id": "a0beb927-fe1a-4960-87a0-eecbc6ad0d2d", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/a0beb927-fe1a-4960-87a0-eecbc6ad0d2d/load-valid-012.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 12, + "filename": "load-valid-013.pdf", + "client_file_id": "load-valid-013", + "status": "prepared", + "document_id": "68eb4635-a8e4-4208-8a71-5d7949ee003c", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/68eb4635-a8e4-4208-8a71-5d7949ee003c/load-valid-013.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 13, + "filename": "load-valid-014.pdf", + "client_file_id": "load-valid-014", + "status": "prepared", + "document_id": "ac942036-c749-4cb7-b262-db97317239be", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/ac942036-c749-4cb7-b262-db97317239be/load-valid-014.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 14, + "filename": "load-valid-015.pdf", + "client_file_id": "load-valid-015", + "status": "prepared", + "document_id": "48393057-6d70-4ce3-affc-2c1abc84ad1d", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/48393057-6d70-4ce3-affc-2c1abc84ad1d/load-valid-015.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 15, + "filename": "load-valid-016.pdf", + "client_file_id": "load-valid-016", + "status": "prepared", + "document_id": "7e6d1c3a-d91b-44e8-8f50-d2dc5f5ec5b3", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/7e6d1c3a-d91b-44e8-8f50-d2dc5f5ec5b3/load-valid-016.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 16, + "filename": "load-valid-017.pdf", + "client_file_id": "load-valid-017", + "status": "prepared", + "document_id": "ebaa6b49-42a7-4481-95a6-944085f68725", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/ebaa6b49-42a7-4481-95a6-944085f68725/load-valid-017.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 17, + "filename": "load-valid-018.pdf", + "client_file_id": "load-valid-018", + "status": "prepared", + "document_id": "db721cdd-1220-400b-b0b9-17ee9a592a34", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/db721cdd-1220-400b-b0b9-17ee9a592a34/load-valid-018.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 18, + "filename": "load-valid-019.pdf", + "client_file_id": "load-valid-019", + "status": "prepared", + "document_id": "7c523c2d-fc75-416a-b392-656ba6856a2c", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/7c523c2d-fc75-416a-b392-656ba6856a2c/load-valid-019.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 19, + "filename": "load-valid-020.pdf", + "client_file_id": "load-valid-020", + "status": "prepared", + "document_id": "90ba0897-a5ca-4f89-b4bf-e3bccb8d257e", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/90ba0897-a5ca-4f89-b4bf-e3bccb8d257e/load-valid-020.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 20, + "filename": "load-valid-021.pdf", + "client_file_id": "load-valid-021", + "status": "prepared", + "document_id": "dd01be11-e8bd-4302-b3d6-e1742437ddc6", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/dd01be11-e8bd-4302-b3d6-e1742437ddc6/load-valid-021.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 21, + "filename": "load-valid-022.pdf", + "client_file_id": "load-valid-022", + "status": "prepared", + "document_id": "a97568b2-fe01-4bb1-a0e2-993336e59d60", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/a97568b2-fe01-4bb1-a0e2-993336e59d60/load-valid-022.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 22, + "filename": "load-valid-023.pdf", + "client_file_id": "load-valid-023", + "status": "prepared", + "document_id": "0472d799-02fe-49c3-9671-8e004fa6d1b1", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/0472d799-02fe-49c3-9671-8e004fa6d1b1/load-valid-023.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 23, + "filename": "load-valid-024.pdf", + "client_file_id": "load-valid-024", + "status": "prepared", + "document_id": "4d5d938b-23d8-49e0-9ad7-e31e432c39a8", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/4d5d938b-23d8-49e0-9ad7-e31e432c39a8/load-valid-024.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 24, + "filename": "load-valid-025.pdf", + "client_file_id": "load-valid-025", + "status": "prepared", + "document_id": "414d9371-658e-4235-9bfe-428de3057c6a", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/414d9371-658e-4235-9bfe-428de3057c6a/load-valid-025.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + } + ] + }, + "complete": { + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "accepted_count": 25, + "failed_count": 0, + "items": [ + { + "index": 0, + "document_id": "437741a4-34e9-498e-b1ba-df76e228d460", + "status": "uploaded", + "job_id": "6a99581f-7293-4dfa-9f02-22ce9522fb9b", + "error": null + }, + { + "index": 1, + "document_id": "0b8a0ee4-fe82-4bbc-af7e-0fbe6cd26c04", + "status": "uploaded", + "job_id": "02cb745d-673f-45ec-8063-da4d7ccb81e1", + "error": null + }, + { + "index": 2, + "document_id": "ab9b5224-5f2c-4c1f-821e-0a0f3867d82b", + "status": "uploaded", + "job_id": "ade671ef-498f-420a-ab2d-9df4f39552df", + "error": null + }, + { + "index": 3, + "document_id": "79cea03e-1db7-4143-b361-070f8eae1504", + "status": "uploaded", + "job_id": "358ffed5-e324-4a81-8a44-e555743932c8", + "error": null + }, + { + "index": 4, + "document_id": "bd67791c-f2cc-4377-b43c-e8cfe1ac5e21", + "status": "uploaded", + "job_id": "4210d1b7-be49-4108-930f-0d9521690423", + "error": null + }, + { + "index": 5, + "document_id": "5338251f-afa2-42cf-80f8-b149c14d26dc", + "status": "uploaded", + "job_id": "013ad4f9-2957-4025-8c91-2991cdb04a9e", + "error": null + }, + { + "index": 6, + "document_id": "570e6989-fd09-4a60-bcf1-671ff0d5b471", + "status": "uploaded", + "job_id": "c89d7bf7-9f97-4a5b-9a58-a2f84f4319d2", + "error": null + }, + { + "index": 7, + "document_id": "67753764-a6c3-46ab-85a2-1dd5c725b8e0", + "status": "uploaded", + "job_id": "4b818942-0662-4f77-9442-26eb10f97cf0", + "error": null + }, + { + "index": 8, + "document_id": "f107ad72-6542-4433-a9fb-b48e53ea4720", + "status": "uploaded", + "job_id": "2d20e18a-a1f2-467e-aafe-dcfe2a662509", + "error": null + }, + { + "index": 9, + "document_id": "3f917b1a-ea97-4070-9e01-c9a3a8e17217", + "status": "uploaded", + "job_id": "f3978463-9693-49e4-9570-b7e7cd1d7786", + "error": null + }, + { + "index": 10, + "document_id": "956ba011-8d02-492f-92bb-59a2819e334b", + "status": "uploaded", + "job_id": "ebd2458b-f629-4841-8c7e-9a1437bcfc3e", + "error": null + }, + { + "index": 11, + "document_id": "a0beb927-fe1a-4960-87a0-eecbc6ad0d2d", + "status": "uploaded", + "job_id": "793bb017-b696-437a-acd6-563f9379ec77", + "error": null + }, + { + "index": 12, + "document_id": "68eb4635-a8e4-4208-8a71-5d7949ee003c", + "status": "uploaded", + "job_id": "f8d75553-3b39-43d6-9147-95d7db0de0bd", + "error": null + }, + { + "index": 13, + "document_id": "ac942036-c749-4cb7-b262-db97317239be", + "status": "uploaded", + "job_id": "1b1b3603-018a-4b69-807f-f21d7963b0de", + "error": null + }, + { + "index": 14, + "document_id": "48393057-6d70-4ce3-affc-2c1abc84ad1d", + "status": "uploaded", + "job_id": "7d5b8947-8fe2-4ce5-80c4-68eed4642e36", + "error": null + }, + { + "index": 15, + "document_id": "7e6d1c3a-d91b-44e8-8f50-d2dc5f5ec5b3", + "status": "uploaded", + "job_id": "023816b1-fed5-4d54-a6ca-e6b8c013244f", + "error": null + }, + { + "index": 16, + "document_id": "ebaa6b49-42a7-4481-95a6-944085f68725", + "status": "uploaded", + "job_id": "024ca882-3241-474d-8a12-da922279b9c7", + "error": null + }, + { + "index": 17, + "document_id": "db721cdd-1220-400b-b0b9-17ee9a592a34", + "status": "uploaded", + "job_id": "0cacbdfa-414a-4e9d-850e-a5360fe1166f", + "error": null + }, + { + "index": 18, + "document_id": "7c523c2d-fc75-416a-b392-656ba6856a2c", + "status": "uploaded", + "job_id": "010eb6df-86f3-4564-bebd-7b154ef9418b", + "error": null + }, + { + "index": 19, + "document_id": "90ba0897-a5ca-4f89-b4bf-e3bccb8d257e", + "status": "uploaded", + "job_id": "30b8ee1c-57c8-4671-b7d8-b1f44dcbb6ce", + "error": null + }, + { + "index": 20, + "document_id": "dd01be11-e8bd-4302-b3d6-e1742437ddc6", + "status": "uploaded", + "job_id": "9d3f906f-b622-434e-ae8b-7018bb43647b", + "error": null + }, + { + "index": 21, + "document_id": "a97568b2-fe01-4bb1-a0e2-993336e59d60", + "status": "uploaded", + "job_id": "4515db67-c4f1-4c9d-a52d-0f3b173976c5", + "error": null + }, + { + "index": 22, + "document_id": "0472d799-02fe-49c3-9671-8e004fa6d1b1", + "status": "uploaded", + "job_id": "95136293-e3d7-4ed7-ba22-5636262a77d3", + "error": null + }, + { + "index": 23, + "document_id": "4d5d938b-23d8-49e0-9ad7-e31e432c39a8", + "status": "uploaded", + "job_id": "65572bb6-f783-4cb2-b8e2-32fc5fdee431", + "error": null + }, + { + "index": 24, + "document_id": "414d9371-658e-4235-9bfe-428de3057c6a", + "status": "uploaded", + "job_id": "a1cf83e2-7b0e-433b-99a9-3e5c6084d344", + "error": null + } + ] + }, + "final_run": { + "id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "name": "load-valid-25-2026-06-08T08:12:03.126850+00:00", + "status": "completed", + "total_documents": 25, + "accepted_documents": 25, + "rejected_documents": 0, + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 0, + "ready": 0, + "indexed": 25, + "failed": 0, + "total": 25 + }, + "created_at": "2026-06-08T08:12:18.569045Z", + "updated_at": "2026-06-08T08:14:06.736760Z" + }, + "documents": { + "items": [ + { + "id": "f107ad72-6542-4433-a9fb-b48e53ea4720", + "filename": "load-valid-009.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:10.153489Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:50.932436Z", + "extract_enqueued_at": "2026-06-08T08:12:50.932436Z", + "extract_started_at": "2026-06-08T08:12:54.054442Z", + "extract_finished_at": "2026-06-08T08:12:55.045244Z", + "index_enqueued_at": "2026-06-08T08:12:55.623470Z", + "index_started_at": "2026-06-08T08:13:05.998938Z", + "index_finished_at": "2026-06-08T08:13:10.153489Z" + } + }, + { + "id": "dd01be11-e8bd-4302-b3d6-e1742437ddc6", + "filename": "load-valid-021.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:48.386962Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:07.095660Z", + "extract_enqueued_at": "2026-06-08T08:13:07.095660Z", + "extract_started_at": "2026-06-08T08:13:09.898699Z", + "extract_finished_at": "2026-06-08T08:13:11.701709Z", + "index_enqueued_at": "2026-06-08T08:13:12.293954Z", + "index_started_at": "2026-06-08T08:13:43.809847Z", + "index_finished_at": "2026-06-08T08:13:48.386962Z" + } + }, + { + "id": "a97568b2-fe01-4bb1-a0e2-993336e59d60", + "filename": "load-valid-022.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:51.524366Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:08.465546Z", + "extract_enqueued_at": "2026-06-08T08:13:08.465546Z", + "extract_started_at": "2026-06-08T08:13:11.100246Z", + "extract_finished_at": "2026-06-08T08:13:12.091699Z", + "index_enqueued_at": "2026-06-08T08:13:12.654714Z", + "index_started_at": "2026-06-08T08:13:47.441108Z", + "index_finished_at": "2026-06-08T08:13:51.524366Z" + } + }, + { + "id": "79cea03e-1db7-4143-b361-070f8eae1504", + "filename": "load-valid-004.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:00.670418Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:40.980141Z", + "extract_enqueued_at": "2026-06-08T08:12:40.980141Z", + "extract_started_at": "2026-06-08T08:12:43.621394Z", + "extract_finished_at": "2026-06-08T08:12:44.657491Z", + "index_enqueued_at": "2026-06-08T08:12:45.459967Z", + "index_started_at": "2026-06-08T08:12:56.066416Z", + "index_finished_at": "2026-06-08T08:13:00.670418Z" + } + }, + { + "id": "bd67791c-f2cc-4377-b43c-e8cfe1ac5e21", + "filename": "load-valid-005.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:02.090208Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:42.178733Z", + "extract_enqueued_at": "2026-06-08T08:12:42.178733Z", + "extract_started_at": "2026-06-08T08:12:45.568998Z", + "extract_finished_at": "2026-06-08T08:12:46.668796Z", + "index_enqueued_at": "2026-06-08T08:12:49.251130Z", + "index_started_at": "2026-06-08T08:12:57.578812Z", + "index_finished_at": "2026-06-08T08:13:02.090208Z" + } + }, + { + "id": "3f917b1a-ea97-4070-9e01-c9a3a8e17217", + "filename": "load-valid-010.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:17.639787Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:52.958349Z", + "extract_enqueued_at": "2026-06-08T08:12:52.958349Z", + "extract_started_at": "2026-06-08T08:12:55.684970Z", + "extract_finished_at": "2026-06-08T08:12:56.751893Z", + "index_enqueued_at": "2026-06-08T08:12:57.320354Z", + "index_started_at": "2026-06-08T08:13:13.734174Z", + "index_finished_at": "2026-06-08T08:13:17.639787Z" + } + }, + { + "id": "67753764-a6c3-46ab-85a2-1dd5c725b8e0", + "filename": "load-valid-008.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:09.979778Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:46.505324Z", + "extract_enqueued_at": "2026-06-08T08:12:46.505324Z", + "extract_started_at": "2026-06-08T08:12:53.048761Z", + "extract_finished_at": "2026-06-08T08:12:54.255211Z", + "index_enqueued_at": "2026-06-08T08:12:54.829439Z", + "index_started_at": "2026-06-08T08:13:05.871023Z", + "index_finished_at": "2026-06-08T08:13:09.979778Z" + } + }, + { + "id": "570e6989-fd09-4a60-bcf1-671ff0d5b471", + "filename": "load-valid-007.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:15.518850Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:44.923498Z", + "extract_enqueued_at": "2026-06-08T08:12:44.923498Z", + "extract_started_at": "2026-06-08T08:12:50.384169Z", + "extract_finished_at": "2026-06-08T08:12:53.166576Z", + "index_enqueued_at": "2026-06-08T08:12:53.734338Z", + "index_started_at": "2026-06-08T08:13:03.964035Z", + "index_finished_at": "2026-06-08T08:13:15.518850Z" + } + }, + { + "id": "956ba011-8d02-492f-92bb-59a2819e334b", + "filename": "load-valid-011.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:17.890931Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:54.159763Z", + "extract_enqueued_at": "2026-06-08T08:12:54.159763Z", + "extract_started_at": "2026-06-08T08:12:57.027092Z", + "extract_finished_at": "2026-06-08T08:12:58.014249Z", + "index_enqueued_at": "2026-06-08T08:12:58.861602Z", + "index_started_at": "2026-06-08T08:13:13.968853Z", + "index_finished_at": "2026-06-08T08:13:17.890931Z" + } + }, + { + "id": "a0beb927-fe1a-4960-87a0-eecbc6ad0d2d", + "filename": "load-valid-012.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:22.743664Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:55.369836Z", + "extract_enqueued_at": "2026-06-08T08:12:55.369836Z", + "extract_started_at": "2026-06-08T08:12:58.265633Z", + "extract_finished_at": "2026-06-08T08:12:59.337823Z", + "index_enqueued_at": "2026-06-08T08:12:59.996390Z", + "index_started_at": "2026-06-08T08:13:18.554086Z", + "index_finished_at": "2026-06-08T08:13:22.743664Z" + } + }, + { + "id": "414d9371-658e-4235-9bfe-428de3057c6a", + "filename": "load-valid-025.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:58.994382Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:12.159709Z", + "extract_enqueued_at": "2026-06-08T08:13:12.159709Z", + "extract_started_at": "2026-06-08T08:13:14.940719Z", + "extract_finished_at": "2026-06-08T08:13:15.775277Z", + "index_enqueued_at": "2026-06-08T08:13:16.340701Z", + "index_started_at": "2026-06-08T08:13:55.190576Z", + "index_finished_at": "2026-06-08T08:13:58.994382Z" + } + }, + { + "id": "5338251f-afa2-42cf-80f8-b149c14d26dc", + "filename": "load-valid-006.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:02.264724Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:43.506996Z", + "extract_enqueued_at": "2026-06-08T08:12:43.506996Z", + "extract_started_at": "2026-06-08T08:12:46.347376Z", + "extract_finished_at": "2026-06-08T08:12:49.199225Z", + "index_enqueued_at": "2026-06-08T08:12:51.009223Z", + "index_started_at": "2026-06-08T08:12:57.658947Z", + "index_finished_at": "2026-06-08T08:13:02.264724Z" + } + }, + { + "id": "437741a4-34e9-498e-b1ba-df76e228d460", + "filename": "load-valid-001.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:12:52.338245Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:37.448323Z", + "extract_enqueued_at": "2026-06-08T08:12:37.448323Z", + "extract_started_at": "2026-06-08T08:12:39.794547Z", + "extract_finished_at": "2026-06-08T08:12:40.869368Z", + "index_enqueued_at": "2026-06-08T08:12:41.493640Z", + "index_started_at": "2026-06-08T08:12:43.954178Z", + "index_finished_at": "2026-06-08T08:12:52.338245Z" + } + }, + { + "id": "ab9b5224-5f2c-4c1f-821e-0a0f3867d82b", + "filename": "load-valid-003.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:12:53.967697Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:39.794073Z", + "extract_enqueued_at": "2026-06-08T08:12:39.794073Z", + "extract_started_at": "2026-06-08T08:12:42.253070Z", + "extract_finished_at": "2026-06-08T08:12:43.314677Z", + "index_enqueued_at": "2026-06-08T08:12:43.874952Z", + "index_started_at": "2026-06-08T08:12:46.571296Z", + "index_finished_at": "2026-06-08T08:12:53.967697Z" + } + }, + { + "id": "0b8a0ee4-fe82-4bbc-af7e-0fbe6cd26c04", + "filename": "load-valid-002.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:12:54.131532Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:38.628986Z", + "extract_enqueued_at": "2026-06-08T08:12:38.628986Z", + "extract_started_at": "2026-06-08T08:12:41.027216Z", + "extract_finished_at": "2026-06-08T08:12:41.979607Z", + "index_enqueued_at": "2026-06-08T08:12:42.531284Z", + "index_started_at": "2026-06-08T08:12:45.647975Z", + "index_finished_at": "2026-06-08T08:12:54.131532Z" + } + }, + { + "id": "ebaa6b49-42a7-4481-95a6-944085f68725", + "filename": "load-valid-017.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:33.498225Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:02.086444Z", + "extract_enqueued_at": "2026-06-08T08:13:02.086444Z", + "extract_started_at": "2026-06-08T08:13:04.919251Z", + "extract_finished_at": "2026-06-08T08:13:06.106725Z", + "index_enqueued_at": "2026-06-08T08:13:06.666030Z", + "index_started_at": "2026-06-08T08:13:29.257034Z", + "index_finished_at": "2026-06-08T08:13:33.498225Z" + } + }, + { + "id": "db721cdd-1220-400b-b0b9-17ee9a592a34", + "filename": "load-valid-018.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:43.452640Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:03.341567Z", + "extract_enqueued_at": "2026-06-08T08:13:03.341567Z", + "extract_started_at": "2026-06-08T08:13:06.426813Z", + "extract_finished_at": "2026-06-08T08:13:10.764269Z", + "index_enqueued_at": "2026-06-08T08:13:11.351435Z", + "index_started_at": "2026-06-08T08:13:38.958615Z", + "index_finished_at": "2026-06-08T08:13:43.452640Z" + } + }, + { + "id": "68eb4635-a8e4-4208-8a71-5d7949ee003c", + "filename": "load-valid-013.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:25.618404Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:56.681249Z", + "extract_enqueued_at": "2026-06-08T08:12:56.681249Z", + "extract_started_at": "2026-06-08T08:12:59.244184Z", + "extract_finished_at": "2026-06-08T08:13:00.753393Z", + "index_enqueued_at": "2026-06-08T08:13:01.318188Z", + "index_started_at": "2026-06-08T08:13:21.607826Z", + "index_finished_at": "2026-06-08T08:13:25.618404Z" + } + }, + { + "id": "ac942036-c749-4cb7-b262-db97317239be", + "filename": "load-valid-014.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:25.899984Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:57.907787Z", + "extract_enqueued_at": "2026-06-08T08:12:57.907787Z", + "extract_started_at": "2026-06-08T08:13:00.835911Z", + "extract_finished_at": "2026-06-08T08:13:01.947256Z", + "index_enqueued_at": "2026-06-08T08:13:02.509384Z", + "index_started_at": "2026-06-08T08:13:21.855414Z", + "index_finished_at": "2026-06-08T08:13:25.899984Z" + } + }, + { + "id": "48393057-6d70-4ce3-affc-2c1abc84ad1d", + "filename": "load-valid-015.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:30.516275Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:59.243258Z", + "extract_enqueued_at": "2026-06-08T08:12:59.243258Z", + "extract_started_at": "2026-06-08T08:13:02.183713Z", + "extract_finished_at": "2026-06-08T08:13:03.386526Z", + "index_enqueued_at": "2026-06-08T08:13:04.056129Z", + "index_started_at": "2026-06-08T08:13:26.377498Z", + "index_finished_at": "2026-06-08T08:13:30.516275Z" + } + }, + { + "id": "7e6d1c3a-d91b-44e8-8f50-d2dc5f5ec5b3", + "filename": "load-valid-016.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:33.322579Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:00.831906Z", + "extract_enqueued_at": "2026-06-08T08:13:00.831906Z", + "extract_started_at": "2026-06-08T08:13:03.463744Z", + "extract_finished_at": "2026-06-08T08:13:04.506229Z", + "index_enqueued_at": "2026-06-08T08:13:05.109986Z", + "index_started_at": "2026-06-08T08:13:28.877653Z", + "index_finished_at": "2026-06-08T08:13:33.322579Z" + } + }, + { + "id": "0472d799-02fe-49c3-9671-8e004fa6d1b1", + "filename": "load-valid-023.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:52.209727Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:09.768015Z", + "extract_enqueued_at": "2026-06-08T08:13:09.768015Z", + "extract_started_at": "2026-06-08T08:13:12.419160Z", + "extract_finished_at": "2026-06-08T08:13:14.210193Z", + "index_enqueued_at": "2026-06-08T08:13:14.799714Z", + "index_started_at": "2026-06-08T08:13:47.720676Z", + "index_finished_at": "2026-06-08T08:13:52.209727Z" + } + }, + { + "id": "7c523c2d-fc75-416a-b392-656ba6856a2c", + "filename": "load-valid-019.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:39.673260Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:04.635594Z", + "extract_enqueued_at": "2026-06-08T08:13:04.635594Z", + "extract_started_at": "2026-06-08T08:13:07.233196Z", + "extract_finished_at": "2026-06-08T08:13:08.503656Z", + "index_enqueued_at": "2026-06-08T08:13:09.095933Z", + "index_started_at": "2026-06-08T08:13:34.627356Z", + "index_finished_at": "2026-06-08T08:13:39.673260Z" + } + }, + { + "id": "90ba0897-a5ca-4f89-b4bf-e3bccb8d257e", + "filename": "load-valid-020.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:43.170877Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:05.918084Z", + "extract_enqueued_at": "2026-06-08T08:13:05.918084Z", + "extract_started_at": "2026-06-08T08:13:08.786005Z", + "extract_finished_at": "2026-06-08T08:13:10.579070Z", + "index_enqueued_at": "2026-06-08T08:13:11.164233Z", + "index_started_at": "2026-06-08T08:13:38.714943Z", + "index_finished_at": "2026-06-08T08:13:43.170877Z" + } + }, + { + "id": "4d5d938b-23d8-49e0-9ad7-e31e432c39a8", + "filename": "load-valid-024.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:56.013481Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:10.947098Z", + "extract_enqueued_at": "2026-06-08T08:13:10.947098Z", + "extract_started_at": "2026-06-08T08:13:14.318459Z", + "extract_finished_at": "2026-06-08T08:13:15.462815Z", + "index_enqueued_at": "2026-06-08T08:13:16.031726Z", + "index_started_at": "2026-06-08T08:13:52.075094Z", + "index_finished_at": "2026-06-08T08:13:56.013481Z" + } + }, + { + "id": "d91d171e-bb4e-42b3-8d5c-3a7135fd8134", + "filename": "load-valid-007.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:14.689817Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:53.646986Z", + "extract_enqueued_at": "2026-06-08T08:09:53.646986Z", + "extract_started_at": "2026-06-08T08:09:57.032920Z", + "extract_finished_at": "2026-06-08T08:09:58.187896Z", + "index_enqueued_at": "2026-06-08T08:09:58.770841Z", + "index_started_at": "2026-06-08T08:10:10.883507Z", + "index_finished_at": "2026-06-08T08:10:14.689817Z" + } + }, + { + "id": "2a79108e-dc8e-41f6-b08f-9932d2836602", + "filename": "load-valid-008.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:15.138869Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:54.860222Z", + "extract_enqueued_at": "2026-06-08T08:09:54.860222Z", + "extract_started_at": "2026-06-08T08:09:58.013677Z", + "extract_finished_at": "2026-06-08T08:09:59.006307Z", + "index_enqueued_at": "2026-06-08T08:09:59.561883Z", + "index_started_at": "2026-06-08T08:10:11.239976Z", + "index_finished_at": "2026-06-08T08:10:15.138869Z" + } + }, + { + "id": "db415368-a778-471d-9124-19b37289e59e", + "filename": "load-valid-001.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:09:59.396961Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:46.485228Z", + "extract_enqueued_at": "2026-06-08T08:09:46.485228Z", + "extract_started_at": "2026-06-08T08:09:48.753480Z", + "extract_finished_at": "2026-06-08T08:09:49.999550Z", + "index_enqueued_at": "2026-06-08T08:09:50.564392Z", + "index_started_at": "2026-06-08T08:09:53.081145Z", + "index_finished_at": "2026-06-08T08:09:59.396961Z" + } + }, + { + "id": "f71d14a2-2517-49e9-b0b0-8cc90d11c793", + "filename": "load-valid-002.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:09:59.714413Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:47.692083Z", + "extract_enqueued_at": "2026-06-08T08:09:47.692083Z", + "extract_started_at": "2026-06-08T08:09:50.183154Z", + "extract_finished_at": "2026-06-08T08:09:51.147375Z", + "index_enqueued_at": "2026-06-08T08:09:51.755669Z", + "index_started_at": "2026-06-08T08:09:54.242010Z", + "index_finished_at": "2026-06-08T08:09:59.714413Z" + } + }, + { + "id": "5c049412-054f-42e1-9e13-023823e8aad2", + "filename": "load-valid-003.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:02.158083Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:48.931801Z", + "extract_enqueued_at": "2026-06-08T08:09:48.931801Z", + "extract_started_at": "2026-06-08T08:09:51.423905Z", + "extract_finished_at": "2026-06-08T08:09:52.743102Z", + "index_enqueued_at": "2026-06-08T08:09:53.324472Z", + "index_started_at": "2026-06-08T08:09:56.669129Z", + "index_finished_at": "2026-06-08T08:10:02.158083Z" + } + }, + { + "id": "b9073464-28ec-476a-bac8-0971193d9f2f", + "filename": "load-valid-009.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:16.558621Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:56.108341Z", + "extract_enqueued_at": "2026-06-08T08:09:56.108341Z", + "extract_started_at": "2026-06-08T08:09:59.054110Z", + "extract_finished_at": "2026-06-08T08:10:00.153835Z", + "index_enqueued_at": "2026-06-08T08:10:00.722025Z", + "index_started_at": "2026-06-08T08:10:12.572818Z", + "index_finished_at": "2026-06-08T08:10:16.558621Z" + } + }, + { + "id": "148116bf-a6c2-4935-a2fa-48ad7a0c31ee", + "filename": "load-valid-010.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:22.237615Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:57.430426Z", + "extract_enqueued_at": "2026-06-08T08:09:57.430426Z", + "extract_started_at": "2026-06-08T08:10:00.700838Z", + "extract_finished_at": "2026-06-08T08:10:01.996813Z", + "index_enqueued_at": "2026-06-08T08:10:02.578208Z", + "index_started_at": "2026-06-08T08:10:17.718603Z", + "index_finished_at": "2026-06-08T08:10:22.237615Z" + } + }, + { + "id": "9232b00d-0b56-4eca-a273-e569546850c4", + "filename": "load-valid-004.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:07.515406Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:50.121905Z", + "extract_enqueued_at": "2026-06-08T08:09:50.121905Z", + "extract_started_at": "2026-06-08T08:09:52.669554Z", + "extract_finished_at": "2026-06-08T08:09:53.822766Z", + "index_enqueued_at": "2026-06-08T08:09:54.406964Z", + "index_started_at": "2026-06-08T08:10:03.105459Z", + "index_finished_at": "2026-06-08T08:10:07.515406Z" + } + }, + { + "id": "dd32e7ab-cd98-4324-8787-5750afe6f34e", + "filename": "load-valid-005.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:07.940086Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:51.290543Z", + "extract_enqueued_at": "2026-06-08T08:09:51.290543Z", + "extract_started_at": "2026-06-08T08:09:53.873697Z", + "extract_finished_at": "2026-06-08T08:09:55.047888Z", + "index_enqueued_at": "2026-06-08T08:09:55.650679Z", + "index_started_at": "2026-06-08T08:10:03.140619Z", + "index_finished_at": "2026-06-08T08:10:07.940086Z" + } + }, + { + "id": "f5e94ccb-2c27-4772-8c19-652dad8bbb73", + "filename": "load-valid-006.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:09.552363Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:52.473267Z", + "extract_enqueued_at": "2026-06-08T08:09:52.473267Z", + "extract_started_at": "2026-06-08T08:09:55.219272Z", + "extract_finished_at": "2026-06-08T08:09:56.375791Z", + "index_enqueued_at": "2026-06-08T08:09:56.959675Z", + "index_started_at": "2026-06-08T08:10:05.404586Z", + "index_finished_at": "2026-06-08T08:10:09.552363Z" + } + }, + { + "id": "81ac5061-3791-4176-857d-96ce7fb97c92", + "filename": "terms-and-conditions-template.pdf", + "content_type": "application/pdf", + "file_size_bytes": 50399, + "page_count": 2, + "ingestion_run_id": "841b78f4-4233-4bf9-9cfe-e2945c32d0f4", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-07T19:05:02.193118Z", + "updated_at": "2026-06-07T19:05:22.315615Z", + "timing": { + "upload_completed_at": "2026-06-07T19:05:06.456797Z", + "extract_enqueued_at": "2026-06-07T19:05:06.456797Z", + "extract_started_at": "2026-06-07T19:05:10.155645Z", + "extract_finished_at": "2026-06-07T19:05:11.932408Z", + "index_enqueued_at": "2026-06-07T19:05:12.620190Z", + "index_started_at": "2026-06-07T19:05:15.937121Z", + "index_finished_at": "2026-06-07T19:05:22.315615Z" + } + } + ], + "limit": 100, + "offset": 0, + "total": 36 + }, + "queue_snapshots": [ + { + "elapsed_seconds": 72.06, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 2, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 12, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 2, + "extracting": 1, + "indexing": 14, + "ready": 0, + "indexed": 8, + "failed": 0, + "total": 25 + } + }, + { + "elapsed_seconds": 79.51, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 11, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 14, + "ready": 0, + "indexed": 11, + "failed": 0, + "total": 25 + } + }, + { + "elapsed_seconds": 87.058, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 8, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 11, + "ready": 0, + "indexed": 14, + "failed": 0, + "total": 25 + } + }, + { + "elapsed_seconds": 94.637, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 5, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 8, + "ready": 0, + "indexed": 17, + "failed": 0, + "total": 25 + } + }, + { + "elapsed_seconds": 102.151, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 2, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 5, + "ready": 0, + "indexed": 20, + "failed": 0, + "total": 25 + } + }, + { + "elapsed_seconds": 109.587, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 0, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 4, + "ready": 0, + "indexed": 21, + "failed": 0, + "total": 25 + } + }, + { + "elapsed_seconds": 117.001, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 1, + "ready": 0, + "indexed": 24, + "failed": 0, + "total": 25 + } + }, + { + "elapsed_seconds": 124.507, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "completed", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 0, + "ready": 0, + "indexed": 25, + "failed": 0, + "total": 25 + } + } + ], + "durations_seconds": { + "prepare_upload_complete": 69.595, + "total": 125.879 + } +} diff --git a/artifacts/ingestion-load/ingestion-load-valid-50-20260608T083216Z.json b/artifacts/ingestion-load/ingestion-load-valid-50-20260608T083216Z.json new file mode 100644 index 0000000..44379c3 --- /dev/null +++ b/artifacts/ingestion-load/ingestion-load-valid-50-20260608T083216Z.json @@ -0,0 +1,3548 @@ +{ + "generated_at": "2026-06-08T08:28:23.283067+00:00", + "api_base": "http://localhost:8000", + "scenario": "valid", + "requested_count": 50, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "prepare": { + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "bucket": "documents", + "expires_in": 600, + "accepted_count": 50, + "rejected_count": 0, + "items": [ + { + "index": 0, + "filename": "load-valid-001.pdf", + "client_file_id": "load-valid-001", + "status": "prepared", + "document_id": "746a798f-2c64-4790-8961-4deddde8837c", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/746a798f-2c64-4790-8961-4deddde8837c/load-valid-001.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 1, + "filename": "load-valid-002.pdf", + "client_file_id": "load-valid-002", + "status": "prepared", + "document_id": "e5475fa0-4383-44ae-b655-f9fe240a764a", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/e5475fa0-4383-44ae-b655-f9fe240a764a/load-valid-002.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 2, + "filename": "load-valid-003.pdf", + "client_file_id": "load-valid-003", + "status": "prepared", + "document_id": "73812e3d-634a-4d79-9677-46c2b8297c0f", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/73812e3d-634a-4d79-9677-46c2b8297c0f/load-valid-003.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 3, + "filename": "load-valid-004.pdf", + "client_file_id": "load-valid-004", + "status": "prepared", + "document_id": "81a2f229-3c03-4d07-9e19-801c2757e91a", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/81a2f229-3c03-4d07-9e19-801c2757e91a/load-valid-004.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 4, + "filename": "load-valid-005.pdf", + "client_file_id": "load-valid-005", + "status": "prepared", + "document_id": "e88e1f1a-4ef8-4b3e-90ab-55c5725200b1", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/e88e1f1a-4ef8-4b3e-90ab-55c5725200b1/load-valid-005.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 5, + "filename": "load-valid-006.pdf", + "client_file_id": "load-valid-006", + "status": "prepared", + "document_id": "d41ef501-8c84-4861-8493-0d895ff24c80", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/d41ef501-8c84-4861-8493-0d895ff24c80/load-valid-006.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 6, + "filename": "load-valid-007.pdf", + "client_file_id": "load-valid-007", + "status": "prepared", + "document_id": "c75efb8b-dd3b-452a-abe2-4743b65516de", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/c75efb8b-dd3b-452a-abe2-4743b65516de/load-valid-007.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 7, + "filename": "load-valid-008.pdf", + "client_file_id": "load-valid-008", + "status": "prepared", + "document_id": "24e48042-d56e-4338-be7d-7b0c57ca4a2d", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/24e48042-d56e-4338-be7d-7b0c57ca4a2d/load-valid-008.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 8, + "filename": "load-valid-009.pdf", + "client_file_id": "load-valid-009", + "status": "prepared", + "document_id": "64723b16-b7f3-4257-a760-40c64e1d80a5", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/64723b16-b7f3-4257-a760-40c64e1d80a5/load-valid-009.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 9, + "filename": "load-valid-010.pdf", + "client_file_id": "load-valid-010", + "status": "prepared", + "document_id": "a7f3ce2b-a858-4a3e-acc8-f5395e12bf04", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/a7f3ce2b-a858-4a3e-acc8-f5395e12bf04/load-valid-010.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 10, + "filename": "load-valid-011.pdf", + "client_file_id": "load-valid-011", + "status": "prepared", + "document_id": "d77b7f0a-1577-4342-84a6-784ee75cac45", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/d77b7f0a-1577-4342-84a6-784ee75cac45/load-valid-011.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 11, + "filename": "load-valid-012.pdf", + "client_file_id": "load-valid-012", + "status": "prepared", + "document_id": "76fb1c0a-b980-4ade-b6a0-153ea64b88bc", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/76fb1c0a-b980-4ade-b6a0-153ea64b88bc/load-valid-012.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 12, + "filename": "load-valid-013.pdf", + "client_file_id": "load-valid-013", + "status": "prepared", + "document_id": "48239aa7-616a-4ead-850e-6538833041b6", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/48239aa7-616a-4ead-850e-6538833041b6/load-valid-013.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 13, + "filename": "load-valid-014.pdf", + "client_file_id": "load-valid-014", + "status": "prepared", + "document_id": "8726fbd1-f24d-48f4-8840-958cc0cb3f12", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/8726fbd1-f24d-48f4-8840-958cc0cb3f12/load-valid-014.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 14, + "filename": "load-valid-015.pdf", + "client_file_id": "load-valid-015", + "status": "prepared", + "document_id": "99cf77ef-f803-4815-9ba1-37ff8313d3c0", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/99cf77ef-f803-4815-9ba1-37ff8313d3c0/load-valid-015.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 15, + "filename": "load-valid-016.pdf", + "client_file_id": "load-valid-016", + "status": "prepared", + "document_id": "6a5307d6-d01c-4dcb-bde5-779d2dfc571c", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/6a5307d6-d01c-4dcb-bde5-779d2dfc571c/load-valid-016.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 16, + "filename": "load-valid-017.pdf", + "client_file_id": "load-valid-017", + "status": "prepared", + "document_id": "8b7f4266-3eb1-4105-8b77-8a54580a4941", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/8b7f4266-3eb1-4105-8b77-8a54580a4941/load-valid-017.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 17, + "filename": "load-valid-018.pdf", + "client_file_id": "load-valid-018", + "status": "prepared", + "document_id": "440b41e5-5b6d-4c82-ba59-9dd24a9a606b", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/440b41e5-5b6d-4c82-ba59-9dd24a9a606b/load-valid-018.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 18, + "filename": "load-valid-019.pdf", + "client_file_id": "load-valid-019", + "status": "prepared", + "document_id": "f73bde1e-26fc-4901-861b-a2aa2fe27202", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/f73bde1e-26fc-4901-861b-a2aa2fe27202/load-valid-019.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 19, + "filename": "load-valid-020.pdf", + "client_file_id": "load-valid-020", + "status": "prepared", + "document_id": "9d19db64-69cd-4263-a27a-6d0b7348f2f0", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/9d19db64-69cd-4263-a27a-6d0b7348f2f0/load-valid-020.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 20, + "filename": "load-valid-021.pdf", + "client_file_id": "load-valid-021", + "status": "prepared", + "document_id": "3876ca63-1951-473d-aeec-f78498386fda", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/3876ca63-1951-473d-aeec-f78498386fda/load-valid-021.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 21, + "filename": "load-valid-022.pdf", + "client_file_id": "load-valid-022", + "status": "prepared", + "document_id": "5e9c11d9-669d-4802-871a-cb3418546678", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/5e9c11d9-669d-4802-871a-cb3418546678/load-valid-022.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 22, + "filename": "load-valid-023.pdf", + "client_file_id": "load-valid-023", + "status": "prepared", + "document_id": "81a59675-517f-4a65-b9f5-aa54a12bf25d", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/81a59675-517f-4a65-b9f5-aa54a12bf25d/load-valid-023.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 23, + "filename": "load-valid-024.pdf", + "client_file_id": "load-valid-024", + "status": "prepared", + "document_id": "4c9a965d-3f25-4792-b655-a35161d02d11", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/4c9a965d-3f25-4792-b655-a35161d02d11/load-valid-024.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 24, + "filename": "load-valid-025.pdf", + "client_file_id": "load-valid-025", + "status": "prepared", + "document_id": "79320a71-1319-4e0a-9d37-4b41cb6d3d78", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/79320a71-1319-4e0a-9d37-4b41cb6d3d78/load-valid-025.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 25, + "filename": "load-valid-026.pdf", + "client_file_id": "load-valid-026", + "status": "prepared", + "document_id": "4e976fd4-c455-40b9-a606-98b84151a520", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/4e976fd4-c455-40b9-a606-98b84151a520/load-valid-026.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 26, + "filename": "load-valid-027.pdf", + "client_file_id": "load-valid-027", + "status": "prepared", + "document_id": "87e5dde7-8589-49cd-b311-5558ddc16897", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/87e5dde7-8589-49cd-b311-5558ddc16897/load-valid-027.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 27, + "filename": "load-valid-028.pdf", + "client_file_id": "load-valid-028", + "status": "prepared", + "document_id": "1c836e81-f2c5-4593-a540-106c98bb4a3d", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/1c836e81-f2c5-4593-a540-106c98bb4a3d/load-valid-028.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 28, + "filename": "load-valid-029.pdf", + "client_file_id": "load-valid-029", + "status": "prepared", + "document_id": "84a1faed-d481-4812-b45b-3a32b2c8b16a", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/84a1faed-d481-4812-b45b-3a32b2c8b16a/load-valid-029.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 29, + "filename": "load-valid-030.pdf", + "client_file_id": "load-valid-030", + "status": "prepared", + "document_id": "76b16bcc-01a6-446f-8036-4deaa4dd66fe", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/76b16bcc-01a6-446f-8036-4deaa4dd66fe/load-valid-030.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 30, + "filename": "load-valid-031.pdf", + "client_file_id": "load-valid-031", + "status": "prepared", + "document_id": "8d1e7ff4-5011-4277-b4e3-d6b3e88105e5", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/8d1e7ff4-5011-4277-b4e3-d6b3e88105e5/load-valid-031.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 31, + "filename": "load-valid-032.pdf", + "client_file_id": "load-valid-032", + "status": "prepared", + "document_id": "386e11ef-ae15-4591-99a9-70df5f710c6d", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/386e11ef-ae15-4591-99a9-70df5f710c6d/load-valid-032.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 32, + "filename": "load-valid-033.pdf", + "client_file_id": "load-valid-033", + "status": "prepared", + "document_id": "13ca3ad5-9054-4e6d-9342-d01387e72925", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/13ca3ad5-9054-4e6d-9342-d01387e72925/load-valid-033.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 33, + "filename": "load-valid-034.pdf", + "client_file_id": "load-valid-034", + "status": "prepared", + "document_id": "b88e79e8-1078-488d-87fa-e4a667124316", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/b88e79e8-1078-488d-87fa-e4a667124316/load-valid-034.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 34, + "filename": "load-valid-035.pdf", + "client_file_id": "load-valid-035", + "status": "prepared", + "document_id": "2c714d2b-67be-4eab-a9a2-70957e3341f3", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/2c714d2b-67be-4eab-a9a2-70957e3341f3/load-valid-035.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 35, + "filename": "load-valid-036.pdf", + "client_file_id": "load-valid-036", + "status": "prepared", + "document_id": "243bd62b-6188-47f3-9003-d6077743c305", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/243bd62b-6188-47f3-9003-d6077743c305/load-valid-036.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 36, + "filename": "load-valid-037.pdf", + "client_file_id": "load-valid-037", + "status": "prepared", + "document_id": "2ecd4e53-7ad5-4823-a57f-3e45304268d2", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/2ecd4e53-7ad5-4823-a57f-3e45304268d2/load-valid-037.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 37, + "filename": "load-valid-038.pdf", + "client_file_id": "load-valid-038", + "status": "prepared", + "document_id": "85aa233e-c293-4ea0-ba09-0d95931d82e0", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/85aa233e-c293-4ea0-ba09-0d95931d82e0/load-valid-038.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 38, + "filename": "load-valid-039.pdf", + "client_file_id": "load-valid-039", + "status": "prepared", + "document_id": "2941f441-dbd6-4705-95f5-4b6317c6289c", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/2941f441-dbd6-4705-95f5-4b6317c6289c/load-valid-039.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 39, + "filename": "load-valid-040.pdf", + "client_file_id": "load-valid-040", + "status": "prepared", + "document_id": "13507953-6e88-44dc-bbb4-4523c925de21", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/13507953-6e88-44dc-bbb4-4523c925de21/load-valid-040.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 40, + "filename": "load-valid-041.pdf", + "client_file_id": "load-valid-041", + "status": "prepared", + "document_id": "38cc5bef-c0b6-4a72-9b5e-b62514c4c350", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/38cc5bef-c0b6-4a72-9b5e-b62514c4c350/load-valid-041.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 41, + "filename": "load-valid-042.pdf", + "client_file_id": "load-valid-042", + "status": "prepared", + "document_id": "d22ac7e0-36af-4d74-b39c-68c313718b6c", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/d22ac7e0-36af-4d74-b39c-68c313718b6c/load-valid-042.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 42, + "filename": "load-valid-043.pdf", + "client_file_id": "load-valid-043", + "status": "prepared", + "document_id": "be8c6ad8-bf7c-43da-b14d-bac5f133be66", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/be8c6ad8-bf7c-43da-b14d-bac5f133be66/load-valid-043.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 43, + "filename": "load-valid-044.pdf", + "client_file_id": "load-valid-044", + "status": "prepared", + "document_id": "311e98bb-e5d3-4555-88d8-4fc7cbdeb6d3", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/311e98bb-e5d3-4555-88d8-4fc7cbdeb6d3/load-valid-044.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 44, + "filename": "load-valid-045.pdf", + "client_file_id": "load-valid-045", + "status": "prepared", + "document_id": "c6ab16ec-c99f-4f0b-ba1f-3904b2e2d714", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/c6ab16ec-c99f-4f0b-ba1f-3904b2e2d714/load-valid-045.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 45, + "filename": "load-valid-046.pdf", + "client_file_id": "load-valid-046", + "status": "prepared", + "document_id": "c2b70d14-1d26-4b97-8947-ce40a67f6ed0", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/c2b70d14-1d26-4b97-8947-ce40a67f6ed0/load-valid-046.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 46, + "filename": "load-valid-047.pdf", + "client_file_id": "load-valid-047", + "status": "prepared", + "document_id": "198e2de4-7355-494e-abf7-e03accde514e", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/198e2de4-7355-494e-abf7-e03accde514e/load-valid-047.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 47, + "filename": "load-valid-048.pdf", + "client_file_id": "load-valid-048", + "status": "prepared", + "document_id": "e16e94c2-e481-4c50-aa22-94e6960cc117", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/e16e94c2-e481-4c50-aa22-94e6960cc117/load-valid-048.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 48, + "filename": "load-valid-049.pdf", + "client_file_id": "load-valid-049", + "status": "prepared", + "document_id": "945b4dbb-64e8-45c8-aaaa-d3c7c2afe364", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/945b4dbb-64e8-45c8-aaaa-d3c7c2afe364/load-valid-049.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + }, + { + "index": 49, + "filename": "load-valid-050.pdf", + "client_file_id": "load-valid-050", + "status": "prepared", + "document_id": "21368db7-3689-4d43-ae04-a65cd6bd6e11", + "bucket": "documents", + "storage_path": "aa110712-618b-4bba-8556-e256b8aa4df0/21368db7-3689-4d43-ae04-a65cd6bd6e11/load-valid-050.pdf", + "upload_url": "[redacted]", + "expires_in": 600, + "error": null + } + ] + }, + "complete": { + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "accepted_count": 50, + "failed_count": 0, + "items": [ + { + "index": 0, + "document_id": "746a798f-2c64-4790-8961-4deddde8837c", + "status": "uploaded", + "job_id": "da0391d1-fc36-44f5-bb21-f2ed4c483c53", + "error": null + }, + { + "index": 1, + "document_id": "e5475fa0-4383-44ae-b655-f9fe240a764a", + "status": "uploaded", + "job_id": "f45e9616-ecc9-4b90-a2ce-c9dbe2544ee5", + "error": null + }, + { + "index": 2, + "document_id": "73812e3d-634a-4d79-9677-46c2b8297c0f", + "status": "uploaded", + "job_id": "17ea89cd-2f6b-4a2f-b7f2-4dd1c9e909e6", + "error": null + }, + { + "index": 3, + "document_id": "81a2f229-3c03-4d07-9e19-801c2757e91a", + "status": "uploaded", + "job_id": "2bd574cc-f7d2-4819-a18d-e593363a1c05", + "error": null + }, + { + "index": 4, + "document_id": "e88e1f1a-4ef8-4b3e-90ab-55c5725200b1", + "status": "uploaded", + "job_id": "780a4e10-136f-41e3-80e5-28f18d31a261", + "error": null + }, + { + "index": 5, + "document_id": "d41ef501-8c84-4861-8493-0d895ff24c80", + "status": "uploaded", + "job_id": "13df86f1-7625-4ebd-b8a8-7ca1e99b823a", + "error": null + }, + { + "index": 6, + "document_id": "c75efb8b-dd3b-452a-abe2-4743b65516de", + "status": "uploaded", + "job_id": "13603f7b-2f0f-4430-b454-8be7fe8c7b84", + "error": null + }, + { + "index": 7, + "document_id": "24e48042-d56e-4338-be7d-7b0c57ca4a2d", + "status": "uploaded", + "job_id": "7147c812-968c-4902-8516-1da895a2c676", + "error": null + }, + { + "index": 8, + "document_id": "64723b16-b7f3-4257-a760-40c64e1d80a5", + "status": "uploaded", + "job_id": "d86e9b70-4bff-411c-bfd1-bbc7cf894050", + "error": null + }, + { + "index": 9, + "document_id": "a7f3ce2b-a858-4a3e-acc8-f5395e12bf04", + "status": "uploaded", + "job_id": "6f0dbb54-e95c-4cb5-a0b4-2fb2e1aa3489", + "error": null + }, + { + "index": 10, + "document_id": "d77b7f0a-1577-4342-84a6-784ee75cac45", + "status": "uploaded", + "job_id": "3f16989e-e64b-4545-8791-b77c5de2a4d0", + "error": null + }, + { + "index": 11, + "document_id": "76fb1c0a-b980-4ade-b6a0-153ea64b88bc", + "status": "uploaded", + "job_id": "2e09b0b8-1848-419e-8366-144231a3a960", + "error": null + }, + { + "index": 12, + "document_id": "48239aa7-616a-4ead-850e-6538833041b6", + "status": "uploaded", + "job_id": "d7fbe01e-13f9-4e89-abb5-54da675d605a", + "error": null + }, + { + "index": 13, + "document_id": "8726fbd1-f24d-48f4-8840-958cc0cb3f12", + "status": "uploaded", + "job_id": "4f4e2489-e9a3-481d-84e3-c79cfe6e9408", + "error": null + }, + { + "index": 14, + "document_id": "99cf77ef-f803-4815-9ba1-37ff8313d3c0", + "status": "uploaded", + "job_id": "e6454bde-b136-461d-abf3-8ca70017457c", + "error": null + }, + { + "index": 15, + "document_id": "6a5307d6-d01c-4dcb-bde5-779d2dfc571c", + "status": "uploaded", + "job_id": "addcb243-c886-4794-abab-ca287bf2571b", + "error": null + }, + { + "index": 16, + "document_id": "8b7f4266-3eb1-4105-8b77-8a54580a4941", + "status": "uploaded", + "job_id": "791b03ea-27aa-449c-9dea-8777450a17d2", + "error": null + }, + { + "index": 17, + "document_id": "440b41e5-5b6d-4c82-ba59-9dd24a9a606b", + "status": "uploaded", + "job_id": "ada7b6a5-606b-49c9-98db-74695e119b0c", + "error": null + }, + { + "index": 18, + "document_id": "f73bde1e-26fc-4901-861b-a2aa2fe27202", + "status": "uploaded", + "job_id": "44d686b3-a34a-4cef-8ca1-18d467a88be4", + "error": null + }, + { + "index": 19, + "document_id": "9d19db64-69cd-4263-a27a-6d0b7348f2f0", + "status": "uploaded", + "job_id": "e0781ad0-6e13-4796-812d-d55abe41523f", + "error": null + }, + { + "index": 20, + "document_id": "3876ca63-1951-473d-aeec-f78498386fda", + "status": "uploaded", + "job_id": "fc8e6038-fdfd-43f8-9381-a078686fee75", + "error": null + }, + { + "index": 21, + "document_id": "5e9c11d9-669d-4802-871a-cb3418546678", + "status": "uploaded", + "job_id": "f7d55dfc-12ed-4284-99c9-0147e2efd237", + "error": null + }, + { + "index": 22, + "document_id": "81a59675-517f-4a65-b9f5-aa54a12bf25d", + "status": "uploaded", + "job_id": "fc9a4c1e-3b69-45ee-a2dc-837af0f2f3df", + "error": null + }, + { + "index": 23, + "document_id": "4c9a965d-3f25-4792-b655-a35161d02d11", + "status": "uploaded", + "job_id": "b4b050c4-168c-443d-91d9-1cd198dcc15c", + "error": null + }, + { + "index": 24, + "document_id": "79320a71-1319-4e0a-9d37-4b41cb6d3d78", + "status": "uploaded", + "job_id": "1d86c118-589f-4d79-9d51-2eeb2aaf2fa7", + "error": null + }, + { + "index": 25, + "document_id": "4e976fd4-c455-40b9-a606-98b84151a520", + "status": "uploaded", + "job_id": "bc19f1b4-bc88-4fe4-a17d-f0ff0869a05f", + "error": null + }, + { + "index": 26, + "document_id": "87e5dde7-8589-49cd-b311-5558ddc16897", + "status": "uploaded", + "job_id": "b6720e38-6ce5-42bb-9fbc-0823b99b3485", + "error": null + }, + { + "index": 27, + "document_id": "1c836e81-f2c5-4593-a540-106c98bb4a3d", + "status": "uploaded", + "job_id": "f67cb077-8f23-499d-a185-2bed68d69a68", + "error": null + }, + { + "index": 28, + "document_id": "84a1faed-d481-4812-b45b-3a32b2c8b16a", + "status": "uploaded", + "job_id": "daab0522-e298-46ca-b24a-94db00ecacaa", + "error": null + }, + { + "index": 29, + "document_id": "76b16bcc-01a6-446f-8036-4deaa4dd66fe", + "status": "uploaded", + "job_id": "8e349658-be44-4c38-bcb8-7fd8e21bb08b", + "error": null + }, + { + "index": 30, + "document_id": "8d1e7ff4-5011-4277-b4e3-d6b3e88105e5", + "status": "uploaded", + "job_id": "7fb370d8-6e29-4e6f-809d-79a298605c23", + "error": null + }, + { + "index": 31, + "document_id": "386e11ef-ae15-4591-99a9-70df5f710c6d", + "status": "uploaded", + "job_id": "30acd929-cd2f-43cb-9f60-5e165ab6afc0", + "error": null + }, + { + "index": 32, + "document_id": "13ca3ad5-9054-4e6d-9342-d01387e72925", + "status": "uploaded", + "job_id": "e9ddaf5b-21ad-4408-b2cc-edea0f26bc89", + "error": null + }, + { + "index": 33, + "document_id": "b88e79e8-1078-488d-87fa-e4a667124316", + "status": "uploaded", + "job_id": "bee7160d-7a03-498c-ac25-fc40893429fd", + "error": null + }, + { + "index": 34, + "document_id": "2c714d2b-67be-4eab-a9a2-70957e3341f3", + "status": "uploaded", + "job_id": "eadabfc8-cb69-44ea-afda-6cda3b5c1642", + "error": null + }, + { + "index": 35, + "document_id": "243bd62b-6188-47f3-9003-d6077743c305", + "status": "uploaded", + "job_id": "6cb3bb8b-5a9c-48ba-84ce-46e2ef0bd96c", + "error": null + }, + { + "index": 36, + "document_id": "2ecd4e53-7ad5-4823-a57f-3e45304268d2", + "status": "uploaded", + "job_id": "8cd81883-02a6-47af-a8c8-22db4c1071f3", + "error": null + }, + { + "index": 37, + "document_id": "85aa233e-c293-4ea0-ba09-0d95931d82e0", + "status": "uploaded", + "job_id": "fb2b8d84-f56a-49c1-9409-b6c8412353d1", + "error": null + }, + { + "index": 38, + "document_id": "2941f441-dbd6-4705-95f5-4b6317c6289c", + "status": "uploaded", + "job_id": "d3e830f9-5a9f-4b35-a105-37b45e815871", + "error": null + }, + { + "index": 39, + "document_id": "13507953-6e88-44dc-bbb4-4523c925de21", + "status": "uploaded", + "job_id": "e068f385-9233-40fd-a0e6-ef18b30e51f4", + "error": null + }, + { + "index": 40, + "document_id": "38cc5bef-c0b6-4a72-9b5e-b62514c4c350", + "status": "uploaded", + "job_id": "3a58091b-bba3-4fd2-bdd1-d10089d5939b", + "error": null + }, + { + "index": 41, + "document_id": "d22ac7e0-36af-4d74-b39c-68c313718b6c", + "status": "uploaded", + "job_id": "e1fddea6-1965-4e95-be1d-3315dc13a762", + "error": null + }, + { + "index": 42, + "document_id": "be8c6ad8-bf7c-43da-b14d-bac5f133be66", + "status": "uploaded", + "job_id": "dce8031d-08e7-4c2c-bb0a-25c03d6dfd6d", + "error": null + }, + { + "index": 43, + "document_id": "311e98bb-e5d3-4555-88d8-4fc7cbdeb6d3", + "status": "uploaded", + "job_id": "3b246220-c109-4fdb-9af8-64fbc1576eda", + "error": null + }, + { + "index": 44, + "document_id": "c6ab16ec-c99f-4f0b-ba1f-3904b2e2d714", + "status": "uploaded", + "job_id": "e64f2612-8b2e-40c3-81b7-f3524901b2d1", + "error": null + }, + { + "index": 45, + "document_id": "c2b70d14-1d26-4b97-8947-ce40a67f6ed0", + "status": "uploaded", + "job_id": "5b2f10e1-ee6a-4951-8008-94e36503807e", + "error": null + }, + { + "index": 46, + "document_id": "198e2de4-7355-494e-abf7-e03accde514e", + "status": "uploaded", + "job_id": "0fc165a6-c278-41dc-8394-02e1f64158c1", + "error": null + }, + { + "index": 47, + "document_id": "e16e94c2-e481-4c50-aa22-94e6960cc117", + "status": "uploaded", + "job_id": "9ee4fdcf-b02c-484a-b57c-917166b6980f", + "error": null + }, + { + "index": 48, + "document_id": "945b4dbb-64e8-45c8-aaaa-d3c7c2afe364", + "status": "uploaded", + "job_id": "02fe10c0-9aa4-46f0-ba60-16a3aa615a82", + "error": null + }, + { + "index": 49, + "document_id": "21368db7-3689-4d43-ae04-a65cd6bd6e11", + "status": "uploaded", + "job_id": "638c30c2-9e84-45ec-afb2-b5f7c8156370", + "error": null + } + ] + }, + "final_run": { + "id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "name": "load-valid-50-2026-06-08T08:28:23.283067+00:00", + "status": "completed", + "total_documents": 50, + "accepted_documents": 50, + "rejected_documents": 0, + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 0, + "ready": 0, + "indexed": 50, + "failed": 0, + "total": 50 + }, + "created_at": "2026-06-08T08:28:52.526251Z", + "updated_at": "2026-06-08T08:32:13.818013Z" + }, + "documents": { + "items": [ + { + "id": "24e48042-d56e-4338-be7d-7b0c57ca4a2d", + "filename": "load-valid-008.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:03.030936Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:35.685638Z", + "extract_enqueued_at": "2026-06-08T08:29:35.685638Z", + "extract_started_at": "2026-06-08T08:29:39.674755Z", + "extract_finished_at": "2026-06-08T08:29:40.915971Z", + "index_enqueued_at": "2026-06-08T08:29:41.509695Z", + "index_started_at": "2026-06-08T08:29:58.067967Z", + "index_finished_at": "2026-06-08T08:30:03.030936Z" + } + }, + { + "id": "e88e1f1a-4ef8-4b3e-90ab-55c5725200b1", + "filename": "load-valid-005.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:29:53.140630Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:32.066509Z", + "extract_enqueued_at": "2026-06-08T08:29:32.066509Z", + "extract_started_at": "2026-06-08T08:29:35.351507Z", + "extract_finished_at": "2026-06-08T08:29:36.447882Z", + "index_enqueued_at": "2026-06-08T08:29:37.038554Z", + "index_started_at": "2026-06-08T08:29:47.578474Z", + "index_finished_at": "2026-06-08T08:29:53.140630Z" + } + }, + { + "id": "e5475fa0-4383-44ae-b655-f9fe240a764a", + "filename": "load-valid-002.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:29:42.112042Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:28.417369Z", + "extract_enqueued_at": "2026-06-08T08:29:28.417369Z", + "extract_started_at": "2026-06-08T08:29:31.518341Z", + "extract_finished_at": "2026-06-08T08:29:32.646991Z", + "index_enqueued_at": "2026-06-08T08:29:33.259379Z", + "index_started_at": "2026-06-08T08:29:36.717916Z", + "index_finished_at": "2026-06-08T08:29:42.112042Z" + } + }, + { + "id": "73812e3d-634a-4d79-9677-46c2b8297c0f", + "filename": "load-valid-003.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:29:43.022768Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:29.639405Z", + "extract_enqueued_at": "2026-06-08T08:29:29.639405Z", + "extract_started_at": "2026-06-08T08:29:32.546122Z", + "extract_finished_at": "2026-06-08T08:29:33.779548Z", + "index_enqueued_at": "2026-06-08T08:29:34.397079Z", + "index_started_at": "2026-06-08T08:29:37.811536Z", + "index_finished_at": "2026-06-08T08:29:43.022768Z" + } + }, + { + "id": "81a2f229-3c03-4d07-9e19-801c2757e91a", + "filename": "load-valid-004.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:29:51.431456Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:30.843633Z", + "extract_enqueued_at": "2026-06-08T08:29:30.843633Z", + "extract_started_at": "2026-06-08T08:29:34.007136Z", + "extract_finished_at": "2026-06-08T08:29:35.098046Z", + "index_enqueued_at": "2026-06-08T08:29:35.683699Z", + "index_started_at": "2026-06-08T08:29:46.352926Z", + "index_finished_at": "2026-06-08T08:29:51.431456Z" + } + }, + { + "id": "13507953-6e88-44dc-bbb4-4523c925de21", + "filename": "load-valid-040.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:44.026248Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:17.429290Z", + "extract_enqueued_at": "2026-06-08T08:30:17.429290Z", + "extract_started_at": "2026-06-08T08:30:22.306499Z", + "extract_finished_at": "2026-06-08T08:30:23.492080Z", + "index_enqueued_at": "2026-06-08T08:30:24.190063Z", + "index_started_at": "2026-06-08T08:31:39.535235Z", + "index_finished_at": "2026-06-08T08:31:44.026248Z" + } + }, + { + "id": "c75efb8b-dd3b-452a-abe2-4743b65516de", + "filename": "load-valid-007.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:01.752920Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:34.502144Z", + "extract_enqueued_at": "2026-06-08T08:29:34.502144Z", + "extract_started_at": "2026-06-08T08:29:37.840928Z", + "extract_finished_at": "2026-06-08T08:29:39.022915Z", + "index_enqueued_at": "2026-06-08T08:29:39.592595Z", + "index_started_at": "2026-06-08T08:29:56.971992Z", + "index_finished_at": "2026-06-08T08:30:01.752920Z" + } + }, + { + "id": "440b41e5-5b6d-4c82-ba59-9dd24a9a606b", + "filename": "load-valid-018.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:37.911396Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:48.532522Z", + "extract_enqueued_at": "2026-06-08T08:29:48.532522Z", + "extract_started_at": "2026-06-08T08:29:54.122443Z", + "extract_finished_at": "2026-06-08T08:29:55.289960Z", + "index_enqueued_at": "2026-06-08T08:29:55.879083Z", + "index_started_at": "2026-06-08T08:30:32.358009Z", + "index_finished_at": "2026-06-08T08:30:37.911396Z" + } + }, + { + "id": "48239aa7-616a-4ead-850e-6538833041b6", + "filename": "load-valid-013.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:24.246931Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:42.140348Z", + "extract_enqueued_at": "2026-06-08T08:29:42.140348Z", + "extract_started_at": "2026-06-08T08:29:46.596670Z", + "extract_finished_at": "2026-06-08T08:29:47.832081Z", + "index_enqueued_at": "2026-06-08T08:29:48.483301Z", + "index_started_at": "2026-06-08T08:30:18.546771Z", + "index_finished_at": "2026-06-08T08:30:24.246931Z" + } + }, + { + "id": "8726fbd1-f24d-48f4-8840-958cc0cb3f12", + "filename": "load-valid-014.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:26.195833Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:43.425647Z", + "extract_enqueued_at": "2026-06-08T08:29:43.425647Z", + "extract_started_at": "2026-06-08T08:29:47.706528Z", + "extract_finished_at": "2026-06-08T08:29:48.996687Z", + "index_enqueued_at": "2026-06-08T08:29:49.609079Z", + "index_started_at": "2026-06-08T08:30:20.418924Z", + "index_finished_at": "2026-06-08T08:30:26.195833Z" + } + }, + { + "id": "99cf77ef-f803-4815-9ba1-37ff8313d3c0", + "filename": "load-valid-015.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:26.634229Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:44.744118Z", + "extract_enqueued_at": "2026-06-08T08:29:44.744118Z", + "extract_started_at": "2026-06-08T08:29:48.985975Z", + "extract_finished_at": "2026-06-08T08:29:50.113346Z", + "index_enqueued_at": "2026-06-08T08:29:50.742356Z", + "index_started_at": "2026-06-08T08:30:20.690780Z", + "index_finished_at": "2026-06-08T08:30:26.634229Z" + } + }, + { + "id": "3876ca63-1951-473d-aeec-f78498386fda", + "filename": "load-valid-021.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:47.977644Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:52.430294Z", + "extract_enqueued_at": "2026-06-08T08:29:52.430294Z", + "extract_started_at": "2026-06-08T08:29:56.914655Z", + "extract_finished_at": "2026-06-08T08:29:58.102474Z", + "index_enqueued_at": "2026-06-08T08:29:58.675692Z", + "index_started_at": "2026-06-08T08:30:43.379226Z", + "index_finished_at": "2026-06-08T08:30:47.977644Z" + } + }, + { + "id": "c2b70d14-1d26-4b97-8947-ce40a67f6ed0", + "filename": "load-valid-046.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:58.918347Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:26.273092Z", + "extract_enqueued_at": "2026-06-08T08:30:26.273092Z", + "extract_started_at": "2026-06-08T08:30:31.536859Z", + "extract_finished_at": "2026-06-08T08:30:32.768302Z", + "index_enqueued_at": "2026-06-08T08:30:33.389625Z", + "index_started_at": "2026-06-08T08:31:54.768750Z", + "index_finished_at": "2026-06-08T08:31:58.918347Z" + } + }, + { + "id": "6a5307d6-d01c-4dcb-bde5-779d2dfc571c", + "filename": "load-valid-016.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:35.196280Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:45.997037Z", + "extract_enqueued_at": "2026-06-08T08:29:45.997037Z", + "extract_started_at": "2026-06-08T08:29:49.635179Z", + "extract_finished_at": "2026-06-08T08:29:51.057285Z", + "index_enqueued_at": "2026-06-08T08:29:51.686207Z", + "index_started_at": "2026-06-08T08:30:29.951572Z", + "index_finished_at": "2026-06-08T08:30:35.196280Z" + } + }, + { + "id": "8b7f4266-3eb1-4105-8b77-8a54580a4941", + "filename": "load-valid-017.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:37.464188Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:47.234327Z", + "extract_enqueued_at": "2026-06-08T08:29:47.234327Z", + "extract_started_at": "2026-06-08T08:29:51.433971Z", + "extract_finished_at": "2026-06-08T08:29:52.454733Z", + "index_enqueued_at": "2026-06-08T08:29:53.086095Z", + "index_started_at": "2026-06-08T08:30:31.949310Z", + "index_finished_at": "2026-06-08T08:30:37.464188Z" + } + }, + { + "id": "5e9c11d9-669d-4802-871a-cb3418546678", + "filename": "load-valid-022.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:53.553090Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:53.710049Z", + "extract_enqueued_at": "2026-06-08T08:29:53.710049Z", + "extract_started_at": "2026-06-08T08:29:57.866532Z", + "extract_finished_at": "2026-06-08T08:29:59.045313Z", + "index_enqueued_at": "2026-06-08T08:29:59.640726Z", + "index_started_at": "2026-06-08T08:30:49.107717Z", + "index_finished_at": "2026-06-08T08:30:53.553090Z" + } + }, + { + "id": "f73bde1e-26fc-4901-861b-a2aa2fe27202", + "filename": "load-valid-019.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:44.971628Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:49.954517Z", + "extract_enqueued_at": "2026-06-08T08:29:49.954517Z", + "extract_started_at": "2026-06-08T08:29:55.068356Z", + "extract_finished_at": "2026-06-08T08:29:56.173135Z", + "index_enqueued_at": "2026-06-08T08:29:56.753097Z", + "index_started_at": "2026-06-08T08:30:40.335152Z", + "index_finished_at": "2026-06-08T08:30:44.971628Z" + } + }, + { + "id": "9d19db64-69cd-4263-a27a-6d0b7348f2f0", + "filename": "load-valid-020.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:47.811596Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:51.234392Z", + "extract_enqueued_at": "2026-06-08T08:29:51.234392Z", + "extract_started_at": "2026-06-08T08:29:55.990155Z", + "extract_finished_at": "2026-06-08T08:29:56.969776Z", + "index_enqueued_at": "2026-06-08T08:29:57.536724Z", + "index_started_at": "2026-06-08T08:30:42.955786Z", + "index_finished_at": "2026-06-08T08:30:47.811596Z" + } + }, + { + "id": "76fb1c0a-b980-4ade-b6a0-153ea64b88bc", + "filename": "load-valid-012.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:15.272082Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:40.708957Z", + "extract_enqueued_at": "2026-06-08T08:29:40.708957Z", + "extract_started_at": "2026-06-08T08:29:45.063457Z", + "extract_finished_at": "2026-06-08T08:29:46.304487Z", + "index_enqueued_at": "2026-06-08T08:29:46.865685Z", + "index_started_at": "2026-06-08T08:30:10.037522Z", + "index_finished_at": "2026-06-08T08:30:15.272082Z" + } + }, + { + "id": "64723b16-b7f3-4257-a760-40c64e1d80a5", + "filename": "load-valid-009.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:03.476452Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:36.927215Z", + "extract_enqueued_at": "2026-06-08T08:29:36.927215Z", + "extract_started_at": "2026-06-08T08:29:41.787437Z", + "extract_finished_at": "2026-06-08T08:29:42.920740Z", + "index_enqueued_at": "2026-06-08T08:29:43.533141Z", + "index_started_at": "2026-06-08T08:29:58.347031Z", + "index_finished_at": "2026-06-08T08:30:03.476452Z" + } + }, + { + "id": "a7f3ce2b-a858-4a3e-acc8-f5395e12bf04", + "filename": "load-valid-010.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:13.413551Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:38.161655Z", + "extract_enqueued_at": "2026-06-08T08:29:38.161655Z", + "extract_started_at": "2026-06-08T08:29:42.089546Z", + "extract_finished_at": "2026-06-08T08:29:43.221156Z", + "index_enqueued_at": "2026-06-08T08:29:43.843834Z", + "index_started_at": "2026-06-08T08:30:08.492795Z", + "index_finished_at": "2026-06-08T08:30:13.413551Z" + } + }, + { + "id": "d77b7f0a-1577-4342-84a6-784ee75cac45", + "filename": "load-valid-011.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:15.084742Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:39.363743Z", + "extract_enqueued_at": "2026-06-08T08:29:39.363743Z", + "extract_started_at": "2026-06-08T08:29:43.020201Z", + "extract_finished_at": "2026-06-08T08:29:44.214105Z", + "index_enqueued_at": "2026-06-08T08:29:44.798980Z", + "index_started_at": "2026-06-08T08:30:09.785957Z", + "index_finished_at": "2026-06-08T08:30:15.084742Z" + } + }, + { + "id": "81a59675-517f-4a65-b9f5-aa54a12bf25d", + "filename": "load-valid-023.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:56.159861Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:54.981892Z", + "extract_enqueued_at": "2026-06-08T08:29:54.981892Z", + "extract_started_at": "2026-06-08T08:29:59.374943Z", + "extract_finished_at": "2026-06-08T08:30:00.453904Z", + "index_enqueued_at": "2026-06-08T08:30:01.024518Z", + "index_started_at": "2026-06-08T08:30:51.920899Z", + "index_finished_at": "2026-06-08T08:30:56.159861Z" + } + }, + { + "id": "4c9a965d-3f25-4792-b655-a35161d02d11", + "filename": "load-valid-024.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:30:56.326271Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:56.144569Z", + "extract_enqueued_at": "2026-06-08T08:29:56.144569Z", + "extract_started_at": "2026-06-08T08:30:00.617265Z", + "extract_finished_at": "2026-06-08T08:30:01.627966Z", + "index_enqueued_at": "2026-06-08T08:30:02.207429Z", + "index_started_at": "2026-06-08T08:30:52.248462Z", + "index_finished_at": "2026-06-08T08:30:56.326271Z" + } + }, + { + "id": "945b4dbb-64e8-45c8-aaaa-d3c7c2afe364", + "filename": "load-valid-049.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:32:06.975011Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:30.131847Z", + "extract_enqueued_at": "2026-06-08T08:30:30.131847Z", + "extract_started_at": "2026-06-08T08:30:34.724356Z", + "extract_finished_at": "2026-06-08T08:30:35.835317Z", + "index_enqueued_at": "2026-06-08T08:30:36.450624Z", + "index_started_at": "2026-06-08T08:32:02.345114Z", + "index_finished_at": "2026-06-08T08:32:06.975011Z" + } + }, + { + "id": "79320a71-1319-4e0a-9d37-4b41cb6d3d78", + "filename": "load-valid-025.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:01.926165Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:57.392074Z", + "extract_enqueued_at": "2026-06-08T08:29:57.392074Z", + "extract_started_at": "2026-06-08T08:30:01.922028Z", + "extract_finished_at": "2026-06-08T08:30:03.269873Z", + "index_enqueued_at": "2026-06-08T08:30:03.848381Z", + "index_started_at": "2026-06-08T08:30:57.756104Z", + "index_finished_at": "2026-06-08T08:31:01.926165Z" + } + }, + { + "id": "4e976fd4-c455-40b9-a606-98b84151a520", + "filename": "load-valid-026.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:04.413889Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:58.592124Z", + "extract_enqueued_at": "2026-06-08T08:29:58.592124Z", + "extract_started_at": "2026-06-08T08:30:03.301893Z", + "extract_finished_at": "2026-06-08T08:30:04.577163Z", + "index_enqueued_at": "2026-06-08T08:30:05.224672Z", + "index_started_at": "2026-06-08T08:30:59.982743Z", + "index_finished_at": "2026-06-08T08:31:04.413889Z" + } + }, + { + "id": "87e5dde7-8589-49cd-b311-5558ddc16897", + "filename": "load-valid-027.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:04.662618Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:59.925128Z", + "extract_enqueued_at": "2026-06-08T08:29:59.925128Z", + "extract_started_at": "2026-06-08T08:30:04.749405Z", + "extract_finished_at": "2026-06-08T08:30:06.084555Z", + "index_enqueued_at": "2026-06-08T08:30:06.688718Z", + "index_started_at": "2026-06-08T08:31:00.151223Z", + "index_finished_at": "2026-06-08T08:31:04.662618Z" + } + }, + { + "id": "2ecd4e53-7ad5-4823-a57f-3e45304268d2", + "filename": "load-valid-037.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:35.412349Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:13.349117Z", + "extract_enqueued_at": "2026-06-08T08:30:13.349117Z", + "extract_started_at": "2026-06-08T08:30:17.399322Z", + "extract_finished_at": "2026-06-08T08:30:18.524082Z", + "index_enqueued_at": "2026-06-08T08:30:19.115354Z", + "index_started_at": "2026-06-08T08:31:31.223783Z", + "index_finished_at": "2026-06-08T08:31:35.412349Z" + } + }, + { + "id": "85aa233e-c293-4ea0-ba09-0d95931d82e0", + "filename": "load-valid-038.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:38.280810Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:14.808429Z", + "extract_enqueued_at": "2026-06-08T08:30:14.808429Z", + "extract_started_at": "2026-06-08T08:30:19.050142Z", + "extract_finished_at": "2026-06-08T08:30:20.276858Z", + "index_enqueued_at": "2026-06-08T08:30:20.876324Z", + "index_started_at": "2026-06-08T08:31:34.392363Z", + "index_finished_at": "2026-06-08T08:31:38.280810Z" + } + }, + { + "id": "38cc5bef-c0b6-4a72-9b5e-b62514c4c350", + "filename": "load-valid-041.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:45.781407Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:18.940946Z", + "extract_enqueued_at": "2026-06-08T08:30:18.940946Z", + "extract_started_at": "2026-06-08T08:30:24.405573Z", + "extract_finished_at": "2026-06-08T08:30:25.615519Z", + "index_enqueued_at": "2026-06-08T08:30:26.475024Z", + "index_started_at": "2026-06-08T08:31:41.808321Z", + "index_finished_at": "2026-06-08T08:31:45.781407Z" + } + }, + { + "id": "d22ac7e0-36af-4d74-b39c-68c313718b6c", + "filename": "load-valid-042.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:46.370787Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:20.260005Z", + "extract_enqueued_at": "2026-06-08T08:30:20.260005Z", + "extract_started_at": "2026-06-08T08:30:25.955560Z", + "extract_finished_at": "2026-06-08T08:30:27.229030Z", + "index_enqueued_at": "2026-06-08T08:30:27.816244Z", + "index_started_at": "2026-06-08T08:31:42.452734Z", + "index_finished_at": "2026-06-08T08:31:46.370787Z" + } + }, + { + "id": "c6ab16ec-c99f-4f0b-ba1f-3904b2e2d714", + "filename": "load-valid-045.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:54.024750Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:24.759172Z", + "extract_enqueued_at": "2026-06-08T08:30:24.759172Z", + "extract_started_at": "2026-06-08T08:30:29.472362Z", + "extract_finished_at": "2026-06-08T08:30:30.973594Z", + "index_enqueued_at": "2026-06-08T08:30:31.625202Z", + "index_started_at": "2026-06-08T08:31:49.876160Z", + "index_finished_at": "2026-06-08T08:31:54.024750Z" + } + }, + { + "id": "21368db7-3689-4d43-ae04-a65cd6bd6e11", + "filename": "load-valid-050.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:32:10.665694Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:31.567222Z", + "extract_enqueued_at": "2026-06-08T08:30:31.567222Z", + "extract_started_at": "2026-06-08T08:30:36.056400Z", + "extract_finished_at": "2026-06-08T08:30:37.100492Z", + "index_enqueued_at": "2026-06-08T08:30:37.911465Z", + "index_started_at": "2026-06-08T08:32:06.033957Z", + "index_finished_at": "2026-06-08T08:32:10.665694Z" + } + }, + { + "id": "1c836e81-f2c5-4593-a540-106c98bb4a3d", + "filename": "load-valid-028.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:11.323947Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:01.204258Z", + "extract_enqueued_at": "2026-06-08T08:30:01.204258Z", + "extract_started_at": "2026-06-08T08:30:06.849712Z", + "extract_finished_at": "2026-06-08T08:30:08.061965Z", + "index_enqueued_at": "2026-06-08T08:30:08.623137Z", + "index_started_at": "2026-06-08T08:31:06.575355Z", + "index_finished_at": "2026-06-08T08:31:11.323947Z" + } + }, + { + "id": "be8c6ad8-bf7c-43da-b14d-bac5f133be66", + "filename": "load-valid-043.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:51.377396Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:21.879605Z", + "extract_enqueued_at": "2026-06-08T08:30:21.879605Z", + "extract_started_at": "2026-06-08T08:30:27.542990Z", + "extract_finished_at": "2026-06-08T08:30:28.786198Z", + "index_enqueued_at": "2026-06-08T08:30:29.386133Z", + "index_started_at": "2026-06-08T08:31:47.362371Z", + "index_finished_at": "2026-06-08T08:31:51.377396Z" + } + }, + { + "id": "76b16bcc-01a6-446f-8036-4deaa4dd66fe", + "filename": "load-valid-030.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:14.347255Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:03.901263Z", + "extract_enqueued_at": "2026-06-08T08:30:03.901263Z", + "extract_started_at": "2026-06-08T08:30:09.626803Z", + "extract_finished_at": "2026-06-08T08:30:11.125993Z", + "index_enqueued_at": "2026-06-08T08:30:11.789446Z", + "index_started_at": "2026-06-08T08:31:09.379696Z", + "index_finished_at": "2026-06-08T08:31:14.347255Z" + } + }, + { + "id": "2941f441-dbd6-4705-95f5-4b6317c6289c", + "filename": "load-valid-039.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:38.890076Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:16.188812Z", + "extract_enqueued_at": "2026-06-08T08:30:16.188812Z", + "extract_started_at": "2026-06-08T08:30:20.516520Z", + "extract_finished_at": "2026-06-08T08:30:22.597106Z", + "index_enqueued_at": "2026-06-08T08:30:23.203384Z", + "index_started_at": "2026-06-08T08:31:34.807951Z", + "index_finished_at": "2026-06-08T08:31:38.890076Z" + } + }, + { + "id": "386e11ef-ae15-4591-99a9-70df5f710c6d", + "filename": "load-valid-032.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:23.050989Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:06.569917Z", + "extract_enqueued_at": "2026-06-08T08:30:06.569917Z", + "extract_started_at": "2026-06-08T08:30:11.366166Z", + "extract_finished_at": "2026-06-08T08:30:12.480573Z", + "index_enqueued_at": "2026-06-08T08:30:13.157472Z", + "index_started_at": "2026-06-08T08:31:18.555209Z", + "index_finished_at": "2026-06-08T08:31:23.050989Z" + } + }, + { + "id": "198e2de4-7355-494e-abf7-e03accde514e", + "filename": "load-valid-047.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:32:06.055548Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:27.479240Z", + "extract_enqueued_at": "2026-06-08T08:30:27.479240Z", + "extract_started_at": "2026-06-08T08:30:32.512295Z", + "extract_finished_at": "2026-06-08T08:30:33.957859Z", + "index_enqueued_at": "2026-06-08T08:30:34.575407Z", + "index_started_at": "2026-06-08T08:31:57.135289Z", + "index_finished_at": "2026-06-08T08:32:06.055548Z" + } + }, + { + "id": "e16e94c2-e481-4c50-aa22-94e6960cc117", + "filename": "load-valid-048.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:32:01.865076Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:28.704913Z", + "extract_enqueued_at": "2026-06-08T08:30:28.704913Z", + "extract_started_at": "2026-06-08T08:30:34.056825Z", + "extract_finished_at": "2026-06-08T08:30:35.160342Z", + "index_enqueued_at": "2026-06-08T08:30:35.848306Z", + "index_started_at": "2026-06-08T08:31:57.817780Z", + "index_finished_at": "2026-06-08T08:32:01.865076Z" + } + }, + { + "id": "311e98bb-e5d3-4555-88d8-4fc7cbdeb6d3", + "filename": "load-valid-044.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:53.245024Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:23.314375Z", + "extract_enqueued_at": "2026-06-08T08:30:23.314375Z", + "extract_started_at": "2026-06-08T08:30:28.382078Z", + "extract_finished_at": "2026-06-08T08:30:29.522681Z", + "index_enqueued_at": "2026-06-08T08:30:30.266129Z", + "index_started_at": "2026-06-08T08:31:49.197003Z", + "index_finished_at": "2026-06-08T08:31:53.245024Z" + } + }, + { + "id": "b88e79e8-1078-488d-87fa-e4a667124316", + "filename": "load-valid-034.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:27.537148Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:09.230124Z", + "extract_enqueued_at": "2026-06-08T08:30:09.230124Z", + "extract_started_at": "2026-06-08T08:30:13.907371Z", + "extract_finished_at": "2026-06-08T08:30:15.467357Z", + "index_enqueued_at": "2026-06-08T08:30:16.064269Z", + "index_started_at": "2026-06-08T08:31:23.292455Z", + "index_finished_at": "2026-06-08T08:31:27.537148Z" + } + }, + { + "id": "2c714d2b-67be-4eab-a9a2-70957e3341f3", + "filename": "load-valid-035.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:30.760184Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:10.665159Z", + "extract_enqueued_at": "2026-06-08T08:30:10.665159Z", + "extract_started_at": "2026-06-08T08:30:15.713578Z", + "extract_finished_at": "2026-06-08T08:30:16.853072Z", + "index_enqueued_at": "2026-06-08T08:30:17.444222Z", + "index_started_at": "2026-06-08T08:31:26.872210Z", + "index_finished_at": "2026-06-08T08:31:30.760184Z" + } + }, + { + "id": "243bd62b-6188-47f3-9003-d6077743c305", + "filename": "load-valid-036.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:31.132661Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:12.040941Z", + "extract_enqueued_at": "2026-06-08T08:30:12.040941Z", + "extract_started_at": "2026-06-08T08:30:16.660513Z", + "extract_finished_at": "2026-06-08T08:30:18.019008Z", + "index_enqueued_at": "2026-06-08T08:30:18.634172Z", + "index_started_at": "2026-06-08T08:31:27.128902Z", + "index_finished_at": "2026-06-08T08:31:31.132661Z" + } + }, + { + "id": "8d1e7ff4-5011-4277-b4e3-d6b3e88105e5", + "filename": "load-valid-031.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:19.561162Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:05.241752Z", + "extract_enqueued_at": "2026-06-08T08:30:05.241752Z", + "extract_started_at": "2026-06-08T08:30:10.418414Z", + "extract_finished_at": "2026-06-08T08:30:11.923899Z", + "index_enqueued_at": "2026-06-08T08:30:12.614960Z", + "index_started_at": "2026-06-08T08:31:15.272518Z", + "index_finished_at": "2026-06-08T08:31:19.561162Z" + } + }, + { + "id": "84a1faed-d481-4812-b45b-3a32b2c8b16a", + "filename": "load-valid-029.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:14.588900Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:02.519052Z", + "extract_enqueued_at": "2026-06-08T08:30:02.519052Z", + "extract_started_at": "2026-06-08T08:30:07.979359Z", + "extract_finished_at": "2026-06-08T08:30:09.175027Z", + "index_enqueued_at": "2026-06-08T08:30:09.757784Z", + "index_started_at": "2026-06-08T08:31:09.144001Z", + "index_finished_at": "2026-06-08T08:31:14.588900Z" + } + }, + { + "id": "13ca3ad5-9054-4e6d-9342-d01387e72925", + "filename": "load-valid-033.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:31:23.291817Z", + "timing": { + "upload_completed_at": "2026-06-08T08:30:08.012952Z", + "extract_enqueued_at": "2026-06-08T08:30:08.012952Z", + "extract_started_at": "2026-06-08T08:30:12.983220Z", + "extract_finished_at": "2026-06-08T08:30:14.148588Z", + "index_enqueued_at": "2026-06-08T08:30:14.772419Z", + "index_started_at": "2026-06-08T08:31:18.638283Z", + "index_finished_at": "2026-06-08T08:31:23.291817Z" + } + }, + { + "id": "746a798f-2c64-4790-8961-4deddde8837c", + "filename": "load-valid-001.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:29:40.837900Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:27.212506Z", + "extract_enqueued_at": "2026-06-08T08:29:27.212506Z", + "extract_started_at": "2026-06-08T08:29:30.722221Z", + "extract_finished_at": "2026-06-08T08:29:31.719477Z", + "index_enqueued_at": "2026-06-08T08:29:32.306990Z", + "index_started_at": "2026-06-08T08:29:35.729233Z", + "index_finished_at": "2026-06-08T08:29:40.837900Z" + } + }, + { + "id": "d41ef501-8c84-4861-8493-0d895ff24c80", + "filename": "load-valid-006.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "63e0e213-242c-4417-bac7-160ce1b3c164", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:28:25.736243Z", + "updated_at": "2026-06-08T08:29:53.695949Z", + "timing": { + "upload_completed_at": "2026-06-08T08:29:33.266815Z", + "extract_enqueued_at": "2026-06-08T08:29:33.266815Z", + "extract_started_at": "2026-06-08T08:29:36.724717Z", + "extract_finished_at": "2026-06-08T08:29:37.866439Z", + "index_enqueued_at": "2026-06-08T08:29:38.490088Z", + "index_started_at": "2026-06-08T08:29:47.960812Z", + "index_finished_at": "2026-06-08T08:29:53.695949Z" + } + }, + { + "id": "da95ba06-1006-4f67-995a-90a916a4dcbc", + "filename": "load-valid-031.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:17:10.730523Z", + "timing": { + "upload_completed_at": "2026-06-08T08:16:03.459726Z", + "extract_enqueued_at": "2026-06-08T08:16:03.459726Z", + "extract_started_at": "2026-06-08T08:16:06.890000Z", + "extract_finished_at": "2026-06-08T08:16:07.967275Z", + "index_enqueued_at": "2026-06-08T08:16:08.558310Z", + "index_started_at": "2026-06-08T08:17:06.264261Z", + "index_finished_at": "2026-06-08T08:17:10.730523Z" + } + }, + { + "id": "0c3c9098-d30e-4e7d-9bd1-a913846c0332", + "filename": "load-valid-007.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:15:58.896844Z", + "timing": { + "upload_completed_at": "2026-06-08T08:15:32.164705Z", + "extract_enqueued_at": "2026-06-08T08:15:32.164705Z", + "extract_started_at": "2026-06-08T08:15:36.311082Z", + "extract_finished_at": "2026-06-08T08:15:37.475615Z", + "index_enqueued_at": "2026-06-08T08:15:38.089666Z", + "index_started_at": "2026-06-08T08:15:54.173648Z", + "index_finished_at": "2026-06-08T08:15:58.896844Z" + } + }, + { + "id": "28cfa702-14b6-495e-806b-36464eba9b5a", + "filename": "load-valid-006.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:15:52.124306Z", + "timing": { + "upload_completed_at": "2026-06-08T08:15:30.846197Z", + "extract_enqueued_at": "2026-06-08T08:15:30.846197Z", + "extract_started_at": "2026-06-08T08:15:35.188364Z", + "extract_finished_at": "2026-06-08T08:15:36.358512Z", + "index_enqueued_at": "2026-06-08T08:15:36.984782Z", + "index_started_at": "2026-06-08T08:15:46.570321Z", + "index_finished_at": "2026-06-08T08:15:52.124306Z" + } + }, + { + "id": "b66dfd13-32fe-4f64-9b40-cdc39a5c409f", + "filename": "load-valid-010.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:16:07.607798Z", + "timing": { + "upload_completed_at": "2026-06-08T08:15:36.074176Z", + "extract_enqueued_at": "2026-06-08T08:15:36.074176Z", + "extract_started_at": "2026-06-08T08:15:40.551597Z", + "extract_finished_at": "2026-06-08T08:15:41.701926Z", + "index_enqueued_at": "2026-06-08T08:15:42.285289Z", + "index_started_at": "2026-06-08T08:16:03.121951Z", + "index_finished_at": "2026-06-08T08:16:07.607798Z" + } + }, + { + "id": "82b76bbd-3c55-45ef-b743-779171dc052d", + "filename": "load-valid-046.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:17:54.629746Z", + "timing": { + "upload_completed_at": "2026-06-08T08:16:23.343651Z", + "extract_enqueued_at": "2026-06-08T08:16:23.343651Z", + "extract_started_at": "2026-06-08T08:16:26.855731Z", + "extract_finished_at": "2026-06-08T08:16:28.285388Z", + "index_enqueued_at": "2026-06-08T08:16:28.868796Z", + "index_started_at": "2026-06-08T08:17:49.677291Z", + "index_finished_at": "2026-06-08T08:17:54.629746Z" + } + }, + { + "id": "d3858bb8-06d0-4e4b-a041-f63b64071ba4", + "filename": "load-valid-008.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:16:00.910687Z", + "timing": { + "upload_completed_at": "2026-06-08T08:15:33.460281Z", + "extract_enqueued_at": "2026-06-08T08:15:33.460281Z", + "extract_started_at": "2026-06-08T08:15:37.361699Z", + "extract_finished_at": "2026-06-08T08:15:38.527800Z", + "index_enqueued_at": "2026-06-08T08:15:39.138966Z", + "index_started_at": "2026-06-08T08:15:55.781791Z", + "index_finished_at": "2026-06-08T08:16:00.910687Z" + } + }, + { + "id": "304e6af5-c2b6-4f84-8c74-7b0ad885d914", + "filename": "load-valid-002.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:15:40.534552Z", + "timing": { + "upload_completed_at": "2026-06-08T08:15:25.918740Z", + "extract_enqueued_at": "2026-06-08T08:15:25.918740Z", + "extract_started_at": "2026-06-08T08:15:29.194626Z", + "extract_finished_at": "2026-06-08T08:15:30.290045Z", + "index_enqueued_at": "2026-06-08T08:15:30.881091Z", + "index_started_at": "2026-06-08T08:15:35.541083Z", + "index_finished_at": "2026-06-08T08:15:40.534552Z" + } + }, + { + "id": "dd6b67a6-86a5-4dc0-a2d7-bc15802228ea", + "filename": "load-valid-044.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:17:47.079866Z", + "timing": { + "upload_completed_at": "2026-06-08T08:16:20.851119Z", + "extract_enqueued_at": "2026-06-08T08:16:20.851119Z", + "extract_started_at": "2026-06-08T08:16:24.432139Z", + "extract_finished_at": "2026-06-08T08:16:25.672378Z", + "index_enqueued_at": "2026-06-08T08:16:26.328450Z", + "index_started_at": "2026-06-08T08:17:42.807244Z", + "index_finished_at": "2026-06-08T08:17:47.079866Z" + } + }, + { + "id": "671b6c41-2b25-42bc-aa75-5ff375dae743", + "filename": "load-valid-037.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:17:29.435885Z", + "timing": { + "upload_completed_at": "2026-06-08T08:16:11.752520Z", + "extract_enqueued_at": "2026-06-08T08:16:11.752520Z", + "extract_started_at": "2026-06-08T08:16:15.441753Z", + "extract_finished_at": "2026-06-08T08:16:16.385442Z", + "index_enqueued_at": "2026-06-08T08:16:16.957876Z", + "index_started_at": "2026-06-08T08:17:23.595628Z", + "index_finished_at": "2026-06-08T08:17:29.435885Z" + } + }, + { + "id": "bc301078-b8e3-4cf6-acca-70716a6d2ab1", + "filename": "load-valid-034.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:17:19.387347Z", + "timing": { + "upload_completed_at": "2026-06-08T08:16:07.269883Z", + "extract_enqueued_at": "2026-06-08T08:16:07.269883Z", + "extract_started_at": "2026-06-08T08:16:11.055043Z", + "extract_finished_at": "2026-06-08T08:16:12.248310Z", + "index_enqueued_at": "2026-06-08T08:16:12.821562Z", + "index_started_at": "2026-06-08T08:17:14.587038Z", + "index_finished_at": "2026-06-08T08:17:19.387347Z" + } + }, + { + "id": "9a6d9c4d-c13f-4006-a2da-7510aac9ae6a", + "filename": "load-valid-049.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:18:03.561502Z", + "timing": { + "upload_completed_at": "2026-06-08T08:16:27.514073Z", + "extract_enqueued_at": "2026-06-08T08:16:27.514073Z", + "extract_started_at": "2026-06-08T08:16:31.129797Z", + "extract_finished_at": "2026-06-08T08:16:32.467705Z", + "index_enqueued_at": "2026-06-08T08:16:33.073352Z", + "index_started_at": "2026-06-08T08:17:58.840115Z", + "index_finished_at": "2026-06-08T08:18:03.561502Z" + } + }, + { + "id": "15bd4092-5676-41e6-9d3c-5ec1a894ca25", + "filename": "load-valid-035.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:17:21.534901Z", + "timing": { + "upload_completed_at": "2026-06-08T08:16:08.625439Z", + "extract_enqueued_at": "2026-06-08T08:16:08.625439Z", + "extract_started_at": "2026-06-08T08:16:12.007660Z", + "extract_finished_at": "2026-06-08T08:16:13.434412Z", + "index_enqueued_at": "2026-06-08T08:16:14.033390Z", + "index_started_at": "2026-06-08T08:17:17.194758Z", + "index_finished_at": "2026-06-08T08:17:21.534901Z" + } + }, + { + "id": "3adae90d-0dd9-4ce1-b833-5c054f824ac8", + "filename": "load-valid-047.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:17:55.448037Z", + "timing": { + "upload_completed_at": "2026-06-08T08:16:24.589678Z", + "extract_enqueued_at": "2026-06-08T08:16:24.589678Z", + "extract_started_at": "2026-06-08T08:16:29.084916Z", + "extract_finished_at": "2026-06-08T08:16:30.247923Z", + "index_enqueued_at": "2026-06-08T08:16:30.874295Z", + "index_started_at": "2026-06-08T08:17:50.845381Z", + "index_finished_at": "2026-06-08T08:17:55.448037Z" + } + }, + { + "id": "878a8ce8-426a-4e5a-a2fd-ed2cba19371c", + "filename": "load-valid-048.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "dde64caa-f3ba-45b1-b0f4-f2bd13619eb0", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:14:19.811791Z", + "updated_at": "2026-06-08T08:17:59.999511Z", + "timing": { + "upload_completed_at": "2026-06-08T08:16:26.133016Z", + "extract_enqueued_at": "2026-06-08T08:16:26.133016Z", + "extract_started_at": "2026-06-08T08:16:29.557087Z", + "extract_finished_at": "2026-06-08T08:16:30.886996Z", + "index_enqueued_at": "2026-06-08T08:16:31.560319Z", + "index_started_at": "2026-06-08T08:17:55.446973Z", + "index_finished_at": "2026-06-08T08:17:59.999511Z" + } + }, + { + "id": "a97568b2-fe01-4bb1-a0e2-993336e59d60", + "filename": "load-valid-022.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:51.524366Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:08.465546Z", + "extract_enqueued_at": "2026-06-08T08:13:08.465546Z", + "extract_started_at": "2026-06-08T08:13:11.100246Z", + "extract_finished_at": "2026-06-08T08:13:12.091699Z", + "index_enqueued_at": "2026-06-08T08:13:12.654714Z", + "index_started_at": "2026-06-08T08:13:47.441108Z", + "index_finished_at": "2026-06-08T08:13:51.524366Z" + } + }, + { + "id": "4d5d938b-23d8-49e0-9ad7-e31e432c39a8", + "filename": "load-valid-024.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:56.013481Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:10.947098Z", + "extract_enqueued_at": "2026-06-08T08:13:10.947098Z", + "extract_started_at": "2026-06-08T08:13:14.318459Z", + "extract_finished_at": "2026-06-08T08:13:15.462815Z", + "index_enqueued_at": "2026-06-08T08:13:16.031726Z", + "index_started_at": "2026-06-08T08:13:52.075094Z", + "index_finished_at": "2026-06-08T08:13:56.013481Z" + } + }, + { + "id": "dd01be11-e8bd-4302-b3d6-e1742437ddc6", + "filename": "load-valid-021.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:48.386962Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:07.095660Z", + "extract_enqueued_at": "2026-06-08T08:13:07.095660Z", + "extract_started_at": "2026-06-08T08:13:09.898699Z", + "extract_finished_at": "2026-06-08T08:13:11.701709Z", + "index_enqueued_at": "2026-06-08T08:13:12.293954Z", + "index_started_at": "2026-06-08T08:13:43.809847Z", + "index_finished_at": "2026-06-08T08:13:48.386962Z" + } + }, + { + "id": "79cea03e-1db7-4143-b361-070f8eae1504", + "filename": "load-valid-004.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:00.670418Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:40.980141Z", + "extract_enqueued_at": "2026-06-08T08:12:40.980141Z", + "extract_started_at": "2026-06-08T08:12:43.621394Z", + "extract_finished_at": "2026-06-08T08:12:44.657491Z", + "index_enqueued_at": "2026-06-08T08:12:45.459967Z", + "index_started_at": "2026-06-08T08:12:56.066416Z", + "index_finished_at": "2026-06-08T08:13:00.670418Z" + } + }, + { + "id": "bd67791c-f2cc-4377-b43c-e8cfe1ac5e21", + "filename": "load-valid-005.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:02.090208Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:42.178733Z", + "extract_enqueued_at": "2026-06-08T08:12:42.178733Z", + "extract_started_at": "2026-06-08T08:12:45.568998Z", + "extract_finished_at": "2026-06-08T08:12:46.668796Z", + "index_enqueued_at": "2026-06-08T08:12:49.251130Z", + "index_started_at": "2026-06-08T08:12:57.578812Z", + "index_finished_at": "2026-06-08T08:13:02.090208Z" + } + }, + { + "id": "3f917b1a-ea97-4070-9e01-c9a3a8e17217", + "filename": "load-valid-010.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:17.639787Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:52.958349Z", + "extract_enqueued_at": "2026-06-08T08:12:52.958349Z", + "extract_started_at": "2026-06-08T08:12:55.684970Z", + "extract_finished_at": "2026-06-08T08:12:56.751893Z", + "index_enqueued_at": "2026-06-08T08:12:57.320354Z", + "index_started_at": "2026-06-08T08:13:13.734174Z", + "index_finished_at": "2026-06-08T08:13:17.639787Z" + } + }, + { + "id": "67753764-a6c3-46ab-85a2-1dd5c725b8e0", + "filename": "load-valid-008.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:09.979778Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:46.505324Z", + "extract_enqueued_at": "2026-06-08T08:12:46.505324Z", + "extract_started_at": "2026-06-08T08:12:53.048761Z", + "extract_finished_at": "2026-06-08T08:12:54.255211Z", + "index_enqueued_at": "2026-06-08T08:12:54.829439Z", + "index_started_at": "2026-06-08T08:13:05.871023Z", + "index_finished_at": "2026-06-08T08:13:09.979778Z" + } + }, + { + "id": "570e6989-fd09-4a60-bcf1-671ff0d5b471", + "filename": "load-valid-007.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:15.518850Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:44.923498Z", + "extract_enqueued_at": "2026-06-08T08:12:44.923498Z", + "extract_started_at": "2026-06-08T08:12:50.384169Z", + "extract_finished_at": "2026-06-08T08:12:53.166576Z", + "index_enqueued_at": "2026-06-08T08:12:53.734338Z", + "index_started_at": "2026-06-08T08:13:03.964035Z", + "index_finished_at": "2026-06-08T08:13:15.518850Z" + } + }, + { + "id": "956ba011-8d02-492f-92bb-59a2819e334b", + "filename": "load-valid-011.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:17.890931Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:54.159763Z", + "extract_enqueued_at": "2026-06-08T08:12:54.159763Z", + "extract_started_at": "2026-06-08T08:12:57.027092Z", + "extract_finished_at": "2026-06-08T08:12:58.014249Z", + "index_enqueued_at": "2026-06-08T08:12:58.861602Z", + "index_started_at": "2026-06-08T08:13:13.968853Z", + "index_finished_at": "2026-06-08T08:13:17.890931Z" + } + }, + { + "id": "a0beb927-fe1a-4960-87a0-eecbc6ad0d2d", + "filename": "load-valid-012.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:22.743664Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:55.369836Z", + "extract_enqueued_at": "2026-06-08T08:12:55.369836Z", + "extract_started_at": "2026-06-08T08:12:58.265633Z", + "extract_finished_at": "2026-06-08T08:12:59.337823Z", + "index_enqueued_at": "2026-06-08T08:12:59.996390Z", + "index_started_at": "2026-06-08T08:13:18.554086Z", + "index_finished_at": "2026-06-08T08:13:22.743664Z" + } + }, + { + "id": "5338251f-afa2-42cf-80f8-b149c14d26dc", + "filename": "load-valid-006.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:02.264724Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:43.506996Z", + "extract_enqueued_at": "2026-06-08T08:12:43.506996Z", + "extract_started_at": "2026-06-08T08:12:46.347376Z", + "extract_finished_at": "2026-06-08T08:12:49.199225Z", + "index_enqueued_at": "2026-06-08T08:12:51.009223Z", + "index_started_at": "2026-06-08T08:12:57.658947Z", + "index_finished_at": "2026-06-08T08:13:02.264724Z" + } + }, + { + "id": "f107ad72-6542-4433-a9fb-b48e53ea4720", + "filename": "load-valid-009.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:10.153489Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:50.932436Z", + "extract_enqueued_at": "2026-06-08T08:12:50.932436Z", + "extract_started_at": "2026-06-08T08:12:54.054442Z", + "extract_finished_at": "2026-06-08T08:12:55.045244Z", + "index_enqueued_at": "2026-06-08T08:12:55.623470Z", + "index_started_at": "2026-06-08T08:13:05.998938Z", + "index_finished_at": "2026-06-08T08:13:10.153489Z" + } + }, + { + "id": "437741a4-34e9-498e-b1ba-df76e228d460", + "filename": "load-valid-001.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:12:52.338245Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:37.448323Z", + "extract_enqueued_at": "2026-06-08T08:12:37.448323Z", + "extract_started_at": "2026-06-08T08:12:39.794547Z", + "extract_finished_at": "2026-06-08T08:12:40.869368Z", + "index_enqueued_at": "2026-06-08T08:12:41.493640Z", + "index_started_at": "2026-06-08T08:12:43.954178Z", + "index_finished_at": "2026-06-08T08:12:52.338245Z" + } + }, + { + "id": "ab9b5224-5f2c-4c1f-821e-0a0f3867d82b", + "filename": "load-valid-003.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:12:53.967697Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:39.794073Z", + "extract_enqueued_at": "2026-06-08T08:12:39.794073Z", + "extract_started_at": "2026-06-08T08:12:42.253070Z", + "extract_finished_at": "2026-06-08T08:12:43.314677Z", + "index_enqueued_at": "2026-06-08T08:12:43.874952Z", + "index_started_at": "2026-06-08T08:12:46.571296Z", + "index_finished_at": "2026-06-08T08:12:53.967697Z" + } + }, + { + "id": "0b8a0ee4-fe82-4bbc-af7e-0fbe6cd26c04", + "filename": "load-valid-002.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:12:54.131532Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:38.628986Z", + "extract_enqueued_at": "2026-06-08T08:12:38.628986Z", + "extract_started_at": "2026-06-08T08:12:41.027216Z", + "extract_finished_at": "2026-06-08T08:12:41.979607Z", + "index_enqueued_at": "2026-06-08T08:12:42.531284Z", + "index_started_at": "2026-06-08T08:12:45.647975Z", + "index_finished_at": "2026-06-08T08:12:54.131532Z" + } + }, + { + "id": "ebaa6b49-42a7-4481-95a6-944085f68725", + "filename": "load-valid-017.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:33.498225Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:02.086444Z", + "extract_enqueued_at": "2026-06-08T08:13:02.086444Z", + "extract_started_at": "2026-06-08T08:13:04.919251Z", + "extract_finished_at": "2026-06-08T08:13:06.106725Z", + "index_enqueued_at": "2026-06-08T08:13:06.666030Z", + "index_started_at": "2026-06-08T08:13:29.257034Z", + "index_finished_at": "2026-06-08T08:13:33.498225Z" + } + }, + { + "id": "db721cdd-1220-400b-b0b9-17ee9a592a34", + "filename": "load-valid-018.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:43.452640Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:03.341567Z", + "extract_enqueued_at": "2026-06-08T08:13:03.341567Z", + "extract_started_at": "2026-06-08T08:13:06.426813Z", + "extract_finished_at": "2026-06-08T08:13:10.764269Z", + "index_enqueued_at": "2026-06-08T08:13:11.351435Z", + "index_started_at": "2026-06-08T08:13:38.958615Z", + "index_finished_at": "2026-06-08T08:13:43.452640Z" + } + }, + { + "id": "68eb4635-a8e4-4208-8a71-5d7949ee003c", + "filename": "load-valid-013.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:25.618404Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:56.681249Z", + "extract_enqueued_at": "2026-06-08T08:12:56.681249Z", + "extract_started_at": "2026-06-08T08:12:59.244184Z", + "extract_finished_at": "2026-06-08T08:13:00.753393Z", + "index_enqueued_at": "2026-06-08T08:13:01.318188Z", + "index_started_at": "2026-06-08T08:13:21.607826Z", + "index_finished_at": "2026-06-08T08:13:25.618404Z" + } + }, + { + "id": "ac942036-c749-4cb7-b262-db97317239be", + "filename": "load-valid-014.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:25.899984Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:57.907787Z", + "extract_enqueued_at": "2026-06-08T08:12:57.907787Z", + "extract_started_at": "2026-06-08T08:13:00.835911Z", + "extract_finished_at": "2026-06-08T08:13:01.947256Z", + "index_enqueued_at": "2026-06-08T08:13:02.509384Z", + "index_started_at": "2026-06-08T08:13:21.855414Z", + "index_finished_at": "2026-06-08T08:13:25.899984Z" + } + }, + { + "id": "48393057-6d70-4ce3-affc-2c1abc84ad1d", + "filename": "load-valid-015.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:30.516275Z", + "timing": { + "upload_completed_at": "2026-06-08T08:12:59.243258Z", + "extract_enqueued_at": "2026-06-08T08:12:59.243258Z", + "extract_started_at": "2026-06-08T08:13:02.183713Z", + "extract_finished_at": "2026-06-08T08:13:03.386526Z", + "index_enqueued_at": "2026-06-08T08:13:04.056129Z", + "index_started_at": "2026-06-08T08:13:26.377498Z", + "index_finished_at": "2026-06-08T08:13:30.516275Z" + } + }, + { + "id": "7e6d1c3a-d91b-44e8-8f50-d2dc5f5ec5b3", + "filename": "load-valid-016.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:33.322579Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:00.831906Z", + "extract_enqueued_at": "2026-06-08T08:13:00.831906Z", + "extract_started_at": "2026-06-08T08:13:03.463744Z", + "extract_finished_at": "2026-06-08T08:13:04.506229Z", + "index_enqueued_at": "2026-06-08T08:13:05.109986Z", + "index_started_at": "2026-06-08T08:13:28.877653Z", + "index_finished_at": "2026-06-08T08:13:33.322579Z" + } + }, + { + "id": "0472d799-02fe-49c3-9671-8e004fa6d1b1", + "filename": "load-valid-023.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:52.209727Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:09.768015Z", + "extract_enqueued_at": "2026-06-08T08:13:09.768015Z", + "extract_started_at": "2026-06-08T08:13:12.419160Z", + "extract_finished_at": "2026-06-08T08:13:14.210193Z", + "index_enqueued_at": "2026-06-08T08:13:14.799714Z", + "index_started_at": "2026-06-08T08:13:47.720676Z", + "index_finished_at": "2026-06-08T08:13:52.209727Z" + } + }, + { + "id": "7c523c2d-fc75-416a-b392-656ba6856a2c", + "filename": "load-valid-019.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:39.673260Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:04.635594Z", + "extract_enqueued_at": "2026-06-08T08:13:04.635594Z", + "extract_started_at": "2026-06-08T08:13:07.233196Z", + "extract_finished_at": "2026-06-08T08:13:08.503656Z", + "index_enqueued_at": "2026-06-08T08:13:09.095933Z", + "index_started_at": "2026-06-08T08:13:34.627356Z", + "index_finished_at": "2026-06-08T08:13:39.673260Z" + } + }, + { + "id": "90ba0897-a5ca-4f89-b4bf-e3bccb8d257e", + "filename": "load-valid-020.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "9a88c92f-9520-4057-a3ed-7d37a88e9766", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:12:05.460739Z", + "updated_at": "2026-06-08T08:13:43.170877Z", + "timing": { + "upload_completed_at": "2026-06-08T08:13:05.918084Z", + "extract_enqueued_at": "2026-06-08T08:13:05.918084Z", + "extract_started_at": "2026-06-08T08:13:08.786005Z", + "extract_finished_at": "2026-06-08T08:13:10.579070Z", + "index_enqueued_at": "2026-06-08T08:13:11.164233Z", + "index_started_at": "2026-06-08T08:13:38.714943Z", + "index_finished_at": "2026-06-08T08:13:43.170877Z" + } + }, + { + "id": "f5e94ccb-2c27-4772-8c19-652dad8bbb73", + "filename": "load-valid-006.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:09.552363Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:52.473267Z", + "extract_enqueued_at": "2026-06-08T08:09:52.473267Z", + "extract_started_at": "2026-06-08T08:09:55.219272Z", + "extract_finished_at": "2026-06-08T08:09:56.375791Z", + "index_enqueued_at": "2026-06-08T08:09:56.959675Z", + "index_started_at": "2026-06-08T08:10:05.404586Z", + "index_finished_at": "2026-06-08T08:10:09.552363Z" + } + }, + { + "id": "f71d14a2-2517-49e9-b0b0-8cc90d11c793", + "filename": "load-valid-002.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:09:59.714413Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:47.692083Z", + "extract_enqueued_at": "2026-06-08T08:09:47.692083Z", + "extract_started_at": "2026-06-08T08:09:50.183154Z", + "extract_finished_at": "2026-06-08T08:09:51.147375Z", + "index_enqueued_at": "2026-06-08T08:09:51.755669Z", + "index_started_at": "2026-06-08T08:09:54.242010Z", + "index_finished_at": "2026-06-08T08:09:59.714413Z" + } + }, + { + "id": "2a79108e-dc8e-41f6-b08f-9932d2836602", + "filename": "load-valid-008.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:15.138869Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:54.860222Z", + "extract_enqueued_at": "2026-06-08T08:09:54.860222Z", + "extract_started_at": "2026-06-08T08:09:58.013677Z", + "extract_finished_at": "2026-06-08T08:09:59.006307Z", + "index_enqueued_at": "2026-06-08T08:09:59.561883Z", + "index_started_at": "2026-06-08T08:10:11.239976Z", + "index_finished_at": "2026-06-08T08:10:15.138869Z" + } + }, + { + "id": "db415368-a778-471d-9124-19b37289e59e", + "filename": "load-valid-001.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:09:59.396961Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:46.485228Z", + "extract_enqueued_at": "2026-06-08T08:09:46.485228Z", + "extract_started_at": "2026-06-08T08:09:48.753480Z", + "extract_finished_at": "2026-06-08T08:09:49.999550Z", + "index_enqueued_at": "2026-06-08T08:09:50.564392Z", + "index_started_at": "2026-06-08T08:09:53.081145Z", + "index_finished_at": "2026-06-08T08:09:59.396961Z" + } + }, + { + "id": "d91d171e-bb4e-42b3-8d5c-3a7135fd8134", + "filename": "load-valid-007.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:14.689817Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:53.646986Z", + "extract_enqueued_at": "2026-06-08T08:09:53.646986Z", + "extract_started_at": "2026-06-08T08:09:57.032920Z", + "extract_finished_at": "2026-06-08T08:09:58.187896Z", + "index_enqueued_at": "2026-06-08T08:09:58.770841Z", + "index_started_at": "2026-06-08T08:10:10.883507Z", + "index_finished_at": "2026-06-08T08:10:14.689817Z" + } + }, + { + "id": "5c049412-054f-42e1-9e13-023823e8aad2", + "filename": "load-valid-003.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:02.158083Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:48.931801Z", + "extract_enqueued_at": "2026-06-08T08:09:48.931801Z", + "extract_started_at": "2026-06-08T08:09:51.423905Z", + "extract_finished_at": "2026-06-08T08:09:52.743102Z", + "index_enqueued_at": "2026-06-08T08:09:53.324472Z", + "index_started_at": "2026-06-08T08:09:56.669129Z", + "index_finished_at": "2026-06-08T08:10:02.158083Z" + } + }, + { + "id": "dd32e7ab-cd98-4324-8787-5750afe6f34e", + "filename": "load-valid-005.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:07.940086Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:51.290543Z", + "extract_enqueued_at": "2026-06-08T08:09:51.290543Z", + "extract_started_at": "2026-06-08T08:09:53.873697Z", + "extract_finished_at": "2026-06-08T08:09:55.047888Z", + "index_enqueued_at": "2026-06-08T08:09:55.650679Z", + "index_started_at": "2026-06-08T08:10:03.140619Z", + "index_finished_at": "2026-06-08T08:10:07.940086Z" + } + }, + { + "id": "9232b00d-0b56-4eca-a273-e569546850c4", + "filename": "load-valid-004.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:07.515406Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:50.121905Z", + "extract_enqueued_at": "2026-06-08T08:09:50.121905Z", + "extract_started_at": "2026-06-08T08:09:52.669554Z", + "extract_finished_at": "2026-06-08T08:09:53.822766Z", + "index_enqueued_at": "2026-06-08T08:09:54.406964Z", + "index_started_at": "2026-06-08T08:10:03.105459Z", + "index_finished_at": "2026-06-08T08:10:07.515406Z" + } + }, + { + "id": "148116bf-a6c2-4935-a2fa-48ad7a0c31ee", + "filename": "load-valid-010.pdf", + "content_type": "application/pdf", + "file_size_bytes": 715, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:22.237615Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:57.430426Z", + "extract_enqueued_at": "2026-06-08T08:09:57.430426Z", + "extract_started_at": "2026-06-08T08:10:00.700838Z", + "extract_finished_at": "2026-06-08T08:10:01.996813Z", + "index_enqueued_at": "2026-06-08T08:10:02.578208Z", + "index_started_at": "2026-06-08T08:10:17.718603Z", + "index_finished_at": "2026-06-08T08:10:22.237615Z" + } + }, + { + "id": "b9073464-28ec-476a-bac8-0971193d9f2f", + "filename": "load-valid-009.pdf", + "content_type": "application/pdf", + "file_size_bytes": 714, + "page_count": 1, + "ingestion_run_id": "89b8da54-a770-42bf-a2da-c87f05be6b7b", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-08T08:09:30.491797Z", + "updated_at": "2026-06-08T08:10:16.558621Z", + "timing": { + "upload_completed_at": "2026-06-08T08:09:56.108341Z", + "extract_enqueued_at": "2026-06-08T08:09:56.108341Z", + "extract_started_at": "2026-06-08T08:09:59.054110Z", + "extract_finished_at": "2026-06-08T08:10:00.153835Z", + "index_enqueued_at": "2026-06-08T08:10:00.722025Z", + "index_started_at": "2026-06-08T08:10:12.572818Z", + "index_finished_at": "2026-06-08T08:10:16.558621Z" + } + }, + { + "id": "81ac5061-3791-4176-857d-96ce7fb97c92", + "filename": "terms-and-conditions-template.pdf", + "content_type": "application/pdf", + "file_size_bytes": 50399, + "page_count": 2, + "ingestion_run_id": "841b78f4-4233-4bf9-9cfe-e2945c32d0f4", + "status": "indexed", + "error_message": null, + "created_at": "2026-06-07T19:05:02.193118Z", + "updated_at": "2026-06-07T19:05:22.315615Z", + "timing": { + "upload_completed_at": "2026-06-07T19:05:06.456797Z", + "extract_enqueued_at": "2026-06-07T19:05:06.456797Z", + "extract_started_at": "2026-06-07T19:05:10.155645Z", + "extract_finished_at": "2026-06-07T19:05:11.932408Z", + "index_enqueued_at": "2026-06-07T19:05:12.620190Z", + "index_started_at": "2026-06-07T19:05:15.937121Z", + "index_finished_at": "2026-06-07T19:05:22.315615Z" + } + } + ], + "limit": 100, + "offset": 0, + "total": 99 + }, + "queue_snapshots": [ + { + "elapsed_seconds": 131.507, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 4, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 29, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 3, + "extracting": 1, + "indexing": 31, + "ready": 0, + "indexed": 15, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 139.284, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 29, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 32, + "ready": 0, + "indexed": 18, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 146.87, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 26, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 29, + "ready": 0, + "indexed": 21, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 154.444, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 23, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 26, + "ready": 0, + "indexed": 24, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 161.999, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 21, + "started_count": 2, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 25, + "ready": 0, + "indexed": 25, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 169.584, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 19, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 22, + "ready": 0, + "indexed": 28, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 177.487, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 16, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 20, + "ready": 0, + "indexed": 30, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 185.381, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 13, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 17, + "ready": 0, + "indexed": 33, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 193.267, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 10, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 14, + "ready": 0, + "indexed": 36, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 200.758, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 8, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 11, + "ready": 0, + "indexed": 39, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 208.222, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 5, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 8, + "ready": 0, + "indexed": 42, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 215.715, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 2, + "started_count": 3, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 5, + "ready": 0, + "indexed": 45, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 223.891, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 0, + "started_count": 2, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "processing", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 3, + "ready": 0, + "indexed": 47, + "failed": 0, + "total": 50 + } + }, + { + "elapsed_seconds": 231.386, + "queues": [ + { + "name": "ingest_extract", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 29 + }, + { + "name": "ingest_index", + "queued_count": 0, + "started_count": 0, + "deferred_count": 0, + "scheduled_count": 0, + "failed_count": 7 + } + ], + "run_status": "completed", + "document_statuses": { + "pending_upload": 0, + "uploading": 0, + "uploaded": 0, + "extracting": 0, + "indexing": 0, + "ready": 0, + "indexed": 50, + "failed": 0, + "total": 50 + } + } + ], + "durations_seconds": { + "prepare_upload_complete": 128.884, + "total": 232.829 + } +} diff --git a/client/package-lock.json b/client/package-lock.json index a2acc21..22f8980 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@supabase/supabase-js": "^2.39.3", "@tanstack/react-query": "^5.17.19", - "axios": "^1.6.5", + "axios": "^1.17.0", "clsx": "^2.1.0", "lucide-react": "^0.309.0", "react": "^18.2.0", @@ -74,7 +74,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -913,9 +912,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", - "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -1471,7 +1470,6 @@ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -1633,7 +1631,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1664,6 +1661,18 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1793,14 +1802,15 @@ } }, "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz", + "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, "node_modules/balanced-match": { @@ -1875,7 +1885,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2147,7 +2156,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2360,7 +2368,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2662,9 +2669,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -2929,6 +2936,19 @@ "node": ">= 0.4" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", @@ -3096,7 +3116,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -3409,7 +3428,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -3424,9 +3442,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "funding": [ { "type": "github", @@ -3660,9 +3678,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -3709,9 +3727,9 @@ "license": "MIT" }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "funding": [ { "type": "opencollective", @@ -3727,9 +3745,8 @@ } ], "license": "MIT", - "peer": true, "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -3904,10 +3921,13 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/punycode": { "version": "2.3.1", @@ -3944,7 +3964,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -3957,7 +3976,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -3984,12 +4002,12 @@ } }, "node_modules/react-router": { - "version": "6.30.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", - "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.23.2" + "@remix-run/router": "1.23.3" }, "engines": { "node": ">=14.0.0" @@ -3999,13 +4017,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.30.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", - "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.23.2", - "react-router": "6.30.3" + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" }, "engines": { "node": ">=14.0.0" @@ -4458,11 +4476,10 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -4639,7 +4656,6 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -4834,9 +4850,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/client/package.json b/client/package.json index e57e8d6..527d879 100644 --- a/client/package.json +++ b/client/package.json @@ -15,11 +15,12 @@ "react-router-dom": "^6.21.3", "@supabase/supabase-js": "^2.39.3", "@tanstack/react-query": "^5.17.19", - "axios": "^1.6.5", + "axios": "^1.17.0", "zustand": "^4.5.0", "lucide-react": "^0.309.0", "clsx": "^2.1.0", - "tailwindcss": "^3.4.1" + "tailwindcss": "^3.4.1", + "postcss": "^8.4.33" }, "devDependencies": { "@types/react": "^18.2.48", @@ -29,7 +30,6 @@ "vite": "^5.0.11", "vitest": "^1.2.0", "eslint": "^8.56.0", - "autoprefixer": "^10.4.17", - "postcss": "^8.4.33" + "autoprefixer": "^10.4.17" } } diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index 4475cc5..4bf2e8f 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -45,6 +45,17 @@ export type DocumentRecord = { created_at?: string; updated_at?: string; error_message?: string | null; + timing?: IngestionTiming | null; +}; + +export type IngestionTiming = { + upload_completed_at?: string | null; + extract_enqueued_at?: string | null; + extract_started_at?: string | null; + extract_finished_at?: string | null; + index_enqueued_at?: string | null; + index_started_at?: string | null; + index_finished_at?: string | null; }; export type UploadPrepareResponse = { @@ -173,6 +184,7 @@ export type QueryResponse = { export type QueryStreamMeta = { request_id: string; document_id: string; + document_ids?: string[]; top_k: number; }; @@ -462,6 +474,23 @@ function normalizeDocument(rawDoc: unknown): DocumentRecord | null { created_at: raw.created_at ? String(raw.created_at) : undefined, updated_at: raw.updated_at ? String(raw.updated_at) : undefined, error_message: raw.error_message ? String(raw.error_message) : null, + timing: normalizeIngestionTiming(raw.timing), + }; +} + +function normalizeIngestionTiming(value: unknown): IngestionTiming | null { + if (typeof value !== "object" || value === null) { + return null; + } + const raw = value as Record; + return { + upload_completed_at: raw.upload_completed_at == null ? null : String(raw.upload_completed_at), + extract_enqueued_at: raw.extract_enqueued_at == null ? null : String(raw.extract_enqueued_at), + extract_started_at: raw.extract_started_at == null ? null : String(raw.extract_started_at), + extract_finished_at: raw.extract_finished_at == null ? null : String(raw.extract_finished_at), + index_enqueued_at: raw.index_enqueued_at == null ? null : String(raw.index_enqueued_at), + index_started_at: raw.index_started_at == null ? null : String(raw.index_started_at), + index_finished_at: raw.index_finished_at == null ? null : String(raw.index_finished_at), }; } @@ -637,7 +666,7 @@ export async function apiUploadDocument(token: string, file: File): Promise { const response = await apiRequest>("/query", token, { method: "POST", @@ -733,7 +762,7 @@ function normalizeCitations(value: unknown): QueryCitation[] { export async function apiQueryStream( token: string, - payload: { document_id: string; question: string }, + payload: { document_id?: string; document_ids?: string[]; question: string }, handlers: QueryStreamEventHandlers, abortSignal?: AbortSignal, ): Promise { @@ -791,6 +820,9 @@ export async function apiQueryStream( handlers.onMeta?.({ request_id: String(payloadData.request_id ?? ""), document_id: String(payloadData.document_id ?? ""), + document_ids: Array.isArray(payloadData.document_ids) + ? payloadData.document_ids.map((item) => String(item)) + : undefined, top_k: Number(payloadData.top_k ?? 0), }); } else if (parsed.event === "delta" && payloadData) { diff --git a/client/src/pages/ObservabilityPage.tsx b/client/src/pages/ObservabilityPage.tsx index 558fd03..5de3c3c 100644 --- a/client/src/pages/ObservabilityPage.tsx +++ b/client/src/pages/ObservabilityPage.tsx @@ -1,15 +1,91 @@ import { useEffect, useMemo, useState } from "react"; import { useAuth } from "../context/AuthContext"; -import { apiGetObservability, type ObservabilityResponse } from "../lib/api"; +import { apiGetDocuments, apiGetObservability, type DocumentRecord, type ObservabilityResponse } from "../lib/api"; function pct(value: number): string { return `${(value * 100).toFixed(1)}%`; } +function durationMs(start?: string | null, end?: string | null): number | null { + if (!start || !end) { + return null; + } + const startMs = new Date(start).getTime(); + const endMs = new Date(end).getTime(); + if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs < startMs) { + return null; + } + return endMs - startMs; +} + +function formatDuration(ms: number | null): string { + if (ms === null) { + return "--"; + } + if (ms < 1000) { + return `${ms} ms`; + } + const seconds = ms / 1000; + if (seconds < 60) { + return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`; + } + const minutes = Math.floor(seconds / 60); + const remaining = Math.round(seconds % 60); + return `${minutes}m ${remaining}s`; +} + +function average(values: Array): number | null { + const valid = values.filter((value): value is number => value !== null); + if (valid.length === 0) { + return null; + } + return Math.round(valid.reduce((sum, value) => sum + value, 0) / valid.length); +} + +function timingStats(documents: DocumentRecord[]) { + const documentsWithTiming = documents.filter((document) => document.timing); + const completed = documentsWithTiming.filter((document) => document.timing?.index_finished_at); + const totalDurations = completed.map((document) => + durationMs(document.timing?.upload_completed_at, document.timing?.index_finished_at), + ); + const uploadTimes = completed + .map((document) => document.timing?.upload_completed_at) + .filter((value): value is string => Boolean(value)) + .map((value) => new Date(value).getTime()) + .filter((value) => Number.isFinite(value)); + const finishTimes = completed + .map((document) => document.timing?.index_finished_at) + .filter((value): value is string => Boolean(value)) + .map((value) => new Date(value).getTime()) + .filter((value) => Number.isFinite(value)); + const batchWallMs = + uploadTimes.length > 0 && finishTimes.length > 0 + ? Math.max(...finishTimes) - Math.min(...uploadTimes) + : null; + return { + completed, + avgExtractMs: average( + completed.map((document) => + durationMs(document.timing?.extract_started_at, document.timing?.extract_finished_at), + ), + ), + avgIndexMs: average( + completed.map((document) => + durationMs(document.timing?.index_started_at, document.timing?.index_finished_at), + ), + ), + avgTotalMs: average( + totalDurations, + ), + batchWallMs, + }; +} + export default function ObservabilityPage() { const { accessToken } = useAuth(); const [data, setData] = useState(null); + const [documents, setDocuments] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -22,14 +98,19 @@ export default function ObservabilityPage() { setLoading(true); setError(null); try { - const response = await apiGetObservability(accessToken); + const [response, recentDocuments] = await Promise.all([ + apiGetObservability(accessToken), + apiGetDocuments(accessToken, { limit: 100 }), + ]); if (active) { setData(response); + setDocuments(recentDocuments); } } catch (err) { if (active) { setError(err instanceof Error ? err.message : "Failed to load observability"); setData(null); + setDocuments([]); } } finally { if (active) { @@ -50,6 +131,8 @@ export default function ObservabilityPage() { return Math.max(...data.query_volume.map((point) => point.count), 1); }, [data]); + const ingestionTiming = useMemo(() => timingStats(documents), [documents]); + if (loading) { return
Loading observability data...
; } @@ -127,6 +210,54 @@ export default function ObservabilityPage() { +
+
+
+

Ingestion timing

+

Recent completed documents with stage timing.

+
+

{ingestionTiming.completed.length} completed documents sampled

+
+ +
+ + + + +
+ +
+ {ingestionTiming.completed.length === 0 ? ( +

+ No completed ingestion timing data yet. +

+ ) : ( + ingestionTiming.completed.map((document) => ( +
+
+

{document.filename}

+

{document.status}

+
+
+ + + +
+
+ )) + )} +
+
+

Recent query errors

@@ -155,3 +286,12 @@ function Metric({ label, value }: { label: string; value: string }) {
); } + +function TimingValue({ label, value }: { label: string; value: number | null }) { + return ( +
+

{label}

+

{formatDuration(value)}

+
+ ); +} diff --git a/client/src/pages/UploadPage.tsx b/client/src/pages/UploadPage.tsx index 47d5c5b..0c69894 100644 --- a/client/src/pages/UploadPage.tsx +++ b/client/src/pages/UploadPage.tsx @@ -91,10 +91,83 @@ function statusRank(status: DocumentStatus): number { return 3; } +function timestampValue(value?: string): number { + const parsed = value ? new Date(value).getTime() : 0; + return Number.isFinite(parsed) ? parsed : 0; +} + +function compareByFilename(a: DocumentRecord, b: DocumentRecord): number { + const filenameCompare = a.filename.localeCompare(b.filename, undefined, { + numeric: true, + sensitivity: "base", + }); + if (filenameCompare !== 0) { + return filenameCompare; + } + return a.id.localeCompare(b.id); +} + function formatDate(value?: string): string { return value ? new Date(value).toLocaleString() : "--"; } +function durationMs(start?: string | null, end?: string | null): number | null { + if (!start || !end) { + return null; + } + const startMs = new Date(start).getTime(); + const endMs = new Date(end).getTime(); + if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs < startMs) { + return null; + } + return endMs - startMs; +} + +function formatDuration(ms: number | null): string | null { + if (ms === null) { + return null; + } + if (ms < 1000) { + return `${ms} ms`; + } + const seconds = ms / 1000; + if (seconds < 60) { + return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`; + } + const minutes = Math.floor(seconds / 60); + const remaining = Math.round(seconds % 60); + return `${minutes}m ${remaining}s`; +} + +function ingestionDurations(document: DocumentRecord) { + const timing = document.timing; + if (!timing) { + return []; + } + return [ + { + label: "Queue", + value: formatDuration(durationMs(timing.extract_enqueued_at, timing.extract_started_at)), + }, + { + label: "Extract", + value: formatDuration(durationMs(timing.extract_started_at, timing.extract_finished_at)), + }, + { + label: "Index wait", + value: formatDuration(durationMs(timing.index_enqueued_at, timing.index_started_at)), + }, + { + label: "Index", + value: formatDuration(durationMs(timing.index_started_at, timing.index_finished_at)), + }, + { + label: "Total", + value: formatDuration(durationMs(timing.upload_completed_at, timing.index_finished_at)), + }, + ].filter((item): item is { label: string; value: string } => Boolean(item.value)); +} + function countDocuments(documents: DocumentRecord[]): IngestionRunStatusCounts { const counts: IngestionRunStatusCounts = { pending_upload: 0, @@ -241,16 +314,17 @@ export default function UploadPage() { return true; }) .sort((a, b) => { + let primary = 0; if (sortMode === "oldest") { - return new Date(a.created_at ?? "").getTime() - new Date(b.created_at ?? "").getTime(); - } - if (sortMode === "updated") { - return new Date(b.updated_at ?? "").getTime() - new Date(a.updated_at ?? "").getTime(); + primary = timestampValue(a.created_at) - timestampValue(b.created_at); + } else if (sortMode === "updated") { + primary = timestampValue(b.updated_at) - timestampValue(a.updated_at); + } else if (sortMode === "status") { + primary = statusRank(a.status) - statusRank(b.status); + } else { + primary = timestampValue(b.created_at) - timestampValue(a.created_at); } - if (sortMode === "status") { - return statusRank(a.status) - statusRank(b.status); - } - return new Date(b.created_at ?? "").getTime() - new Date(a.created_at ?? "").getTime(); + return primary || compareByFilename(a, b); }); const activeDocuments = visibleDocuments.filter((doc) => isProcessing(doc.status)); @@ -640,6 +714,8 @@ function DocumentRow({ onRetry?: () => void; onDelete: () => void; }) { + const durations = ingestionDurations(document); + return (
@@ -683,6 +759,16 @@ function DocumentRow({
+ {durations.length > 0 ? ( +
+ {durations.map((item) => ( +
+

{item.label}

+

{item.value}

+
+ ))} +
+ ) : null}
); } diff --git a/docker-compose.yml b/docker-compose.yml index 55356af..de76f0f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,6 +31,9 @@ services: - OPENAI_EMBEDDING_TIMEOUT_SECONDS=${OPENAI_EMBEDDING_TIMEOUT_SECONDS:-300} - INGEST_EXTRACT_JOB_TIMEOUT_SECONDS=${INGEST_EXTRACT_JOB_TIMEOUT_SECONDS:-900} - INGEST_INDEX_JOB_TIMEOUT_SECONDS=${INGEST_INDEX_JOB_TIMEOUT_SECONDS:-1800} + - INGEST_STALE_UPLOADED_SECONDS=${INGEST_STALE_UPLOADED_SECONDS:-3600} + - INGEST_STALE_EXTRACTING_SECONDS=${INGEST_STALE_EXTRACTING_SECONDS:-1800} + - INGEST_STALE_INDEXING_SECONDS=${INGEST_STALE_INDEXING_SECONDS:-3600} - EMBEDDING_BATCH_SIZE=${EMBEDDING_BATCH_SIZE:-32} - ENVIRONMENT=development volumes: diff --git a/docs/ingestion-load-testing.md b/docs/ingestion-load-testing.md new file mode 100644 index 0000000..ba27c1c --- /dev/null +++ b/docs/ingestion-load-testing.md @@ -0,0 +1,34 @@ +# Ingestion Load Testing + +Use `scripts/ingestion_load_test.py` to run repeatable ingestion benchmarks through the real API, Supabase upload URLs, Redis queues, and workers. + +## Prerequisites + +- Server and workers are running with Docker Compose. +- Supabase schema has been updated with `scripts/schema.supabase.sql`. +- You have a valid app bearer token for a user with a workspace. + +## PowerShell Examples + +```powershell +cd H:\varun\MultiDoc-RAG\MultiDoc-RAG +$env:RAG_BEARER_TOKEN = "" +python scripts\ingestion_load_test.py --count 10 --scenario valid +python scripts\ingestion_load_test.py --count 25 --scenario valid +python scripts\ingestion_load_test.py --count 50 --scenario valid +python scripts\ingestion_load_test.py --count 10 --scenario mixed +``` + +The script writes comparable JSON artifacts to: + +```text +artifacts/ingestion-load/ +``` + +Each artifact includes prepare/complete responses, final ingestion-run state, current document list, total wall-clock duration, prepare/upload duration, and queue snapshots captured while polling. + +## Notes + +- The generated PDFs are one-page text PDFs with deterministic names. +- The mixed scenario replaces the final generated file with an invalid text file to confirm isolated failures. +- Use `--timeout-seconds` and `--poll-seconds` to tune long runs. diff --git a/scripts/ingestion_load_test.py b/scripts/ingestion_load_test.py new file mode 100644 index 0000000..26020c8 --- /dev/null +++ b/scripts/ingestion_load_test.py @@ -0,0 +1,276 @@ +from __future__ import annotations + +import argparse +import json +import os +import time +import urllib.error +import urllib.parse +import urllib.request +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + + +def _validate_http_url(url: str) -> None: + parsed = urllib.parse.urlparse(url) + if parsed.scheme not in {"http", "https"} or not parsed.netloc: + raise RuntimeError(f"Refusing non-HTTP URL: {url}") + + +def _json_request( + *, + method: str, + url: str, + token: str, + request_timeout_seconds: float, + payload: dict[str, Any] | None = None, +) -> dict[str, Any]: + _validate_http_url(url) + body = json.dumps(payload).encode("utf-8") if payload is not None else None + request = urllib.request.Request( + url, + data=body, + method=method, + headers={ + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + }, + ) + try: + with urllib.request.urlopen(request, timeout=request_timeout_seconds) as response: # nosec B310 + response_body = response.read().decode("utf-8") + except urllib.error.HTTPError as exc: + detail = exc.read().decode("utf-8", errors="replace") + raise RuntimeError(f"{method} {url} failed with {exc.code}: {detail}") from exc + return json.loads(response_body) if response_body else {} + + +def _put_file(url: str, path: Path) -> None: + _validate_http_url(url) + request = urllib.request.Request( + url, + data=path.read_bytes(), + method="PUT", + headers={"Content-Type": "application/pdf"}, + ) + try: + with urllib.request.urlopen(request, timeout=120) as response: # nosec B310 + response.read() + except urllib.error.HTTPError as exc: + detail = exc.read().decode("utf-8", errors="replace") + raise RuntimeError(f"PUT upload failed with {exc.code}: {detail}") from exc + + +def _redact_upload_urls(value: Any) -> Any: + if isinstance(value, dict): + return { + key: "[redacted]" if key == "upload_url" else _redact_upload_urls(item) + for key, item in value.items() + } + if isinstance(value, list): + return [_redact_upload_urls(item) for item in value] + return value + + +def _pdf_bytes(title: str, body: str) -> bytes: + stream = ( + "BT\n" + "/F1 12 Tf\n" + "72 740 Td\n" + f"({title}) Tj\n" + "0 -24 Td\n" + f"({body}) Tj\n" + "ET\n" + ).encode("latin-1", errors="replace") + objects = [ + b"1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n", + b"2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n", + b"3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >> endobj\n", + b"4 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj\n", + b"5 0 obj << /Length " + + str(len(stream)).encode("ascii") + + b" >> stream\n" + + stream + + b"endstream\nendobj\n", + ] + content = bytearray(b"%PDF-1.4\n") + offsets = [0] + for obj in objects: + offsets.append(len(content)) + content.extend(obj) + xref_offset = len(content) + content.extend(f"xref\n0 {len(objects) + 1}\n".encode("ascii")) + content.extend(b"0000000000 65535 f \n") + for offset in offsets[1:]: + content.extend(f"{offset:010d} 00000 n \n".encode("ascii")) + content.extend( + ( + f"trailer << /Size {len(objects) + 1} /Root 1 0 R >>\n" + f"startxref\n{xref_offset}\n%%EOF\n" + ).encode("ascii") + ) + return bytes(content) + + +def _build_dataset(directory: Path, count: int, scenario: str) -> list[Path]: + directory.mkdir(parents=True, exist_ok=True) + files: list[Path] = [] + for index in range(count): + filename = f"load-{scenario}-{index + 1:03d}.pdf" + path = directory / filename + text = ( + f"Load test document {index + 1}. " + f"Scenario {scenario}. " + "This document contains extractable text for ingestion benchmarking." + ) + path.write_bytes(_pdf_bytes(filename, text)) + files.append(path) + if scenario == "mixed" and files: + invalid_path = directory / "load-mixed-invalid.txt" + invalid_path.write_text("This is intentionally not a PDF.", encoding="utf-8") + files[-1] = invalid_path + return files + + +def _terminal(status_value: str) -> bool: + return status_value in {"completed", "partial", "failed"} + + +def run_load_test(args: argparse.Namespace) -> dict[str, Any]: + token = args.token or os.getenv("RAG_BEARER_TOKEN") + if not token: + raise RuntimeError("Provide --token or set RAG_BEARER_TOKEN") + + api_base = args.api_base.rstrip("/") + started_at = time.perf_counter() + generated_at = datetime.now(timezone.utc).isoformat() + dataset_dir = Path(args.dataset_dir) + files = _build_dataset(dataset_dir, args.count, args.scenario) + + prepare_payload = { + "name": f"load-{args.scenario}-{args.count}-{generated_at}", + "files": [ + { + "filename": path.name, + "content_type": "application/pdf", + "file_size_bytes": path.stat().st_size, + "client_file_id": path.stem, + "idempotency_key": f"{args.scenario}-{args.count}-{path.name}-{generated_at}", + } + for path in files + ], + } + prepare_started = time.perf_counter() + prepare = _json_request( + method="POST", + url=f"{api_base}/documents/upload-prepare-batch", + token=token, + request_timeout_seconds=args.request_timeout_seconds, + payload=prepare_payload, + ) + upload_items = [] + file_by_name = {path.name: path for path in files} + for item in prepare.get("items", []): + if item.get("status") not in {"prepared", "accepted"}: + continue + path = file_by_name[str(item["filename"])] + _put_file(str(item["upload_url"]), path) + upload_items.append( + { + "document_id": item["document_id"], + "bucket": item["bucket"], + "storage_path": item["storage_path"], + } + ) + + complete = _json_request( + method="POST", + url=f"{api_base}/documents/upload-complete-batch", + token=token, + request_timeout_seconds=args.request_timeout_seconds, + payload={"ingestion_run_id": prepare.get("ingestion_run_id"), "files": upload_items}, + ) + prepare_upload_complete_seconds = round(time.perf_counter() - prepare_started, 3) + + queue_snapshots: list[dict[str, Any]] = [] + run: dict[str, Any] = {} + deadline = time.perf_counter() + args.timeout_seconds + while time.perf_counter() < deadline: + if prepare.get("ingestion_run_id"): + run = _json_request( + method="GET", + url=f"{api_base}/documents/ingestion-runs/{prepare['ingestion_run_id']}", + token=token, + request_timeout_seconds=args.request_timeout_seconds, + ) + queues = _json_request( + method="GET", + url=f"{api_base}/documents/ingestion-queues", + token=token, + request_timeout_seconds=args.request_timeout_seconds, + ) + queue_snapshots.append( + { + "elapsed_seconds": round(time.perf_counter() - started_at, 3), + "queues": queues.get("queues", []), + "run_status": run.get("status"), + "document_statuses": run.get("document_statuses"), + } + ) + if run and _terminal(str(run.get("status"))): + break + time.sleep(args.poll_seconds) + + documents = _json_request( + method="GET", + url=f"{api_base}/documents?limit=100", + token=token, + request_timeout_seconds=args.request_timeout_seconds, + ) + total_seconds = round(time.perf_counter() - started_at, 3) + result = { + "generated_at": generated_at, + "api_base": api_base, + "scenario": args.scenario, + "requested_count": args.count, + "ingestion_run_id": prepare.get("ingestion_run_id"), + "prepare": _redact_upload_urls(prepare), + "complete": complete, + "final_run": run, + "documents": documents, + "queue_snapshots": queue_snapshots, + "durations_seconds": { + "prepare_upload_complete": prepare_upload_complete_seconds, + "total": total_seconds, + }, + } + + artifact_dir = Path(args.artifact_dir) + artifact_dir.mkdir(parents=True, exist_ok=True) + artifact_path = artifact_dir / ( + f"ingestion-load-{args.scenario}-{args.count}-" + f"{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}.json" + ) + artifact_path.write_text(json.dumps(result, indent=2, default=str), encoding="utf-8") + result["artifact_path"] = str(artifact_path) + return result + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Run a repeatable ingestion load test.") + parser.add_argument("--api-base", default=os.getenv("RAG_API_BASE", "http://localhost:8000")) + parser.add_argument("--token", default=None, help="Bearer token; defaults to RAG_BEARER_TOKEN") + parser.add_argument("--count", type=int, default=10) + parser.add_argument("--scenario", choices=["valid", "mixed"], default="valid") + parser.add_argument("--dataset-dir", default="artifacts/ingestion-load/dataset") + parser.add_argument("--artifact-dir", default="artifacts/ingestion-load") + parser.add_argument("--poll-seconds", type=float, default=5) + parser.add_argument("--request-timeout-seconds", type=float, default=180) + parser.add_argument("--timeout-seconds", type=int, default=1800) + return parser.parse_args() + + +if __name__ == "__main__": + summary = run_load_test(parse_args()) + print(json.dumps({"artifact_path": summary["artifact_path"], "final_run": summary["final_run"]}, indent=2)) diff --git a/scripts/schema.local.sql b/scripts/schema.local.sql index 9d63331..d3611f6 100644 --- a/scripts/schema.local.sql +++ b/scripts/schema.local.sql @@ -48,10 +48,17 @@ CREATE TABLE IF NOT EXISTS documents ( error_message TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + upload_completed_at TIMESTAMPTZ, + extract_enqueued_at TIMESTAMPTZ, + extract_started_at TIMESTAMPTZ, + extract_finished_at TIMESTAMPTZ, + index_enqueued_at TIMESTAMPTZ, + index_started_at TIMESTAMPTZ, + index_finished_at TIMESTAMPTZ, CONSTRAINT chk_file_size CHECK (file_size_bytes > 0 AND file_size_bytes <= 10485760), CONSTRAINT chk_page_count CHECK (page_count IS NULL OR (page_count > 0 AND page_count <= 10)), - CONSTRAINT chk_status CHECK (status IN ('pending_upload', 'uploaded', 'indexing', 'ready', 'failed')) + CONSTRAINT chk_status CHECK (status IN ('pending_upload', 'uploading', 'uploaded', 'extracting', 'indexing', 'indexed', 'ready', 'failed')) ); CREATE UNIQUE INDEX IF NOT EXISTS idx_documents_workspace_hash @@ -62,6 +69,22 @@ CREATE INDEX IF NOT EXISTS idx_documents_workspace_status ON documents(workspace ALTER TABLE documents ADD COLUMN IF NOT EXISTS ingestion_run_id UUID; +ALTER TABLE documents + ADD COLUMN IF NOT EXISTS upload_completed_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS extract_enqueued_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS extract_started_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS extract_finished_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS index_enqueued_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS index_started_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS index_finished_at TIMESTAMPTZ; + +ALTER TABLE documents + DROP CONSTRAINT IF EXISTS chk_status; + +ALTER TABLE documents + ADD CONSTRAINT chk_status + CHECK (status IN ('pending_upload', 'uploading', 'uploaded', 'extracting', 'indexing', 'indexed', 'ready', 'failed')); + DO $$ BEGIN ALTER TABLE documents diff --git a/scripts/schema.supabase.sql b/scripts/schema.supabase.sql index 9d63331..d3611f6 100644 --- a/scripts/schema.supabase.sql +++ b/scripts/schema.supabase.sql @@ -48,10 +48,17 @@ CREATE TABLE IF NOT EXISTS documents ( error_message TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + upload_completed_at TIMESTAMPTZ, + extract_enqueued_at TIMESTAMPTZ, + extract_started_at TIMESTAMPTZ, + extract_finished_at TIMESTAMPTZ, + index_enqueued_at TIMESTAMPTZ, + index_started_at TIMESTAMPTZ, + index_finished_at TIMESTAMPTZ, CONSTRAINT chk_file_size CHECK (file_size_bytes > 0 AND file_size_bytes <= 10485760), CONSTRAINT chk_page_count CHECK (page_count IS NULL OR (page_count > 0 AND page_count <= 10)), - CONSTRAINT chk_status CHECK (status IN ('pending_upload', 'uploaded', 'indexing', 'ready', 'failed')) + CONSTRAINT chk_status CHECK (status IN ('pending_upload', 'uploading', 'uploaded', 'extracting', 'indexing', 'indexed', 'ready', 'failed')) ); CREATE UNIQUE INDEX IF NOT EXISTS idx_documents_workspace_hash @@ -62,6 +69,22 @@ CREATE INDEX IF NOT EXISTS idx_documents_workspace_status ON documents(workspace ALTER TABLE documents ADD COLUMN IF NOT EXISTS ingestion_run_id UUID; +ALTER TABLE documents + ADD COLUMN IF NOT EXISTS upload_completed_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS extract_enqueued_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS extract_started_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS extract_finished_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS index_enqueued_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS index_started_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS index_finished_at TIMESTAMPTZ; + +ALTER TABLE documents + DROP CONSTRAINT IF EXISTS chk_status; + +ALTER TABLE documents + ADD CONSTRAINT chk_status + CHECK (status IN ('pending_upload', 'uploading', 'uploaded', 'extracting', 'indexing', 'indexed', 'ready', 'failed')); + DO $$ BEGIN ALTER TABLE documents diff --git a/server/.env.example b/server/.env.example index ced2831..6160540 100644 --- a/server/.env.example +++ b/server/.env.example @@ -7,3 +7,8 @@ ENVIRONMENT=development API_HOST=0.0.0.0 API_PORT=8000 DAILY_TOKEN_LIMIT=100000 +MAX_QUERY_DOCUMENTS=10 +MAX_QUERY_TOTAL_PAGES=100 +INGEST_STALE_UPLOADED_SECONDS=3600 +INGEST_STALE_EXTRACTING_SECONDS=1800 +INGEST_STALE_INDEXING_SECONDS=3600 diff --git a/server/app/api/documents.py b/server/app/api/documents.py index d088006..596be56 100644 --- a/server/app/api/documents.py +++ b/server/app/api/documents.py @@ -21,12 +21,17 @@ from app.api.deps import get_workspace_id from app.config import settings +from app.core.ingestion_reconciliation import ( + configured_stale_thresholds, + reconcile_stale_ingestion, +) from app.core.ingestion_runs import ( - derive_ingestion_run_status, - empty_document_status_counts, + document_status_counts_for_run as core_document_status_counts_for_run, + refresh_ingestion_run_status as core_refresh_ingestion_run_status, ) from app.core.ingestion_policy import ( IngestionFailureCategory, + failure_category_from_message, format_bytes, ingestion_failure_message, is_retryable_failure, @@ -41,10 +46,16 @@ DocumentListItem, DocumentListResponse, DocumentProgress, + IngestionHealthFailureSummary, + IngestionHealthResponse, + IngestionReconciliationResponse, + IngestionTiming, IngestionQueueStatusItem, IngestionQueueStatusResponse, IngestionRunResponse, IngestionRunStatusCounts, + ReconciledDocumentItem, + ReconciledIngestionRunItem, UploadCompleteBatchItem, UploadCompleteBatchRequest, UploadCompleteBatchResponse, @@ -78,6 +89,28 @@ "ready", "failed", } +ACTIVE_INGESTION_STATUSES = ("uploaded", "extracting", "indexing") +INGESTION_TIMING_COLUMNS = ( + "upload_completed_at", + "extract_enqueued_at", + "extract_started_at", + "extract_finished_at", + "index_enqueued_at", + "index_started_at", + "index_finished_at", +) +EXPECTED_REJECTION_CATEGORIES = { + IngestionFailureCategory.VALIDATION, + IngestionFailureCategory.PAGE_LIMIT, + IngestionFailureCategory.UNSUPPORTED_CONTENT, +} +INFRASTRUCTURE_FAILURE_CATEGORIES = { + IngestionFailureCategory.UPLOAD_STORAGE, + IngestionFailureCategory.EXTRACTION, + IngestionFailureCategory.INDEXING, + IngestionFailureCategory.BUDGET, + IngestionFailureCategory.TRANSIENT_INFRASTRUCTURE, +} def _idempotency_hash(payload_key: str) -> str: @@ -138,6 +171,64 @@ def _document_columns(db: Session) -> set[str]: return {col["name"] for col in inspect(bind).get_columns("documents")} +def _timing_select_fields(columns: set[str]) -> list[str]: + return [ + column_name if column_name in columns else f"NULL AS {column_name}" + for column_name in INGESTION_TIMING_COLUMNS + ] + + +def _timing_from_row(row) -> IngestionTiming: + return IngestionTiming( + **{column_name: row[column_name] for column_name in INGESTION_TIMING_COLUMNS} + ) + + +def _set_optional_timestamp_updates( + *, + update_fields: list[str], + columns: set[str], + set_now: tuple[str, ...] = (), + clear: tuple[str, ...] = (), +) -> None: + for column_name in set_now: + if column_name in columns: + update_fields.append(f"{column_name} = :updated_at") + for column_name in clear: + if column_name in columns: + update_fields.append(f"{column_name} = NULL") + + +def _age_seconds(now: datetime, timestamp_value: datetime | None) -> int | None: + if timestamp_value is None: + return None + if timestamp_value.tzinfo is None: + timestamp_value = timestamp_value.replace(tzinfo=UTC) + return max(0, int((now - timestamp_value.astimezone(UTC)).total_seconds())) + + +def _failure_summary(error_messages: list[str | None]) -> IngestionHealthFailureSummary: + expected_rejections = 0 + infrastructure_failures = 0 + unknown_failures = 0 + + for error_message in error_messages: + category = failure_category_from_message(error_message) + if category in EXPECTED_REJECTION_CATEGORIES: + expected_rejections += 1 + elif category in INFRASTRUCTURE_FAILURE_CATEGORIES: + infrastructure_failures += 1 + else: + unknown_failures += 1 + + return IngestionHealthFailureSummary( + total_failed=len(error_messages), + expected_rejections=expected_rejections, + infrastructure_failures=infrastructure_failures, + unknown_failures=unknown_failures, + ) + + def _table_exists(db: Session, table_name: str) -> bool: return inspect(db.get_bind()).has_table(table_name) @@ -279,75 +370,15 @@ def _create_ingestion_run( def _document_status_counts_for_run( *, db: Session, workspace_id: uuid.UUID, run_id: uuid.UUID ) -> dict[str, int]: - counts = empty_document_status_counts() - rows = ( - db.execute( - text( - """ - SELECT status, COUNT(*) AS count - FROM documents - WHERE workspace_id = :workspace_id - AND ingestion_run_id = :run_id - GROUP BY status - """ - ), - {"workspace_id": workspace_id, "run_id": run_id}, - ) - .mappings() - .all() - ) - for row in rows: - status_name = str(row["status"]) - counts[status_name] = int(row["count"] or 0) - counts["total"] = sum(counts.values()) - return counts + return core_document_status_counts_for_run(db=db, workspace_id=workspace_id, run_id=run_id) def _refresh_ingestion_run_status( *, db: Session, workspace_id: uuid.UUID, run_id: uuid.UUID ) -> str: - run_row = ( - db.execute( - text( - """ - SELECT accepted_documents, rejected_documents - FROM ingestion_runs - WHERE id = :run_id - AND workspace_id = :workspace_id - LIMIT 1 - """ - ), - {"workspace_id": workspace_id, "run_id": run_id}, - ) - .mappings() - .first() - ) - if run_row is None: + run_status = core_refresh_ingestion_run_status(db=db, workspace_id=workspace_id, run_id=run_id) + if run_status is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ingestion run not found") - - status_counts = _document_status_counts_for_run(db=db, workspace_id=workspace_id, run_id=run_id) - run_status = derive_ingestion_run_status( - status_counts=status_counts, - accepted_documents=int(run_row["accepted_documents"] or 0), - rejected_documents=int(run_row["rejected_documents"] or 0), - ) - db.execute( - text( - """ - UPDATE ingestion_runs - SET status = :status, - updated_at = :updated_at - WHERE id = :run_id - AND workspace_id = :workspace_id - """ - ), - { - "status": run_status, - "updated_at": datetime.now(UTC), - "workspace_id": workspace_id, - "run_id": run_id, - }, - ) return run_status @@ -356,6 +387,29 @@ def _registry_count(registry) -> int: return int(count_value() if callable(count_value) else count_value) +def _queue_status_items(redis_conn: Redis) -> list[IngestionQueueStatusItem]: + queue_items: list[IngestionQueueStatusItem] = [] + for queue_name in ("ingest_extract", "ingest_index"): + queue = Queue(queue_name, connection=redis_conn) + queue_items.append( + IngestionQueueStatusItem( + name=queue_name, + queued_count=len(queue), + started_count=_registry_count( + StartedJobRegistry(queue_name, connection=redis_conn) + ), + deferred_count=_registry_count( + DeferredJobRegistry(queue_name, connection=redis_conn) + ), + scheduled_count=_registry_count( + ScheduledJobRegistry(queue_name, connection=redis_conn) + ), + failed_count=_registry_count(FailedJobRegistry(queue_name, connection=redis_conn)), + ) + ) + return queue_items + + def _enqueue_extract( *, workspace_id: uuid.UUID, @@ -433,6 +487,7 @@ def list_documents( error_message_select = ( "error_message" if "error_message" in columns else "NULL AS error_message" ) + timing_select = ", ".join(_timing_select_fields(columns)) where_clauses = ["workspace_id = :workspace_id"] params: dict[str, object] = {"workspace_id": workspace_id, "limit": limit, "offset": offset} @@ -450,7 +505,8 @@ def list_documents( list_sql = text( f""" SELECT id, filename, {content_type_select}, file_size_bytes, {page_count_select}, - {ingestion_run_id_select}, status, {error_message_select}, created_at, updated_at + {ingestion_run_id_select}, status, {error_message_select}, created_at, updated_at, + {timing_select} FROM documents WHERE {where_sql} ORDER BY created_at DESC @@ -471,6 +527,7 @@ def list_documents( error_message=row["error_message"], created_at=row["created_at"], updated_at=row["updated_at"], + timing=_timing_from_row(row), ) for row in rows ] @@ -483,29 +540,132 @@ def get_ingestion_queues( workspace_id: uuid.UUID = Depends(get_workspace_id), ) -> IngestionQueueStatusResponse: redis_conn = Redis.from_url(settings.REDIS_URL) - queue_items: list[IngestionQueueStatusItem] = [] - for queue_name in ("ingest_extract", "ingest_index"): - queue = Queue(queue_name, connection=redis_conn) - queue_items.append( - IngestionQueueStatusItem( - name=queue_name, - queued_count=len(queue), - started_count=_registry_count( - StartedJobRegistry(queue_name, connection=redis_conn) - ), - deferred_count=_registry_count( - DeferredJobRegistry(queue_name, connection=redis_conn) - ), - scheduled_count=_registry_count( - ScheduledJobRegistry(queue_name, connection=redis_conn) + # Keep the endpoint workspace-authenticated even though queue depth is global. + _ = workspace_id + return IngestionQueueStatusResponse(queues=_queue_status_items(redis_conn)) + + +@router.get("/ingestion-health", response_model=IngestionHealthResponse) +def get_ingestion_health( + workspace_id: uuid.UUID = Depends(get_workspace_id), + db: Session = Depends(get_db), +) -> IngestionHealthResponse: + now = datetime.now(UTC) + stale_thresholds = configured_stale_thresholds( + uploaded_seconds=settings.INGEST_STALE_UPLOADED_SECONDS, + extracting_seconds=settings.INGEST_STALE_EXTRACTING_SECONDS, + indexing_seconds=settings.INGEST_STALE_INDEXING_SECONDS, + ) + + active_rows = ( + db.execute( + text( + """ + SELECT status, updated_at + FROM documents + WHERE workspace_id = :workspace_id + AND status IN ('uploaded', 'extracting', 'indexing') + """ + ), + {"workspace_id": workspace_id}, + ) + .mappings() + .all() + ) + active_by_status = {status_name: 0 for status_name in ACTIVE_INGESTION_STATUSES} + stale_active_count = 0 + oldest_active_age_seconds: int | None = None + for row in active_rows: + status_name = str(row["status"]) + active_by_status[status_name] = active_by_status.get(status_name, 0) + 1 + age = _age_seconds(now, row["updated_at"]) + if age is not None: + oldest_active_age_seconds = ( + age if oldest_active_age_seconds is None else max(oldest_active_age_seconds, age) + ) + threshold = stale_thresholds.get(status_name) + if threshold is not None and age is not None and age > threshold: + stale_active_count += 1 + + active_run_count = int( + db.execute( + text( + """ + SELECT COUNT(*) + FROM ingestion_runs + WHERE workspace_id = :workspace_id + AND status IN ('preparing', 'processing') + """ + ), + {"workspace_id": workspace_id}, + ).scalar_one() + or 0 + ) + + failed_messages = [ + row["error_message"] + for row in ( + db.execute( + text( + """ + SELECT error_message + FROM documents + WHERE workspace_id = :workspace_id + AND status = 'failed' + """ ), - failed_count=_registry_count(FailedJobRegistry(queue_name, connection=redis_conn)), + {"workspace_id": workspace_id}, ) + .mappings() + .all() ) + ] - # Keep the endpoint workspace-authenticated even though queue depth is global. - _ = workspace_id - return IngestionQueueStatusResponse(queues=queue_items) + redis_conn = Redis.from_url(settings.REDIS_URL) + return IngestionHealthResponse( + generated_at=now, + queues=_queue_status_items(redis_conn), + active_document_count=len(active_rows), + active_documents_by_status=active_by_status, + stale_active_document_count=stale_active_count, + oldest_active_document_age_seconds=oldest_active_age_seconds, + active_ingestion_run_count=active_run_count, + stale_thresholds_seconds=stale_thresholds, + failures=_failure_summary(failed_messages), + ) + + +@router.post("/ingestion-reconcile", response_model=IngestionReconciliationResponse) +def reconcile_ingestion( + workspace_id: uuid.UUID = Depends(get_workspace_id), + db: Session = Depends(get_db), +) -> IngestionReconciliationResponse: + result = reconcile_stale_ingestion( + db=db, + workspace_id=workspace_id, + stale_after_seconds=configured_stale_thresholds( + uploaded_seconds=settings.INGEST_STALE_UPLOADED_SECONDS, + extracting_seconds=settings.INGEST_STALE_EXTRACTING_SECONDS, + indexing_seconds=settings.INGEST_STALE_INDEXING_SECONDS, + ), + ) + db.commit() + return IngestionReconciliationResponse( + failed_document_count=len(result.documents), + refreshed_run_count=len(result.runs), + documents=[ + ReconciledDocumentItem( + id=document.id, + ingestion_run_id=document.ingestion_run_id, + previous_status=document.previous_status, + status=document.status, + error_message=document.error_message, + updated_at=document.updated_at, + ) + for document in result.documents + ], + runs=[ReconciledIngestionRunItem(id=run.id, status=run.status) for run in result.runs], + ) @router.get("/ingestion-runs/{run_id}", response_model=IngestionRunResponse) @@ -587,6 +747,7 @@ def get_document( select_fields.append("pages_total") else: select_fields.append("NULL AS pages_total") + select_fields.extend(_timing_select_fields(columns)) detail_sql = text( f""" @@ -662,6 +823,7 @@ def get_document( chunks_count=chunks_count, embeddings_count=embeddings_count, ), + timing=_timing_from_row(row), ) @@ -1168,6 +1330,18 @@ def _complete_upload_document( update_fields = ["status = 'uploaded'", "updated_at = :updated_at"] if "error_message" in columns: update_fields.append("error_message = NULL") + _set_optional_timestamp_updates( + update_fields=update_fields, + columns=columns, + set_now=("upload_completed_at", "extract_enqueued_at"), + clear=( + "extract_started_at", + "extract_finished_at", + "index_enqueued_at", + "index_started_at", + "index_finished_at", + ), + ) update_result = db.execute( text( f""" @@ -1462,6 +1636,18 @@ def retry_document( update_fields.append("error_message = NULL") if "page_count" in columns: update_fields.append("page_count = NULL") + _set_optional_timestamp_updates( + update_fields=update_fields, + columns=columns, + set_now=("extract_enqueued_at",), + clear=( + "extract_started_at", + "extract_finished_at", + "index_enqueued_at", + "index_started_at", + "index_finished_at", + ), + ) db.execute( text( @@ -1589,6 +1775,26 @@ def reindex_document( } if "error_message" in columns: update_fields.append("error_message = NULL") + if has_pages: + _set_optional_timestamp_updates( + update_fields=update_fields, + columns=columns, + set_now=("index_enqueued_at",), + clear=("index_started_at", "index_finished_at"), + ) + else: + _set_optional_timestamp_updates( + update_fields=update_fields, + columns=columns, + set_now=("extract_enqueued_at",), + clear=( + "extract_started_at", + "extract_finished_at", + "index_enqueued_at", + "index_started_at", + "index_finished_at", + ), + ) db.execute( text( diff --git a/server/app/api/query.py b/server/app/api/query.py index a4e613a..004630f 100644 --- a/server/app/api/query.py +++ b/server/app/api/query.py @@ -6,7 +6,7 @@ from datetime import UTC, datetime from fastapi import APIRouter, Depends, HTTPException, status -from sqlalchemy import text +from sqlalchemy import bindparam, text from sqlalchemy.orm import Session from app.api.deps import get_current_user, get_workspace_id @@ -17,13 +17,14 @@ from app.core.llm import answer_question_strict_grounded from app.core.prompts import INSUFFICIENT_CONTEXT_MESSAGE from app.core.rate_limit import enforce_query_rate_limit -from app.core.retrieval import RetrievedChunk, retrieve_top_k_chunks +from app.core.retrieval import RetrievedChunk, retrieve_top_k_chunks_for_documents from app.core.token_budget import commit_usage, get_budget_status, release_tokens, reserve_tokens from app.db.session import get_db from app.schemas.query import QueryCitation, QueryRequest, QueryResponse, QueryUsage router = APIRouter() PROMPT_TEMPLATE_TOKENS = 200 +QUERY_READY_STATUSES = {"ready", "indexed"} def _enforce_query_rate_limit(workspace_id: uuid.UUID) -> None: @@ -35,7 +36,13 @@ def _estimate_query_tokens(question: str) -> int: def _estimate_llm_input_tokens(question: str, chunks: list[RetrievedChunk]) -> int: - return int(math.ceil(sum(chunk.token_count for chunk in chunks) + PROMPT_TEMPLATE_TOKENS + (len(question) / 4))) + return int( + math.ceil( + sum(chunk.token_count for chunk in chunks) + + PROMPT_TEMPLATE_TOKENS + + (len(question) / 4) + ) + ) def _log_query( @@ -43,7 +50,7 @@ def _log_query( *, workspace_id: uuid.UUID, user_id: str, - document_id: uuid.UUID, + document_ids: list[uuid.UUID], question: str, retrieved_chunks: list[RetrievedChunk], answer_text: str | None, @@ -104,7 +111,7 @@ def _log_query( "workspace_id": workspace_id, "user_id": user_id, "query_text": question, - "documents_searched": [document_id], + "documents_searched": document_ids, "retrieved_chunk_ids": [chunk.chunk_id for chunk in retrieved_chunks], "chunk_scores": [chunk.score for chunk in retrieved_chunks], "answer_text": answer_text, @@ -131,6 +138,76 @@ def _usage_to_response(usage: dict[str, int | datetime]) -> QueryUsage: ) +def _selected_document_ids(payload: QueryRequest) -> list[uuid.UUID]: + selected: list[uuid.UUID] = [] + for document_id in payload.selected_document_ids: + if document_id not in selected: + selected.append(document_id) + return selected + + +def resolve_query_document_ids( + *, + db: Session, + workspace_id: uuid.UUID, + payload: QueryRequest, +) -> list[uuid.UUID]: + document_ids = _selected_document_ids(payload) + if not document_ids: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="At least one document is required", + ) + if len(document_ids) > settings.MAX_QUERY_DOCUMENTS: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Query supports up to {settings.MAX_QUERY_DOCUMENTS} selected documents", + ) + + rows = ( + db.execute( + text( + """ + SELECT id, status, COALESCE(page_count, 0) AS page_count + FROM documents + WHERE workspace_id = :workspace_id + AND id IN :document_ids + """ + ).bindparams(bindparam("document_ids", expanding=True)), + {"workspace_id": workspace_id, "document_ids": document_ids}, + ) + .mappings() + .all() + ) + rows_by_id = {row["id"]: row for row in rows} + missing_ids = [document_id for document_id in document_ids if document_id not in rows_by_id] + if missing_ids: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found") + + not_ready = [ + document_id + for document_id in document_ids + if rows_by_id[document_id]["status"] not in QUERY_READY_STATUSES + ] + if not_ready: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="All selected documents must be ready or indexed for querying", + ) + + total_pages = sum(int(row["page_count"] or 0) for row in rows) + if total_pages > settings.MAX_QUERY_TOTAL_PAGES: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=( + f"Selected documents contain {total_pages} pages; " + f"query supports up to {settings.MAX_QUERY_TOTAL_PAGES} pages" + ), + ) + + return document_ids + + @router.post("", response_model=QueryResponse) def run_query( payload: QueryRequest, @@ -146,22 +223,7 @@ def run_query( detail=f"question must be between 1 and {settings.MAX_QUESTION_CHARS} characters", ) - document = db.execute( - text( - """ - SELECT id, status - FROM documents - WHERE id = :document_id - AND workspace_id = :workspace_id - LIMIT 1 - """ - ), - {"document_id": payload.document_id, "workspace_id": workspace_id}, - ).mappings().first() - if document is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found") - if document["status"] != "ready": - raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Document is not ready for querying") + document_ids = resolve_query_document_ids(db=db, workspace_id=workspace_id, payload=payload) request_started = time.perf_counter() usage_date_utc = datetime.now(UTC) @@ -174,10 +236,10 @@ def run_query( try: retrieval_started = time.perf_counter() embedding_result = embed_query_text(question) - chunks = retrieve_top_k_chunks( + chunks = retrieve_top_k_chunks_for_documents( db=db, workspace_id=workspace_id, - document_id=payload.document_id, + document_ids=document_ids, query_embedding=embedding_result.embedding, top_k=settings.TOP_K, ) @@ -185,7 +247,9 @@ def run_query( estimated_query_embedding = _estimate_query_tokens(question) estimated_input = _estimate_llm_input_tokens(question, chunks) - estimated_total = estimated_query_embedding + estimated_input + settings.LLM_MAX_OUTPUT_TOKENS + estimated_total = ( + estimated_query_embedding + estimated_input + settings.LLM_MAX_OUTPUT_TOKENS + ) reserve_tokens( db=db, workspace_id=workspace_id, @@ -198,7 +262,9 @@ def run_query( if not chunks: answer_text = INSUFFICIENT_CONTEXT_MESSAGE committed = min(embedding_result.total_tokens, reserved_amount) - commit_usage(db=db, workspace_id=workspace_id, amount=committed, usage_date_utc=usage_date_utc) + commit_usage( + db=db, workspace_id=workspace_id, amount=committed, usage_date_utc=usage_date_utc + ) if reserved_amount > committed: release_tokens( db=db, @@ -207,13 +273,15 @@ def run_query( usage_date_utc=usage_date_utc, ) reserved_amount = 0 - usage_now = get_budget_status(db=db, workspace_id=workspace_id, usage_date_utc=usage_date_utc) + usage_now = get_budget_status( + db=db, workspace_id=workspace_id, usage_date_utc=usage_date_utc + ) total_latency_ms = int((time.perf_counter() - request_started) * 1000) _log_query( db=db, workspace_id=workspace_id, user_id=user.user_id, - document_id=payload.document_id, + document_ids=document_ids, question=question, retrieved_chunks=chunks, answer_text=answer_text, @@ -226,7 +294,9 @@ def run_query( llm_output_tokens=None, total_tokens_used=committed, ) - return QueryResponse(answer=answer_text, citations=[], usage=_usage_to_response(usage_now)) + return QueryResponse( + answer=answer_text, citations=[], usage=_usage_to_response(usage_now) + ) llm_started = time.perf_counter() llm_result = answer_question_strict_grounded(question=question, chunks=chunks) @@ -237,7 +307,9 @@ def run_query( actual_total = embedding_result.total_tokens + llm_result.total_tokens committed = min(actual_total, reserved_amount) - commit_usage(db=db, workspace_id=workspace_id, amount=committed, usage_date_utc=usage_date_utc) + commit_usage( + db=db, workspace_id=workspace_id, amount=committed, usage_date_utc=usage_date_utc + ) if reserved_amount > committed: release_tokens( db=db, @@ -246,7 +318,9 @@ def run_query( usage_date_utc=usage_date_utc, ) reserved_amount = 0 - usage_now = get_budget_status(db=db, workspace_id=workspace_id, usage_date_utc=usage_date_utc) + usage_now = get_budget_status( + db=db, workspace_id=workspace_id, usage_date_utc=usage_date_utc + ) citations = [ QueryCitation( @@ -263,7 +337,7 @@ def run_query( db=db, workspace_id=workspace_id, user_id=user.user_id, - document_id=payload.document_id, + document_ids=document_ids, question=question, retrieved_chunks=chunks, answer_text=answer_text, @@ -300,18 +374,28 @@ def run_query( ) from exc except HTTPException: if reserved_amount > 0: - release_tokens(db=db, workspace_id=workspace_id, amount=reserved_amount, usage_date_utc=usage_date_utc) + release_tokens( + db=db, + workspace_id=workspace_id, + amount=reserved_amount, + usage_date_utc=usage_date_utc, + ) raise except Exception as exc: # noqa: BLE001 if reserved_amount > 0: - release_tokens(db=db, workspace_id=workspace_id, amount=reserved_amount, usage_date_utc=usage_date_utc) + release_tokens( + db=db, + workspace_id=workspace_id, + amount=reserved_amount, + usage_date_utc=usage_date_utc, + ) total_latency_ms = int((time.perf_counter() - request_started) * 1000) try: _log_query( db=db, workspace_id=workspace_id, user_id=user.user_id, - document_id=payload.document_id, + document_ids=document_ids, question=question, retrieved_chunks=[], answer_text=answer_text, diff --git a/server/app/api/query_stream.py b/server/app/api/query_stream.py index 6eac70b..3a028dd 100644 --- a/server/app/api/query_stream.py +++ b/server/app/api/query_stream.py @@ -7,7 +7,6 @@ from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import StreamingResponse -from sqlalchemy import text from sqlalchemy.orm import Session from app.api.deps import get_current_user, get_workspace_id @@ -16,6 +15,7 @@ _estimate_llm_input_tokens, _estimate_query_tokens, _log_query, + resolve_query_document_ids, ) from app.config import settings, utc_next_reset_at from app.core.auth import AuthenticatedUser @@ -23,7 +23,7 @@ from app.core.errors import BudgetExceededError from app.core.llm import LLMResult, stream_answer_question_strict_grounded from app.core.prompts import INSUFFICIENT_CONTEXT_MESSAGE -from app.core.retrieval import RetrievedChunk, retrieve_top_k_chunks +from app.core.retrieval import RetrievedChunk, retrieve_top_k_chunks_for_documents from app.core.token_budget import commit_usage, get_budget_status, release_tokens, reserve_tokens from app.db.session import get_db from app.schemas.query import QueryRequest @@ -85,32 +85,33 @@ async def event_stream(): ) return - document = db.execute( - text( - """ - SELECT id, status - FROM documents - WHERE id = :document_id - AND workspace_id = :workspace_id - LIMIT 1 - """ - ), - {"document_id": payload.document_id, "workspace_id": workspace_id}, - ).mappings().first() - if document is None: - yield _sse_event("error", _error_payload("Document not found", "DOCUMENT_NOT_FOUND")) - return - if document["status"] != "ready": - yield _sse_event("error", _error_payload("Document is not ready for querying", "DOCUMENT_NOT_READY")) + try: + document_ids = resolve_query_document_ids( + db=db, workspace_id=workspace_id, payload=payload + ) + except HTTPException as exc: + code = ( + "DOCUMENT_NOT_FOUND" + if exc.status_code == 404 + else ( + "DOCUMENT_NOT_READY" + if exc.status_code == 409 + else "INVALID_DOCUMENT_SELECTION" + ) + ) + message = ( + str(exc.detail) if isinstance(exc.detail, str) else "Invalid document selection" + ) + yield _sse_event("error", _error_payload(message, code)) return retrieval_started = time.perf_counter() embedding_result = embed_query_text(question) embedding_tokens_used = embedding_result.total_tokens - chunks = retrieve_top_k_chunks( + chunks = retrieve_top_k_chunks_for_documents( db=db, workspace_id=workspace_id, - document_id=payload.document_id, + document_ids=document_ids, query_embedding=embedding_result.embedding, top_k=settings.TOP_K, ) @@ -118,7 +119,9 @@ async def event_stream(): estimated_query_embedding = _estimate_query_tokens(question) estimated_input = _estimate_llm_input_tokens(question, chunks) - estimated_total = estimated_query_embedding + estimated_input + settings.LLM_MAX_OUTPUT_TOKENS + estimated_total = ( + estimated_query_embedding + estimated_input + settings.LLM_MAX_OUTPUT_TOKENS + ) reserve_tokens( db=db, workspace_id=workspace_id, @@ -130,14 +133,24 @@ async def event_stream(): yield _sse_event( "meta", - {"request_id": request_id, "document_id": str(payload.document_id), "top_k": settings.TOP_K}, + { + "request_id": request_id, + "document_id": str(document_ids[0]), + "document_ids": [str(document_id) for document_id in document_ids], + "top_k": settings.TOP_K, + }, ) if not chunks: answer_text = INSUFFICIENT_CONTEXT_MESSAGE yield _sse_event("delta", {"text": answer_text}) committed = min(embedding_result.total_tokens, reserved_amount) - commit_usage(db=db, workspace_id=workspace_id, amount=committed, usage_date_utc=usage_date_utc) + commit_usage( + db=db, + workspace_id=workspace_id, + amount=committed, + usage_date_utc=usage_date_utc, + ) if reserved_amount > committed: release_tokens( db=db, @@ -146,13 +159,15 @@ async def event_stream(): usage_date_utc=usage_date_utc, ) reserved_amount = 0 - usage_now = get_budget_status(db=db, workspace_id=workspace_id, usage_date_utc=usage_date_utc) + usage_now = get_budget_status( + db=db, workspace_id=workspace_id, usage_date_utc=usage_date_utc + ) total_latency_ms = int((time.perf_counter() - request_started) * 1000) _log_query( db=db, workspace_id=workspace_id, user_id=user.user_id, - document_id=payload.document_id, + document_ids=document_ids, question=question, retrieved_chunks=chunks, answer_text=answer_text, @@ -173,7 +188,9 @@ async def event_stream(): llm_started = time.perf_counter() stream_result: LLMResult | None = None streamed_parts: list[str] = [] - async for event in stream_answer_question_strict_grounded(question=question, chunks=chunks): + async for event in stream_answer_question_strict_grounded( + question=question, chunks=chunks + ): if await request.is_disconnected(): raise ConnectionError("Client disconnected") if event.type == "delta": @@ -199,7 +216,9 @@ async def event_stream(): actual_total = embedding_result.total_tokens + stream_result.total_tokens committed = min(actual_total, reserved_amount) - commit_usage(db=db, workspace_id=workspace_id, amount=committed, usage_date_utc=usage_date_utc) + commit_usage( + db=db, workspace_id=workspace_id, amount=committed, usage_date_utc=usage_date_utc + ) if reserved_amount > committed: release_tokens( db=db, @@ -208,7 +227,9 @@ async def event_stream(): usage_date_utc=usage_date_utc, ) reserved_amount = 0 - usage_now = get_budget_status(db=db, workspace_id=workspace_id, usage_date_utc=usage_date_utc) + usage_now = get_budget_status( + db=db, workspace_id=workspace_id, usage_date_utc=usage_date_utc + ) citations_payload = { "citations": [ @@ -227,7 +248,7 @@ async def event_stream(): db=db, workspace_id=workspace_id, user_id=user.user_id, - document_id=payload.document_id, + document_ids=document_ids, question=question, retrieved_chunks=chunks, answer_text=answer_text, @@ -246,7 +267,12 @@ async def event_stream(): except BudgetExceededError as exc: if reserved_amount > 0: try: - release_tokens(db=db, workspace_id=workspace_id, amount=reserved_amount, usage_date_utc=usage_date_utc) + release_tokens( + db=db, + workspace_id=workspace_id, + amount=reserved_amount, + usage_date_utc=usage_date_utc, + ) except Exception: # noqa: BLE001 db.rollback() yield _sse_event( @@ -259,13 +285,23 @@ async def event_stream(): except ConnectionError: if reserved_amount > 0: try: - release_tokens(db=db, workspace_id=workspace_id, amount=reserved_amount, usage_date_utc=usage_date_utc) + release_tokens( + db=db, + workspace_id=workspace_id, + amount=reserved_amount, + usage_date_utc=usage_date_utc, + ) except Exception: # noqa: BLE001 db.rollback() except HTTPException as exc: if reserved_amount > 0: try: - release_tokens(db=db, workspace_id=workspace_id, amount=reserved_amount, usage_date_utc=usage_date_utc) + release_tokens( + db=db, + workspace_id=workspace_id, + amount=reserved_amount, + usage_date_utc=usage_date_utc, + ) except Exception: # noqa: BLE001 db.rollback() message = str(exc.detail) if isinstance(exc.detail, str) else "Query failed" @@ -273,7 +309,12 @@ async def event_stream(): except Exception as exc: # noqa: BLE001 if reserved_amount > 0: try: - release_tokens(db=db, workspace_id=workspace_id, amount=reserved_amount, usage_date_utc=usage_date_utc) + release_tokens( + db=db, + workspace_id=workspace_id, + amount=reserved_amount, + usage_date_utc=usage_date_utc, + ) except Exception: # noqa: BLE001 db.rollback() total_latency_ms = int((time.perf_counter() - request_started) * 1000) @@ -282,7 +323,7 @@ async def event_stream(): db=db, workspace_id=workspace_id, user_id=user.user_id, - document_id=payload.document_id, + document_ids=payload.selected_document_ids, question=payload.question.strip(), retrieved_chunks=chunks, answer_text=answer_text, diff --git a/server/app/config.py b/server/app/config.py index 73c30d2..551fbc0 100644 --- a/server/app/config.py +++ b/server/app/config.py @@ -26,6 +26,8 @@ class Settings(BaseSettings): LLM_MAX_OUTPUT_TOKENS: int = 2000 TOP_K: int = 5 MAX_QUESTION_CHARS: int = 500 + MAX_QUERY_DOCUMENTS: int = 10 + MAX_QUERY_TOTAL_PAGES: int = 100 EMBEDDING_MODEL: str = "text-embedding-3-small" EMBEDDING_DIM: int = 1536 OPENAI_EMBEDDING_TIMEOUT_SECONDS: int = 300 @@ -38,6 +40,9 @@ class Settings(BaseSettings): EMBEDDING_BATCH_SIZE: int = 32 INGEST_EXTRACT_JOB_TIMEOUT_SECONDS: int = 900 INGEST_INDEX_JOB_TIMEOUT_SECONDS: int = 1800 + INGEST_STALE_UPLOADED_SECONDS: int = 3600 + INGEST_STALE_EXTRACTING_SECONDS: int = 1800 + INGEST_STALE_INDEXING_SECONDS: int = 3600 ALLOWED_CONTENT_TYPES: list[str] = Field(default_factory=lambda: ["application/pdf"]) model_config = SettingsConfigDict(env_file=".env", extra="ignore") diff --git a/server/app/core/ingestion_reconciliation.py b/server/app/core/ingestion_reconciliation.py new file mode 100644 index 0000000..43e0f2e --- /dev/null +++ b/server/app/core/ingestion_reconciliation.py @@ -0,0 +1,235 @@ +from __future__ import annotations + +from dataclasses import dataclass +from datetime import UTC, datetime, timedelta +import uuid + +from sqlalchemy import text +from sqlalchemy.orm import Session + +from app.core.ingestion_policy import ( + IngestionFailureCategory, + ingestion_failure_message, +) +from app.core.ingestion_runs import ( + PROCESSING_DOCUMENT_STATUSES, + refresh_ingestion_run_status, +) + + +@dataclass(frozen=True) +class ReconciledDocument: + id: uuid.UUID + workspace_id: uuid.UUID + ingestion_run_id: uuid.UUID | None + previous_status: str + status: str + error_message: str + updated_at: datetime + + +@dataclass(frozen=True) +class ReconciledIngestionRun: + id: uuid.UUID + workspace_id: uuid.UUID + status: str + + +@dataclass(frozen=True) +class IngestionReconciliationResult: + documents: list[ReconciledDocument] + runs: list[ReconciledIngestionRun] + + +def _document_columns(db: Session) -> set[str]: + rows = db.execute( + text( + """ + SELECT column_name + FROM information_schema.columns + WHERE table_schema = current_schema() + AND table_name = 'documents' + """ + ) + ).scalars() + return {str(column_name) for column_name in rows} + + +def configured_stale_thresholds( + *, + uploaded_seconds: int, + extracting_seconds: int, + indexing_seconds: int, +) -> dict[str, int]: + thresholds = { + "uploaded": uploaded_seconds, + "extracting": extracting_seconds, + "indexing": indexing_seconds, + } + return { + status_name: int(seconds) + for status_name, seconds in thresholds.items() + if status_name in PROCESSING_DOCUMENT_STATUSES and int(seconds) > 0 + } + + +def stale_document_failure_message(*, previous_status: str, threshold_seconds: int) -> str: + return ingestion_failure_message( + IngestionFailureCategory.TRANSIENT_INFRASTRUCTURE, + ( + f"Document stayed in {previous_status} for more than " + f"{threshold_seconds} seconds. Ingestion may have been interrupted; " + "retry the document to restart ingestion." + ), + )[:2000] + + +def _active_run_rows(*, db: Session, workspace_id: uuid.UUID | None) -> list[dict[str, uuid.UUID]]: + workspace_clause = "AND workspace_id = :workspace_id" if workspace_id else "" + params: dict[str, object] = {} + if workspace_id: + params["workspace_id"] = workspace_id + return list( + db.execute( + text( + f""" + SELECT id, workspace_id + FROM ingestion_runs + WHERE status IN ('preparing', 'processing') + {workspace_clause} + """ + ), + params, + ) + .mappings() + .all() + ) + + +def _stale_document_rows( + *, + db: Session, + workspace_id: uuid.UUID | None, + previous_status: str, + cutoff: datetime, +) -> list[dict[str, object]]: + workspace_clause = "AND workspace_id = :workspace_id" if workspace_id else "" + params: dict[str, object] = { + "previous_status": previous_status, + "cutoff": cutoff, + } + if workspace_id: + params["workspace_id"] = workspace_id + return list( + db.execute( + text( + f""" + SELECT id, workspace_id, ingestion_run_id + FROM documents + WHERE status = :previous_status + AND updated_at < :cutoff + {workspace_clause} + """ + ), + params, + ) + .mappings() + .all() + ) + + +def reconcile_stale_ingestion( + *, + db: Session, + stale_after_seconds: dict[str, int], + workspace_id: uuid.UUID | None = None, + now: datetime | None = None, +) -> IngestionReconciliationResult: + now_utc = now.astimezone(UTC) if now else datetime.now(UTC) + reconciled_documents: list[ReconciledDocument] = [] + run_keys: set[tuple[uuid.UUID, uuid.UUID]] = set() + columns = _document_columns(db) + + for previous_status, threshold_seconds in stale_after_seconds.items(): + if previous_status not in PROCESSING_DOCUMENT_STATUSES or threshold_seconds <= 0: + continue + cutoff = now_utc - timedelta(seconds=threshold_seconds) + error_message = stale_document_failure_message( + previous_status=previous_status, + threshold_seconds=threshold_seconds, + ) + rows = _stale_document_rows( + db=db, + workspace_id=workspace_id, + previous_status=previous_status, + cutoff=cutoff, + ) + for row in rows: + document_id = row["id"] + row_workspace_id = row["workspace_id"] + ingestion_run_id = row["ingestion_run_id"] + update_fields = [ + "status = 'failed'", + "error_message = :error_message", + "updated_at = :updated_at", + ] + if previous_status == "extracting" and "extract_finished_at" in columns: + update_fields.append("extract_finished_at = :updated_at") + if previous_status == "indexing" and "index_finished_at" in columns: + update_fields.append("index_finished_at = :updated_at") + db.execute( + text( + f""" + UPDATE documents + SET {", ".join(update_fields)} + WHERE id = :document_id + AND workspace_id = :workspace_id + AND status = :previous_status + """ + ), + { + "document_id": document_id, + "workspace_id": row_workspace_id, + "previous_status": previous_status, + "error_message": error_message, + "updated_at": now_utc, + }, + ) + if ingestion_run_id: + run_keys.add((row_workspace_id, ingestion_run_id)) + reconciled_documents.append( + ReconciledDocument( + id=document_id, + workspace_id=row_workspace_id, + ingestion_run_id=ingestion_run_id, + previous_status=previous_status, + status="failed", + error_message=error_message, + updated_at=now_utc, + ) + ) + + for row in _active_run_rows(db=db, workspace_id=workspace_id): + run_keys.add((row["workspace_id"], row["id"])) + + reconciled_runs: list[ReconciledIngestionRun] = [] + for run_workspace_id, run_id in sorted(run_keys, key=lambda item: (str(item[0]), str(item[1]))): + run_status = refresh_ingestion_run_status( + db=db, + workspace_id=run_workspace_id, + run_id=run_id, + updated_at=now_utc, + ) + if run_status is not None: + reconciled_runs.append( + ReconciledIngestionRun( + id=run_id, + workspace_id=run_workspace_id, + status=run_status, + ) + ) + + return IngestionReconciliationResult( + documents=reconciled_documents, + runs=reconciled_runs, + ) diff --git a/server/app/core/ingestion_runs.py b/server/app/core/ingestion_runs.py index e82ada9..7b3e88a 100644 --- a/server/app/core/ingestion_runs.py +++ b/server/app/core/ingestion_runs.py @@ -1,5 +1,21 @@ from __future__ import annotations +from datetime import UTC, datetime +import uuid + +from sqlalchemy import text +from sqlalchemy.orm import Session + +DOCUMENT_STATUSES = ( + "pending_upload", + "uploading", + "uploaded", + "extracting", + "indexing", + "indexed", + "ready", + "failed", +) PROCESSING_DOCUMENT_STATUSES = {"uploaded", "extracting", "indexing"} SUCCESS_DOCUMENT_STATUSES = {"ready", "indexed"} FAILED_DOCUMENT_STATUSES = {"failed"} @@ -7,16 +23,7 @@ def empty_document_status_counts() -> dict[str, int]: - return { - "pending_upload": 0, - "uploading": 0, - "uploaded": 0, - "extracting": 0, - "indexing": 0, - "ready": 0, - "indexed": 0, - "failed": 0, - } + return {status_name: 0 for status_name in DOCUMENT_STATUSES} def derive_ingestion_run_status( @@ -53,3 +60,82 @@ def derive_ingestion_run_status( if processing_count > 0 or pending_count < accepted_documents: return "processing" return "preparing" + + +def document_status_counts_for_run( + *, db: Session, workspace_id: uuid.UUID, run_id: uuid.UUID +) -> dict[str, int]: + counts = empty_document_status_counts() + rows = ( + db.execute( + text( + """ + SELECT status, COUNT(*) AS count + FROM documents + WHERE workspace_id = :workspace_id + AND ingestion_run_id = :run_id + GROUP BY status + """ + ), + {"workspace_id": workspace_id, "run_id": run_id}, + ) + .mappings() + .all() + ) + for row in rows: + status_name = str(row["status"]) + counts[status_name] = int(row["count"] or 0) + counts["total"] = sum(counts.values()) + return counts + + +def refresh_ingestion_run_status( + *, + db: Session, + workspace_id: uuid.UUID, + run_id: uuid.UUID, + updated_at: datetime | None = None, +) -> str | None: + run_row = ( + db.execute( + text( + """ + SELECT accepted_documents, rejected_documents + FROM ingestion_runs + WHERE id = :run_id + AND workspace_id = :workspace_id + LIMIT 1 + """ + ), + {"workspace_id": workspace_id, "run_id": run_id}, + ) + .mappings() + .first() + ) + if run_row is None: + return None + + status_counts = document_status_counts_for_run(db=db, workspace_id=workspace_id, run_id=run_id) + run_status = derive_ingestion_run_status( + status_counts=status_counts, + accepted_documents=int(run_row["accepted_documents"] or 0), + rejected_documents=int(run_row["rejected_documents"] or 0), + ) + db.execute( + text( + """ + UPDATE ingestion_runs + SET status = :status, + updated_at = :updated_at + WHERE id = :run_id + AND workspace_id = :workspace_id + """ + ), + { + "status": run_status, + "updated_at": updated_at or datetime.now(UTC), + "workspace_id": workspace_id, + "run_id": run_id, + }, + ) + return run_status diff --git a/server/app/core/retrieval.py b/server/app/core/retrieval.py index 623c521..53940cb 100644 --- a/server/app/core/retrieval.py +++ b/server/app/core/retrieval.py @@ -2,7 +2,7 @@ from typing import Any import uuid -from sqlalchemy import text +from sqlalchemy import bindparam, text from sqlalchemy.orm import Session @@ -33,6 +33,24 @@ def retrieve_top_k_chunks( query_embedding: list[float], top_k: int, ) -> list[RetrievedChunk]: + return retrieve_top_k_chunks_for_documents( + db=db, + workspace_id=workspace_id, + document_ids=[document_id], + query_embedding=query_embedding, + top_k=top_k, + ) + + +def retrieve_top_k_chunks_for_documents( + db: Session, + workspace_id: uuid.UUID, + document_ids: list[uuid.UUID], + query_embedding: list[float], + top_k: int, +) -> list[RetrievedChunk]: + if not document_ids: + return [] vector_literal = _embedding_to_vector_literal(query_embedding) sql = text( """ @@ -51,19 +69,19 @@ def retrieve_top_k_chunks( AND dp.document_id = c.document_id AND dp.page_number = c.page_start WHERE ce.workspace_id = :workspace_id - AND ce.document_id = :document_id + AND ce.document_id IN :document_ids AND c.workspace_id = :workspace_id - AND c.document_id = :document_id + AND c.document_id IN :document_ids ORDER BY ce.embedding <=> CAST(:query_embedding AS vector) LIMIT :top_k """ - ) + ).bindparams(bindparam("document_ids", expanding=True)) rows: list[dict[str, Any]] = ( db.execute( sql, { "workspace_id": workspace_id, - "document_id": document_id, + "document_ids": document_ids, "query_embedding": vector_literal, "top_k": top_k, }, diff --git a/server/app/db/models.py b/server/app/db/models.py index fa5db69..4c84c81 100644 --- a/server/app/db/models.py +++ b/server/app/db/models.py @@ -47,6 +47,27 @@ class IngestionRun(Base): updated_at: Mapped[DateTime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now() ) + upload_completed_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) + extract_enqueued_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) + extract_started_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) + extract_finished_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) + index_enqueued_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) + index_started_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) + index_finished_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) class Document(Base): diff --git a/server/app/schemas/documents.py b/server/app/schemas/documents.py index 7bf4f4e..4c0074f 100644 --- a/server/app/schemas/documents.py +++ b/server/app/schemas/documents.py @@ -92,6 +92,16 @@ class DocumentJobResponse(BaseModel): job_id: str +class IngestionTiming(BaseModel): + upload_completed_at: datetime | None = None + extract_enqueued_at: datetime | None = None + extract_started_at: datetime | None = None + extract_finished_at: datetime | None = None + index_enqueued_at: datetime | None = None + index_started_at: datetime | None = None + index_finished_at: datetime | None = None + + class DocumentListItem(BaseModel): id: uuid.UUID filename: str @@ -103,6 +113,7 @@ class DocumentListItem(BaseModel): error_message: str | None = None created_at: datetime updated_at: datetime + timing: IngestionTiming | None = None class DocumentListResponse(BaseModel): @@ -132,6 +143,7 @@ class DocumentDetailResponse(BaseModel): created_at: datetime updated_at: datetime progress: DocumentProgress + timing: IngestionTiming | None = None class IngestionRunStatusCounts(BaseModel): @@ -169,3 +181,43 @@ class IngestionQueueStatusItem(BaseModel): class IngestionQueueStatusResponse(BaseModel): queues: list[IngestionQueueStatusItem] + + +class IngestionHealthFailureSummary(BaseModel): + total_failed: int + expected_rejections: int + infrastructure_failures: int + unknown_failures: int + + +class IngestionHealthResponse(BaseModel): + generated_at: datetime + queues: list[IngestionQueueStatusItem] + active_document_count: int + active_documents_by_status: dict[str, int] + stale_active_document_count: int + oldest_active_document_age_seconds: int | None = None + active_ingestion_run_count: int + stale_thresholds_seconds: dict[str, int] + failures: IngestionHealthFailureSummary + + +class ReconciledDocumentItem(BaseModel): + id: uuid.UUID + ingestion_run_id: uuid.UUID | None = None + previous_status: str + status: str + error_message: str + updated_at: datetime + + +class ReconciledIngestionRunItem(BaseModel): + id: uuid.UUID + status: str + + +class IngestionReconciliationResponse(BaseModel): + failed_document_count: int + refreshed_run_count: int + documents: list[ReconciledDocumentItem] + runs: list[ReconciledIngestionRunItem] diff --git a/server/app/schemas/query.py b/server/app/schemas/query.py index ac051aa..3a3e587 100644 --- a/server/app/schemas/query.py +++ b/server/app/schemas/query.py @@ -1,13 +1,29 @@ import uuid from datetime import datetime -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, model_validator class QueryRequest(BaseModel): - document_id: uuid.UUID + document_id: uuid.UUID | None = None + document_ids: list[uuid.UUID] | None = None question: str = Field(min_length=1, max_length=500) + @model_validator(mode="after") + def require_selected_documents(self) -> "QueryRequest": + if self.document_id is None and not self.document_ids: + raise ValueError("document_id or document_ids is required") + return self + + @property + def selected_document_ids(self) -> list[uuid.UUID]: + selected: list[uuid.UUID] = [] + if self.document_ids: + selected.extend(self.document_ids) + if self.document_id is not None and self.document_id not in selected: + selected.insert(0, self.document_id) + return selected + class QueryCitation(BaseModel): document_id: uuid.UUID diff --git a/server/tests/test_api/test_documents_jobs.py b/server/tests/test_api/test_documents_jobs.py new file mode 100644 index 0000000..c0d8907 --- /dev/null +++ b/server/tests/test_api/test_documents_jobs.py @@ -0,0 +1,224 @@ +from __future__ import annotations + +from types import SimpleNamespace +import uuid + +import pytest +from fastapi import HTTPException + +from app.api import documents +from app.core.ingestion_policy import ( + IngestionFailureCategory, + ingestion_failure_message, +) + + +TIMING_COLUMNS = { + "error_message", + "storage_bucket", + "ingestion_run_id", + "page_count", + "upload_completed_at", + "extract_enqueued_at", + "extract_started_at", + "extract_finished_at", + "index_enqueued_at", + "index_started_at", + "index_finished_at", +} + + +class FakeResult: + def __init__(self, *, first_row=None, scalar_value=0) -> None: + self.first_row = first_row + self.scalar_value = scalar_value + + def mappings(self): + return self + + def first(self): + return self.first_row + + def scalar_one(self): + return self.scalar_value + + +class FakeDb: + def __init__(self, *, document_row: dict[str, object], pages_count: int = 0) -> None: + self.document_row = document_row + self.pages_count = pages_count + self.statements: list[str] = [] + self.params: list[dict[str, object]] = [] + self.commits = 0 + self.rollbacks = 0 + + def execute(self, stmt, params=None): + sql = str(stmt) + self.statements.append(sql) + self.params.append(dict(params or {})) + if "FROM documents" in sql and "LIMIT 1" in sql: + return FakeResult(first_row=self.document_row) + if "COUNT(*) FROM document_pages" in sql: + return FakeResult(scalar_value=self.pages_count) + return FakeResult() + + def commit(self) -> None: + self.commits += 1 + + def rollback(self) -> None: + self.rollbacks += 1 + + @property + def sql(self) -> str: + return "\n".join(self.statements) + + +@pytest.fixture(autouse=True) +def bypass_external_dependencies(monkeypatch): + monkeypatch.setattr(documents, "enforce_workspace_rate_limit", lambda **kwargs: None) + monkeypatch.setattr(documents, "_document_columns", lambda db: TIMING_COLUMNS) + + +def _retryable_error() -> str: + return ingestion_failure_message( + IngestionFailureCategory.TRANSIENT_INFRASTRUCTURE, + "Worker exited before completing ingestion.", + ) + + +def _terminal_error() -> str: + return ingestion_failure_message( + IngestionFailureCategory.PAGE_LIMIT, + "PDF has 28 pages. Maximum supported page count is 10.", + ) + + +def _document_row(*, status: str, error_message: str | None = None) -> dict[str, object]: + return { + "id": uuid.uuid4(), + "status": status, + "storage_path": "workspace/document/file.pdf", + "storage_bucket": "documents", + "error_message": error_message, + "ingestion_run_id": uuid.uuid4(), + } + + +def test_retry_rejects_terminal_document_failures(monkeypatch) -> None: + db = FakeDb(document_row=_document_row(status="failed", error_message=_terminal_error())) + monkeypatch.setattr( + documents, + "_enqueue_extract", + lambda **kwargs: pytest.fail("terminal failures must not enqueue retry jobs"), + ) + + with pytest.raises(HTTPException) as exc_info: + documents.retry_document(uuid.uuid4(), workspace_id=uuid.uuid4(), db=db) + + assert exc_info.value.status_code == 409 + assert "Retry is not allowed" in str(exc_info.value.detail) + assert db.commits == 0 + + +def test_retry_clears_artifacts_resets_timing_and_enqueues_extract(monkeypatch) -> None: + document_id = uuid.uuid4() + workspace_id = uuid.uuid4() + row = _document_row(status="failed", error_message=_retryable_error()) + db = FakeDb(document_row=row) + enqueued: dict[str, object] = {} + + def fake_enqueue_extract(**kwargs): + enqueued.update(kwargs) + return SimpleNamespace(id="extract-job") + + monkeypatch.setattr(documents, "_enqueue_extract", fake_enqueue_extract) + + response = documents.retry_document(document_id, workspace_id=workspace_id, db=db) + + assert response.status == "uploaded" + assert response.job_id == "extract-job" + assert "DELETE FROM chunk_embeddings" in db.sql + assert "DELETE FROM chunks" in db.sql + assert "DELETE FROM document_pages" in db.sql + assert "extract_enqueued_at = :updated_at" in db.sql + assert "extract_started_at = NULL" in db.sql + assert "index_finished_at = NULL" in db.sql + assert enqueued["document_id"] == document_id + assert enqueued["workspace_id"] == workspace_id + assert db.commits == 1 + + +def test_reindex_with_existing_pages_enqueues_index(monkeypatch) -> None: + document_id = uuid.uuid4() + workspace_id = uuid.uuid4() + db = FakeDb(document_row=_document_row(status="indexed"), pages_count=2) + enqueued: dict[str, object] = {} + + def fake_enqueue_index(**kwargs): + enqueued.update(kwargs) + return SimpleNamespace(id="index-job") + + monkeypatch.setattr(documents, "_enqueue_index", fake_enqueue_index) + monkeypatch.setattr( + documents, + "_enqueue_extract", + lambda **kwargs: pytest.fail("documents with existing pages should enqueue index"), + ) + + response = documents.reindex_document(document_id, workspace_id=workspace_id, db=db) + + assert response.status == "indexing" + assert response.job_id == "index-job" + assert "index_enqueued_at = :updated_at" in db.sql + assert "index_started_at = NULL" in db.sql + assert enqueued["document_id"] == document_id + assert enqueued["workspace_id"] == workspace_id + + +def test_reindex_without_pages_enqueues_extract(monkeypatch) -> None: + document_id = uuid.uuid4() + workspace_id = uuid.uuid4() + db = FakeDb(document_row=_document_row(status="ready"), pages_count=0) + enqueued: dict[str, object] = {} + + def fake_enqueue_extract(**kwargs): + enqueued.update(kwargs) + return SimpleNamespace(id="extract-job") + + monkeypatch.setattr(documents, "_enqueue_extract", fake_enqueue_extract) + monkeypatch.setattr( + documents, + "_enqueue_index", + lambda **kwargs: pytest.fail("documents without pages should enqueue extract"), + ) + + response = documents.reindex_document(document_id, workspace_id=workspace_id, db=db) + + assert response.status == "uploaded" + assert response.job_id == "extract-job" + assert "extract_enqueued_at = :updated_at" in db.sql + assert "extract_started_at = NULL" in db.sql + assert "index_enqueued_at = NULL" in db.sql + assert enqueued["document_id"] == document_id + assert enqueued["workspace_id"] == workspace_id + + +def test_failure_summary_separates_expected_rejections_from_infrastructure_failures() -> None: + summary = documents._failure_summary( + [ + ingestion_failure_message( + IngestionFailureCategory.PAGE_LIMIT, + "PDF has too many pages.", + ), + ingestion_failure_message( + IngestionFailureCategory.INDEXING, + "OpenAI request failed.", + ), + "legacy raw exception", + ] + ) + + assert summary.total_failed == 3 + assert summary.expected_rejections == 1 + assert summary.infrastructure_failures == 1 + assert summary.unknown_failures == 1 diff --git a/server/tests/test_api/test_query_multi_document.py b/server/tests/test_api/test_query_multi_document.py new file mode 100644 index 0000000..4649ea7 --- /dev/null +++ b/server/tests/test_api/test_query_multi_document.py @@ -0,0 +1,133 @@ +from __future__ import annotations + +import uuid + +import pytest +from fastapi import HTTPException + +from app.api import query +from app.schemas.query import QueryRequest + + +class FakeResult: + def __init__(self, rows=None) -> None: + self.rows = rows or [] + + def mappings(self): + return self + + def all(self): + return self.rows + + +class FakeDb: + def __init__(self, rows=None) -> None: + self.rows = rows or [] + self.statements: list[str] = [] + self.params: list[dict[str, object]] = [] + self.commits = 0 + + def execute(self, stmt, params=None): + self.statements.append(str(stmt)) + self.params.append(dict(params or {})) + return FakeResult(self.rows) + + def commit(self) -> None: + self.commits += 1 + + @property + def sql(self) -> str: + return "\n".join(self.statements) + + +def test_query_request_accepts_legacy_and_multi_document_selection() -> None: + first_id = uuid.uuid4() + second_id = uuid.uuid4() + + legacy = QueryRequest(document_id=first_id, question="What is covered?") + multi = QueryRequest(document_ids=[first_id, second_id], question="What is covered?") + combined = QueryRequest( + document_id=first_id, + document_ids=[second_id], + question="What is covered?", + ) + + assert legacy.selected_document_ids == [first_id] + assert multi.selected_document_ids == [first_id, second_id] + assert combined.selected_document_ids == [first_id, second_id] + + +def test_resolve_query_document_ids_allows_indexed_documents(monkeypatch) -> None: + workspace_id = uuid.uuid4() + first_id = uuid.uuid4() + second_id = uuid.uuid4() + db = FakeDb( + rows=[ + {"id": first_id, "status": "indexed", "page_count": 3}, + {"id": second_id, "status": "ready", "page_count": 4}, + ] + ) + monkeypatch.setattr(query.settings, "MAX_QUERY_DOCUMENTS", 10) + monkeypatch.setattr(query.settings, "MAX_QUERY_TOTAL_PAGES", 10) + + resolved = query.resolve_query_document_ids( + db=db, + workspace_id=workspace_id, + payload=QueryRequest(document_ids=[first_id, second_id], question="Question?"), + ) + + assert resolved == [first_id, second_id] + assert "id IN (__[POSTCOMPILE_document_ids])" in db.sql + assert db.params[0]["document_ids"] == [first_id, second_id] + + +def test_resolve_query_document_ids_rejects_oversized_page_selection(monkeypatch) -> None: + workspace_id = uuid.uuid4() + first_id = uuid.uuid4() + second_id = uuid.uuid4() + db = FakeDb( + rows=[ + {"id": first_id, "status": "indexed", "page_count": 7}, + {"id": second_id, "status": "ready", "page_count": 8}, + ] + ) + monkeypatch.setattr(query.settings, "MAX_QUERY_DOCUMENTS", 10) + monkeypatch.setattr(query.settings, "MAX_QUERY_TOTAL_PAGES", 10) + + with pytest.raises(HTTPException) as exc_info: + query.resolve_query_document_ids( + db=db, + workspace_id=workspace_id, + payload=QueryRequest(document_ids=[first_id, second_id], question="Question?"), + ) + + assert exc_info.value.status_code == 400 + assert "15 pages" in str(exc_info.value.detail) + + +def test_log_query_records_all_selected_documents(monkeypatch) -> None: + workspace_id = uuid.uuid4() + document_ids = [uuid.uuid4(), uuid.uuid4()] + db = FakeDb() + monkeypatch.setattr(query.settings, "LOG_EACH_QUERY", True) + + query._log_query( + db=db, + workspace_id=workspace_id, + user_id=str(uuid.uuid4()), + document_ids=document_ids, + question="Question?", + retrieved_chunks=[], + answer_text="Answer", + error_message=None, + retrieval_latency_ms=1, + llm_latency_ms=2, + total_latency_ms=3, + embedding_tokens_used=4, + llm_input_tokens=5, + llm_output_tokens=6, + total_tokens_used=10, + ) + + assert db.params[0]["documents_searched"] == document_ids + assert db.commits == 1 diff --git a/server/tests/test_core/test_ingestion_reconciliation.py b/server/tests/test_core/test_ingestion_reconciliation.py new file mode 100644 index 0000000..50d4950 --- /dev/null +++ b/server/tests/test_core/test_ingestion_reconciliation.py @@ -0,0 +1,34 @@ +from app.core.ingestion_policy import ( + IngestionFailureCategory, + failure_category_from_message, + is_retryable_failure, +) +from app.core.ingestion_reconciliation import ( + configured_stale_thresholds, + stale_document_failure_message, +) + + +def test_configured_stale_thresholds_keep_only_active_positive_statuses() -> None: + assert configured_stale_thresholds( + uploaded_seconds=3600, + extracting_seconds=0, + indexing_seconds=1800, + ) == { + "uploaded": 3600, + "indexing": 1800, + } + + +def test_stale_document_failure_message_is_retryable_transient_infrastructure() -> None: + message = stale_document_failure_message( + previous_status="indexing", + threshold_seconds=3600, + ) + + assert ( + failure_category_from_message(message) == IngestionFailureCategory.TRANSIENT_INFRASTRUCTURE + ) + assert is_retryable_failure(message) + assert "indexing" in message + assert "retry" in message diff --git a/server/tests/test_core/test_ingestion_runs.py b/server/tests/test_core/test_ingestion_runs.py index 440477f..a1698d3 100644 --- a/server/tests/test_core/test_ingestion_runs.py +++ b/server/tests/test_core/test_ingestion_runs.py @@ -1,9 +1,25 @@ from app.core.ingestion_runs import ( + DOCUMENT_STATUSES, + PROCESSING_DOCUMENT_STATUSES, derive_ingestion_run_status, empty_document_status_counts, ) +def test_document_status_vocabulary_matches_runtime_states() -> None: + assert set(DOCUMENT_STATUSES) == { + "pending_upload", + "uploading", + "uploaded", + "extracting", + "indexing", + "indexed", + "ready", + "failed", + } + assert PROCESSING_DOCUMENT_STATUSES == {"uploaded", "extracting", "indexing"} + + def test_ingestion_run_status_is_processing_while_documents_are_active() -> None: counts = empty_document_status_counts() counts["uploaded"] = 2 @@ -34,6 +50,20 @@ def test_ingestion_run_status_is_partial_when_success_and_failure_finish() -> No ) +def test_ingestion_run_status_counts_indexed_as_success() -> None: + counts = empty_document_status_counts() + counts["indexed"] = 3 + + assert ( + derive_ingestion_run_status( + status_counts=counts, + accepted_documents=3, + rejected_documents=0, + ) + == "completed" + ) + + def test_ingestion_run_status_handles_all_rejected_prepare() -> None: counts = empty_document_status_counts() diff --git a/server/tests/test_core/test_retrieval_multi_document.py b/server/tests/test_core/test_retrieval_multi_document.py new file mode 100644 index 0000000..6bdacf7 --- /dev/null +++ b/server/tests/test_core/test_retrieval_multi_document.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import uuid + +from app.core.retrieval import retrieve_top_k_chunks_for_documents + + +class FakeResult: + def mappings(self): + return self + + def all(self): + return [] + + +class FakeDb: + def __init__(self) -> None: + self.statements: list[str] = [] + self.params: list[dict[str, object]] = [] + + def execute(self, stmt, params=None): + self.statements.append(str(stmt)) + self.params.append(dict(params or {})) + return FakeResult() + + @property + def sql(self) -> str: + return "\n".join(self.statements) + + +def test_retrieve_top_k_chunks_filters_to_selected_documents() -> None: + workspace_id = uuid.uuid4() + document_ids = [uuid.uuid4(), uuid.uuid4()] + db = FakeDb() + + chunks = retrieve_top_k_chunks_for_documents( + db=db, + workspace_id=workspace_id, + document_ids=document_ids, + query_embedding=[0.1, 0.2, 0.3], + top_k=5, + ) + + assert chunks == [] + assert "ce.document_id IN (__[POSTCOMPILE_document_ids])" in db.sql + assert "c.document_id IN (__[POSTCOMPILE_document_ids])" in db.sql + assert db.params[0]["document_ids"] == document_ids diff --git a/worker/jobs/ingest_callbacks.py b/worker/jobs/ingest_callbacks.py index a9a62b2..e55efe2 100644 --- a/worker/jobs/ingest_callbacks.py +++ b/worker/jobs/ingest_callbacks.py @@ -9,6 +9,7 @@ IngestionFailureCategory, ingestion_failure_message, ) +from app.core.ingestion_runs import refresh_ingestion_run_status from app.db.session import SessionLocal @@ -21,6 +22,20 @@ def _failure_message(detail: str) -> str: )[:2000] +def _document_columns(db) -> set[str]: + rows = db.execute( + text( + """ + SELECT column_name + FROM information_schema.columns + WHERE table_schema = current_schema() + AND table_name = 'documents' + """ + ) + ).scalars() + return {str(column_name) for column_name in rows} + + def mark_ingestion_job_failed(job, connection, exc_type, exc_value, traceback) -> None: meta = job.meta or {} workspace_id = meta.get("workspace_id") @@ -71,27 +86,50 @@ def mark_ingestion_job_failed(job, connection, exc_type, exc_value, traceback) - return # Normal job exceptions already set a structured failure before RQ runs - # callbacks. Only repair rows left in an active state by timeout/kill. - if row["status"] == "failed" and row["error_message"]: - return - - db.execute( - text( - """ - UPDATE documents - SET status = 'failed', - error_message = :error_message, - updated_at = NOW() - WHERE id = :document_id - AND workspace_id = :workspace_id - """ - ), - { - "workspace_id": workspace_uuid, - "document_id": document_uuid, - "error_message": error_message, - }, + # callbacks. Only repair rows left in an active state by timeout/kill, + # but still refresh the parent run for already-terminal documents. + should_update_document = not ( + row["status"] == "failed" and row["error_message"] ) + + if should_update_document: + update_fields = [ + "status = 'failed'", + "error_message = :error_message", + "updated_at = NOW()", + ] + columns = _document_columns(db) + if job.origin == "ingest_extract" and "extract_finished_at" in columns: + update_fields.append("extract_finished_at = NOW()") + if job.origin == "ingest_index" and "index_finished_at" in columns: + update_fields.append("index_finished_at = NOW()") + + db.execute( + text( + f""" + UPDATE documents + SET {", ".join(update_fields)} + WHERE id = :document_id + AND workspace_id = :workspace_id + """ + ), + { + "workspace_id": workspace_uuid, + "document_id": document_uuid, + "error_message": error_message, + }, + ) + if ingestion_run_id: + try: + run_uuid = uuid.UUID(str(ingestion_run_id)) + except ValueError: + run_uuid = None + if run_uuid: + refresh_ingestion_run_status( + db=db, + workspace_id=workspace_uuid, + run_id=run_uuid, + ) db.commit() logger.warning( diff --git a/worker/jobs/ingest_extract.py b/worker/jobs/ingest_extract.py index 772e0c6..3e710de 100644 --- a/worker/jobs/ingest_extract.py +++ b/worker/jobs/ingest_extract.py @@ -2,13 +2,13 @@ import logging from pathlib import Path +import time import uuid from redis import Redis from rq import Queue from rq.job import Callback from sqlalchemy import text -from sqlalchemy.exc import IntegrityError from app.config import settings from app.core.ingestion_policy import ( @@ -17,6 +17,7 @@ format_bytes, ingestion_failure_message, ) +from app.core.ingestion_runs import refresh_ingestion_run_status from app.core.storage import download_object_bytes from app.db.session import SessionLocal @@ -40,15 +41,28 @@ def _set_document_status( document_id: uuid.UUID, status: str, error_message: str | None = None, + set_now: tuple[str, ...] = (), + clear: tuple[str, ...] = (), + ingestion_run_id: str | None = None, ) -> None: with SessionLocal() as db: + columns = _document_columns(db) + update_fields = [ + "status = :status", + "error_message = :error_message", + "updated_at = NOW()", + ] + for column_name in set_now: + if column_name in columns: + update_fields.append(f"{column_name} = NOW()") + for column_name in clear: + if column_name in columns: + update_fields.append(f"{column_name} = NULL") db.execute( text( - """ + f""" UPDATE documents - SET status = :status, - error_message = :error_message, - updated_at = NOW() + SET {", ".join(update_fields)} WHERE id = :document_id AND workspace_id = :workspace_id """ @@ -60,6 +74,9 @@ def _set_document_status( "workspace_id": workspace_id, }, ) + _refresh_ingestion_run( + db=db, workspace_id=workspace_id, ingestion_run_id=ingestion_run_id + ) db.commit() @@ -101,42 +118,56 @@ def _cleanup_ingestion_artifacts( db.commit() -def _document_has_column(db, column_name: str) -> bool: - result = db.execute( +def _document_columns(db) -> set[str]: + rows = db.execute( text( """ - SELECT 1 + SELECT column_name FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'documents' - AND column_name = :column_name """ ), - {"column_name": column_name}, - ).scalar_one_or_none() - return result is not None + ).scalars() + return {str(column_name) for column_name in rows} -def _allowed_document_statuses(db) -> set[str]: - row = db.execute( - text( - """ - SELECT pg_get_constraintdef(c.oid) - FROM pg_constraint c - JOIN pg_class t ON c.conrelid = t.oid - WHERE t.relname = 'documents' - AND c.conname = 'chk_status' - LIMIT 1 - """ +def _refresh_ingestion_run( + *, db, workspace_id: uuid.UUID, ingestion_run_id: str | None +) -> None: + if not ingestion_run_id: + return + try: + run_uuid = uuid.UUID(str(ingestion_run_id)) + except ValueError: + logger.warning( + "Skipping ingestion run refresh for invalid run id", + extra={ + "workspace_id": str(workspace_id), + "ingestion_run_id": ingestion_run_id, + }, ) - ).scalar_one_or_none() - if not row: - return set() - definition = str(row) - if "IN (" not in definition: - return set() - inside = definition.split("IN (", 1)[1].rsplit(")", 1)[0] - return {part.strip().strip("'\"") for part in inside.split(",")} + return + refresh_ingestion_run_status(db=db, workspace_id=workspace_id, run_id=run_uuid) + + +def _mark_index_enqueued(*, workspace_id: uuid.UUID, document_id: uuid.UUID) -> None: + with SessionLocal() as db: + if "index_enqueued_at" not in _document_columns(db): + return + db.execute( + text( + """ + UPDATE documents + SET index_enqueued_at = NOW(), + updated_at = NOW() + WHERE id = :document_id + AND workspace_id = :workspace_id + """ + ), + {"workspace_id": workspace_id, "document_id": document_id}, + ) + db.commit() def ingest_extract( @@ -149,12 +180,27 @@ def ingest_extract( workspace_uuid = uuid.UUID(workspace_id) document_uuid = uuid.UUID(document_id) temp_path = Path(f"/tmp/{document_uuid}.pdf") + started_at = time.perf_counter() - with SessionLocal() as db: - allowed = _allowed_document_statuses(db) - extracting_status = "extracting" if "extracting" in allowed else "indexing" _set_document_status( - workspace_id=workspace_uuid, document_id=document_uuid, status=extracting_status + workspace_id=workspace_uuid, + document_id=document_uuid, + status="extracting", + set_now=("extract_started_at",), + clear=( + "extract_finished_at", + "index_enqueued_at", + "index_started_at", + "index_finished_at", + ), + ) + logger.info( + "ingest_extract started", + extra={ + "workspace_id": workspace_id, + "document_id": document_id, + "ingestion_run_id": ingestion_run_id, + }, ) try: @@ -255,12 +301,15 @@ def ingest_extract( "error_message = NULL", "updated_at = NOW()", ] + columns = _document_columns(db) + if "extract_finished_at" in columns: + update_fields.append("extract_finished_at = NOW()") params: dict[str, object] = { "workspace_id": workspace_uuid, "document_id": document_uuid, "page_count": pages_total, } - if _document_has_column(db, "pages_total"): + if "pages_total" in columns: update_fields.append("pages_total = :pages_total") params["pages_total"] = pages_total @@ -292,6 +341,20 @@ def ingest_extract( "ingestion_run_id": ingestion_run_id, }, ) + _mark_index_enqueued(workspace_id=workspace_uuid, document_id=document_uuid) + + duration_ms = int((time.perf_counter() - started_at) * 1000) + logger.info( + "ingest_extract completed", + extra={ + "workspace_id": workspace_id, + "document_id": document_id, + "ingestion_run_id": ingestion_run_id, + "pages_total": pages_total, + "text_chars": total_text_chars, + "duration_ms": duration_ms, + }, + ) return { "document_id": document_id, @@ -300,6 +363,7 @@ def ingest_extract( "status": "indexing", } except IngestionFailure as exc: + duration_ms = int((time.perf_counter() - started_at) * 1000) logger.warning( "ingest_extract rejected document", extra={ @@ -307,6 +371,7 @@ def ingest_extract( "document_id": document_id, "ingestion_run_id": ingestion_run_id, "failure_category": exc.category.value, + "duration_ms": duration_ms, }, ) _cleanup_ingestion_artifacts( @@ -317,34 +382,19 @@ def ingest_extract( document_id=document_uuid, status="failed", error_message=str(exc)[:2000], - ) - raise - except IntegrityError as exc: - logger.exception( - "ingest_extract status transition failed due to DB constraint", - extra={ - "workspace_id": workspace_id, - "document_id": document_id, - "ingestion_run_id": ingestion_run_id, - }, - ) - _set_document_status( - workspace_id=workspace_uuid, - document_id=document_uuid, - status="failed", - error_message=_failure_message( - IngestionFailureCategory.TRANSIENT_INFRASTRUCTURE, - f"Database status constraint mismatch during extraction: {exc.orig}", - ), + set_now=("extract_finished_at",), + ingestion_run_id=ingestion_run_id, ) raise except Exception as exc: # noqa: BLE001 + duration_ms = int((time.perf_counter() - started_at) * 1000) logger.exception( "ingest_extract failed", extra={ "workspace_id": workspace_id, "document_id": document_id, "ingestion_run_id": ingestion_run_id, + "duration_ms": duration_ms, }, ) _cleanup_ingestion_artifacts( @@ -358,6 +408,8 @@ def ingest_extract( IngestionFailureCategory.EXTRACTION, f"Text extraction failed. Upload a valid text-based PDF and retry. Details: {exc}", ), + set_now=("extract_finished_at",), + ingestion_run_id=ingestion_run_id, ) raise finally: diff --git a/worker/jobs/ingest_index.py b/worker/jobs/ingest_index.py index 2db359d..b489de5 100644 --- a/worker/jobs/ingest_index.py +++ b/worker/jobs/ingest_index.py @@ -3,6 +3,7 @@ import hashlib import logging import math +import time import uuid from openai import OpenAI @@ -14,6 +15,7 @@ IngestionFailureCategory, ingestion_failure_message, ) +from app.core.ingestion_runs import refresh_ingestion_run_status from app.core.token_budget import commit_usage, release_tokens, reserve_tokens from app.db.session import SessionLocal @@ -32,17 +34,58 @@ def _failure_message(category: IngestionFailureCategory, detail: str) -> str: return ingestion_failure_message(category, detail)[:2000] +def _document_columns(db) -> set[str]: + rows = db.execute( + text( + """ + SELECT column_name + FROM information_schema.columns + WHERE table_schema = current_schema() + AND table_name = 'documents' + """ + ) + ).scalars() + return {str(column_name) for column_name in rows} + + +def _refresh_ingestion_run( + *, db, workspace_id: uuid.UUID, ingestion_run_id: str | None +) -> None: + if not ingestion_run_id: + return + try: + run_uuid = uuid.UUID(str(ingestion_run_id)) + except ValueError: + logger.warning( + "Skipping ingestion run refresh for invalid run id", + extra={ + "workspace_id": str(workspace_id), + "ingestion_run_id": ingestion_run_id, + }, + ) + return + refresh_ingestion_run_status(db=db, workspace_id=workspace_id, run_id=run_uuid) + + def _set_document_failed( - workspace_id: uuid.UUID, document_id: uuid.UUID, error_message: str + workspace_id: uuid.UUID, + document_id: uuid.UUID, + error_message: str, + ingestion_run_id: str | None = None, ) -> None: with SessionLocal() as db: + update_fields = [ + "status = 'failed'", + "error_message = :error_message", + "updated_at = NOW()", + ] + if "index_finished_at" in _document_columns(db): + update_fields.append("index_finished_at = NOW()") db.execute( text( - """ + f""" UPDATE documents - SET status = 'failed', - error_message = :error_message, - updated_at = NOW() + SET {", ".join(update_fields)} WHERE id = :document_id AND workspace_id = :workspace_id """ @@ -53,6 +96,44 @@ def _set_document_failed( "error_message": error_message[:2000], }, ) + _refresh_ingestion_run( + db=db, workspace_id=workspace_id, ingestion_run_id=ingestion_run_id + ) + db.commit() + + +def _mark_document_indexed( + *, + workspace_id: uuid.UUID, + document_id: uuid.UUID, + ingestion_run_id: str | None, +) -> None: + with SessionLocal() as db: + update_fields = [ + "status = :final_status", + "error_message = NULL", + "updated_at = NOW()", + ] + if "index_finished_at" in _document_columns(db): + update_fields.append("index_finished_at = NOW()") + db.execute( + text( + f""" + UPDATE documents + SET {", ".join(update_fields)} + WHERE id = :document_id + AND workspace_id = :workspace_id + """ + ), + { + "workspace_id": workspace_id, + "document_id": document_id, + "final_status": "indexed", + }, + ) + _refresh_ingestion_run( + db=db, workspace_id=workspace_id, ingestion_run_id=ingestion_run_id + ) db.commit() @@ -85,28 +166,6 @@ def _estimate_embedding_tokens(text_value: str) -> int: return max(1, int(math.ceil((len(text_value) / 4.0) * 1.1))) -def _allowed_document_statuses(db) -> set[str]: - row = db.execute( - text( - """ - SELECT pg_get_constraintdef(c.oid) - FROM pg_constraint c - JOIN pg_class t ON c.conrelid = t.oid - WHERE t.relname = 'documents' - AND c.conname = 'chk_status' - LIMIT 1 - """ - ) - ).scalar_one_or_none() - if not row: - return set() - definition = str(row) - if "IN (" not in definition: - return set() - inside = definition.split("IN (", 1)[1].rsplit(")", 1)[0] - return {part.strip().strip("'\"") for part in inside.split(",")} - - def _get_encoding(): if tiktoken is None: return None @@ -178,10 +237,18 @@ def ingest_index( document_uuid = uuid.UUID(document_id) usage_date = utc_today() outstanding_reservations: list[int] = [] + started_at = time.perf_counter() + logger.info( + "ingest_index started", + extra={ + "workspace_id": workspace_id, + "document_id": document_id, + "ingestion_run_id": ingestion_run_id, + }, + ) try: with SessionLocal() as db: - allowed_statuses = _allowed_document_statuses(db) document_row = ( db.execute( text( @@ -201,21 +268,28 @@ def ingest_index( if document_row is None: raise ValueError("Document not found for workspace") - accepted_current_statuses = {"indexing", "uploaded"} - if "extracting" in allowed_statuses: - accepted_current_statuses.add("extracting") + accepted_current_statuses = {"extracting", "indexing", "uploaded"} if document_row["status"] not in accepted_current_statuses: raise ValueError( - f"Document status must be indexing or uploaded (got: {document_row['status']})" + f"Document status must be extracting, indexing, or uploaded (got: {document_row['status']})" ) + update_fields = [ + "status = 'indexing'", + "error_message = NULL", + "updated_at = NOW()", + ] + columns = _document_columns(db) + if "index_started_at" in columns: + update_fields.append("index_started_at = NOW()") + if "index_finished_at" in columns: + update_fields.append("index_finished_at = NULL") + db.execute( text( - """ + f""" UPDATE documents - SET status = 'indexing', - error_message = NULL, - updated_at = NOW() + SET {", ".join(update_fields)} WHERE id = :document_id AND workspace_id = :workspace_id """ @@ -293,6 +367,7 @@ def ingest_index( IngestionFailureCategory.UNSUPPORTED_CONTENT, "No usable text chunks were created from the extracted PDF text.", ), + ingestion_run_id, ) return { "document_id": document_id, @@ -331,6 +406,7 @@ def ingest_index( total_embedding_tokens = 0 for batch_rows in _batched(chunk_rows, embedding_batch_size): + batch_started_at = time.perf_counter() estimated_tokens = sum(int(row["token_count"]) for row in batch_rows) with SessionLocal() as db: reserve_tokens( @@ -347,6 +423,9 @@ def ingest_index( model=model, input=[str(row["content"]) for row in batch_rows], ) + embedding_api_latency_ms = int( + (time.perf_counter() - batch_started_at) * 1000 + ) response_data = list(response.data) if len(response_data) != len(batch_rows): raise ValueError( @@ -390,29 +469,38 @@ def ingest_index( embedding_count += len(batch_rows) total_embedding_tokens += estimated_tokens - - with SessionLocal() as db: - final_status = ( - "indexed" if "indexed" in _allowed_document_statuses(db) else "ready" - ) - db.execute( - text( - """ - UPDATE documents - SET status = :final_status, - error_message = NULL, - updated_at = NOW() - WHERE id = :document_id - AND workspace_id = :workspace_id - """ - ), - { - "workspace_id": workspace_uuid, - "document_id": document_uuid, - "final_status": final_status, + logger.info( + "ingest_index embedding batch completed", + extra={ + "workspace_id": workspace_id, + "document_id": document_id, + "ingestion_run_id": ingestion_run_id, + "embedding_batch_size": len(batch_rows), + "embedding_tokens": estimated_tokens, + "embedding_api_latency_ms": embedding_api_latency_ms, }, ) - db.commit() + + final_status = "indexed" + _mark_document_indexed( + workspace_id=workspace_uuid, + document_id=document_uuid, + ingestion_run_id=ingestion_run_id, + ) + + duration_ms = int((time.perf_counter() - started_at) * 1000) + logger.info( + "ingest_index completed", + extra={ + "workspace_id": workspace_id, + "document_id": document_id, + "ingestion_run_id": ingestion_run_id, + "chunks_total": len(chunk_rows), + "embeddings_total": embedding_count, + "embedding_tokens_used": total_embedding_tokens, + "duration_ms": duration_ms, + }, + ) return { "document_id": document_id, @@ -423,6 +511,7 @@ def ingest_index( "embedding_tokens_used": total_embedding_tokens, } except BudgetExceededError: + duration_ms = int((time.perf_counter() - started_at) * 1000) for reserved in reversed(outstanding_reservations): try: with SessionLocal() as db: @@ -449,9 +538,20 @@ def ingest_index( IngestionFailureCategory.BUDGET, "Insufficient token budget for embeddings. Retry after the workspace budget resets.", ), + ingestion_run_id, + ) + logger.warning( + "ingest_index budget exceeded", + extra={ + "workspace_id": workspace_id, + "document_id": document_id, + "ingestion_run_id": ingestion_run_id, + "duration_ms": duration_ms, + }, ) raise except Exception as exc: # noqa: BLE001 + duration_ms = int((time.perf_counter() - started_at) * 1000) for reserved in reversed(outstanding_reservations): try: with SessionLocal() as db: @@ -477,6 +577,7 @@ def ingest_index( "workspace_id": workspace_id, "document_id": document_id, "ingestion_run_id": ingestion_run_id, + "duration_ms": duration_ms, }, ) _cleanup_index_artifacts(workspace_uuid, document_uuid) @@ -487,5 +588,6 @@ def ingest_index( IngestionFailureCategory.INDEXING, f"Indexing failed while creating chunks or embeddings. Details: {exc}", ), + ingestion_run_id, ) raise diff --git a/worker/tests/test_ingestion_run_refresh.py b/worker/tests/test_ingestion_run_refresh.py new file mode 100644 index 0000000..8eaf5f9 --- /dev/null +++ b/worker/tests/test_ingestion_run_refresh.py @@ -0,0 +1,141 @@ +from __future__ import annotations + +from types import SimpleNamespace +import uuid + +from jobs import ingest_callbacks, ingest_index + + +class FakeResult: + def __init__( + self, + *, + first_row: dict[str, object] | None = None, + all_rows: list[dict[str, object]] | None = None, + scalar_values: list[object] | None = None, + ) -> None: + self.first_row = first_row + self.all_rows = all_rows or [] + self.scalar_values = scalar_values or [] + + def mappings(self): + return self + + def first(self): + return self.first_row + + def all(self): + return self.all_rows + + def scalars(self): + return iter(self.scalar_values) + + +class FakeDb: + def __init__( + self, + *, + document_row: dict[str, object] | None = None, + status_count_rows: list[dict[str, object]] | None = None, + columns: set[str] | None = None, + ) -> None: + self.document_row = document_row + self.status_count_rows = status_count_rows or [] + self.columns = columns or set() + self.statements: list[str] = [] + self.params: list[dict[str, object]] = [] + self.commits = 0 + + def execute(self, stmt, params=None): + sql = str(stmt) + self.statements.append(sql) + self.params.append(dict(params or {})) + if "FROM information_schema.columns" in sql: + return FakeResult(scalar_values=list(self.columns)) + if "SELECT status, error_message" in sql: + return FakeResult(first_row=self.document_row) + if "SELECT accepted_documents, rejected_documents" in sql: + return FakeResult( + first_row={"accepted_documents": 1, "rejected_documents": 0} + ) + if "SELECT status, COUNT(*) AS count" in sql: + return FakeResult(all_rows=self.status_count_rows) + return FakeResult() + + def commit(self) -> None: + self.commits += 1 + + @property + def sql(self) -> str: + return "\n".join(self.statements) + + +class FakeSessionLocal: + def __init__(self, db: FakeDb) -> None: + self.db = db + + def __call__(self): + return self + + def __enter__(self) -> FakeDb: + return self.db + + def __exit__(self, exc_type, exc_value, traceback) -> bool: + return False + + +def test_failure_callback_refreshes_run_for_already_failed_document( + monkeypatch, +) -> None: + workspace_id = uuid.uuid4() + document_id = uuid.uuid4() + run_id = uuid.uuid4() + db = FakeDb( + document_row={ + "status": "failed", + "error_message": "existing structured failure", + }, + status_count_rows=[{"status": "failed", "count": 1}], + ) + monkeypatch.setattr(ingest_callbacks, "SessionLocal", FakeSessionLocal(db)) + job = SimpleNamespace( + id="job-1", + origin="ingest_extract", + func_name="jobs.ingest_extract.ingest_extract", + meta={ + "workspace_id": str(workspace_id), + "document_id": str(document_id), + "ingestion_run_id": str(run_id), + }, + ) + + ingest_callbacks.mark_ingestion_job_failed( + job, None, RuntimeError, RuntimeError("boom"), None + ) + + assert "SET status = 'failed'" not in db.sql + assert "UPDATE ingestion_runs" in db.sql + assert any(params.get("status") == "failed" for params in db.params) + assert db.commits == 1 + + +def test_mark_document_indexed_refreshes_parent_run(monkeypatch) -> None: + workspace_id = uuid.uuid4() + document_id = uuid.uuid4() + run_id = uuid.uuid4() + db = FakeDb( + status_count_rows=[{"status": "indexed", "count": 1}], + columns={"index_finished_at"}, + ) + monkeypatch.setattr(ingest_index, "SessionLocal", FakeSessionLocal(db)) + + ingest_index._mark_document_indexed( + workspace_id=workspace_id, + document_id=document_id, + ingestion_run_id=str(run_id), + ) + + assert "index_finished_at = NOW()" in db.sql + assert "UPDATE ingestion_runs" in db.sql + assert any(params.get("status") == "completed" for params in db.params) + assert db.commits == 1