{"record":{"id":"643ad94366895687","repo":"odysseus-dev/odysseus","slug":"field-schema-sidecar-missing-for-source-pdf","errorCode":null,"errorMessage":"Field schema sidecar missing for source PDF","messagePattern":"Field schema sidecar missing for source PDF","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"routes/document/document_routes.py","lineNumber":1091,"sourceCode":"        user = get_current_user(request)\n        db = SessionLocal()\n        try:\n            doc = db.query(Document).filter(Document.id == doc_id).first()\n            if not doc:\n                raise HTTPException(404, \"Document not found\")\n            _verify_doc_owner(db, doc, user)\n\n            upload_id = find_source_upload_id(doc.current_content or \"\")\n            if not upload_id:\n                raise HTTPException(400, \"Document is not linked to a source PDF\")\n\n            pdf_path = _locate_current_user_upload(request, upload_id, user)\n            if not pdf_path:\n                raise HTTPException(404, f\"Source PDF {upload_id} not found in uploads\")\n\n            fields = load_field_sidecar(pdf_path)\n            if not fields:\n                raise HTTPException(404, \"Field schema sidecar missing for source PDF\")\n\n            values = parse_markdown_to_values(doc.current_content or \"\")\n            field_meta = {f[\"name\"]: f for f in fields}\n\n            preview = []\n            for name, current in values.items():\n                meta = field_meta.get(name)\n                if not meta:\n                    continue\n                preview.append({\n                    \"name\": name,\n                    \"label\": meta.get(\"label\") or name,\n                    \"type\": meta.get(\"type\"),\n                    \"options\": meta.get(\"options\") or [],\n                    \"page\": meta.get(\"page\"),\n                    \"value\": current,\n                })\n","sourceCodeStart":1073,"sourceCodeEnd":1109,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/document/document_routes.py#L1073-L1109","documentation":"Raised by POST /api/document/{doc_id}/export-pdf/preview with 404 when the source PDF file exists but load_field_sidecar(pdf_path) returns nothing — the JSON sidecar describing the form's field schema (names, labels, rects) was not generated or was lost. Without the sidecar the preview cannot map document values onto PDF fields, so the flow aborts even though the PDF itself is present.","triggerScenarios":"The PDF was uploaded through a path that skips sidecar generation (plain upload vs. form-doc ingestion); the sidecar file was deleted by cleanup while the PDF survived; a corrupted or empty sidecar file fails to parse into a truthy field list.","commonSituations":"Mixed ingestion pipelines where only one writes sidecars; storage cleanup keyed to file extension (e.g. keeping .pdf, removing .json); older uploads predating sidecar support.","solutions":["Check for the sidecar file next to the PDF (same base name, .json) and inspect its contents.","Regenerate the sidecar by re-running the form-field extraction step on the source PDF, or re-upload the PDF through the form-doc flow.","If sidecars are routinely lost, make cleanup preserve both the PDF and its companion .json.","Surface a distinct UI message telling the user to re-import the PDF so its schema is rebuilt."],"exampleFix":"# before\nfields = load_field_sidecar(pdf_path)\nif not fields:\n    raise HTTPException(404, \"Field schema sidecar missing for source PDF\")\n\n# after\nfields = load_field_sidecar(pdf_path)\nif not fields:\n    fields = extract_field_schema(pdf_path)  # rebuild from the PDF itself\n    if fields:\n        save_field_sidecar(pdf_path, fields)\nif not fields:\n    raise HTTPException(404, \"Field schema sidecar missing for source PDF\")","handlingStrategy":"fallback","validationCode":"import json, os\n\ndef sidecar_ok(pdf_path: str) -> bool:\n    side = os.path.splitext(pdf_path)[0] + \".json\"\n    if not os.path.exists(side):\n        return False\n    try:\n        return len(json.load(open(side))) > 0\n    except (json.JSONDecodeError, OSError):\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    p = requests.post(f\"{base}/api/document/{doc}/export-pdf/preview\")\nexcept requests.HTTPError as e:\n    if e.response.status_code == 404 and \"sidecar\" in e.response.json().get(\"detail\", \"\"):\n        offer_reimport(doc)  # rebuild schema by re-importing the PDF\n    else:\n        raise","preventionTips":["Always generate the field sidecar during PDF-form ingestion.","Make cleanup keep the .json companion next to each retained .pdf.","Monitor sidecar presence when uploads are migrated between environments."],"tags":["fastapi","http-404","pdf-export","file-management","schema"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}