odysseus-dev/odysseus · warning · HTTPException

Document is not linked to a source PDF

Error message

Document is not linked to a source PDF

What it means

Raised by POST /api/document/{doc_id}/export-pdf/preview with status 400 when find_source_upload_id(doc.current_content) returns None — the document's markdown content contains no marker linking it to an uploaded source PDF. Only documents created from a PDF form upload (whose content embeds the upload reference) support the export-preview flow.

Source

Thrown at routes/document/document_routes.py:1083

    async def export_pdf_preview(doc_id: str, request: Request) -> Dict[str, Any]:
        """Return the field-value mapping that would be written to the PDF.

        Frontend shows this in a confirmation modal so the user can spot/fix
        any wrong values before triggering the actual download.
        """
        from src.pdf_form_doc import find_source_upload_id, parse_markdown_to_values, load_field_sidecar

        user = get_current_user(request)
        db = SessionLocal()
        try:
            doc = db.query(Document).filter(Document.id == doc_id).first()
            if not doc:
                raise HTTPException(404, "Document not found")
            _verify_doc_owner(db, doc, user)

            upload_id = find_source_upload_id(doc.current_content or "")
            if not upload_id:
                raise HTTPException(400, "Document is not linked to a source PDF")

            pdf_path = _locate_current_user_upload(request, upload_id, user)
            if not pdf_path:
                raise HTTPException(404, f"Source PDF {upload_id} not found in uploads")

            fields = load_field_sidecar(pdf_path)
            if not fields:
                raise HTTPException(404, "Field schema sidecar missing for source PDF")

            values = parse_markdown_to_values(doc.current_content or "")
            field_meta = {f["name"]: f for f in fields}

            preview = []
            for name, current in values.items():
                meta = field_meta.get(name)
                if not meta:
                    continue
                preview.append({

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Only expose the export-PDF action when the document is PDF-derived (the marker is present).
  2. If the marker was lost to edits, restore the version that still contains the source-upload reference.
  3. Recreate the document via the original PDF upload flow to re-establish the link.
  4. Return the user to a plain markdown/file export for non-PDF documents.

Example fix

// before
<button onClick={() => exportPdf(doc)}>Export PDF</button>

// after
const isPdfDoc = /\[\[?source[_ -]?upload:([\w-]+)\]?\]/i.test(doc.current_content || '');
{isPdfDoc && <button onClick={() => exportPdf(doc)}>Export PDF</button>}
Defensive patterns

Strategy: type-guard

Validate before calling

import re

SOURCE_MARKER = re.compile(r"source[_ -]?upload[:\s]*([\\w-]+)", re.IGNORECASE)

def is_pdf_derived(content: str | None) -> bool:
    return bool(content and SOURCE_MARKER.search(content))

Type guard

def is_pdf_derived(doc: Document | None) -> bool:
    """Type/narrowing guard: only PDF-derived docs support export-preview."""
    return bool(doc is not None and doc.current_content and SOURCE_MARKER.search(doc.current_content))

Try / catch

try:
    p = requests.post(f"{base}/api/document/{doc}/export-pdf/preview")
except requests.HTTPError as e:
    if e.response.status_code == 400:  # not PDF-derived
        offer_markdown_export(doc)
    else:
        raise

Prevention

When it happens

Trigger: Running export preview on a free-typed or AI-generated document that never came from a PDF upload; the linking marker in current_content was edited away or reformatted by a save; the document was restored from an old version that predates the PDF link.

Common situations: UI offers the export-PDF action for all documents instead of only PDF-derived ones; heavy content edits strip the source marker.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/718e8949bd495209. Report an issue: GitHub.