document-processor/app/templates/documents/detail.html

308 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ document.document_id }}</title>
<style>
body { font-family: sans-serif; }
textarea { font-family: monospace; }
.editor-wrap {
display: flex;
align-items: flex-start;
gap: 0.5rem;
}
.line-numbers {
font-family: monospace;
white-space: pre;
text-align: right;
color: #666;
user-select: none;
padding-top: 2px;
min-width: 3rem;
}
.line-warning {
color: #8a5a00;
font-weight: 600;
}
.error-box {
background: #ffe8e8;
color: #8b0000;
padding: 0.75rem;
border: 1px solid #cc9999;
margin-bottom: 1rem;
}
</style>
</head>
<body>
<p><a href="/documents/">Back to documents</a></p>
<h1>{{ document.document_id }}</h1>
{% if error == "line_count_mismatch" %}
<div class="error-box">
Could not save reviewed OCR because line count did not match OCR layout.
Expected {{ error_expected }}, got {{ error_actual }}.
</div>
{% elif error == "save_ocr_corrected_failed" %}
<div class="error-box">
Could not save OCR-corrected PDF. Check that reviewed OCR line count matches raw OCR line count.
</div>
{% elif error == "rerun_ocr_failed" %}
<div class="error-box">
OCR rerun failed.
</div>
{% elif error == "save_field_enriched_failed" %}
<div class="error-box">
Could not save field-enriched PDF.
</div>
{% endif %}
<p>
<a href="/queue/">Open review queue</a> |
<a href="/trash/">Open trash</a>
</p>
<form method="post" action="/documents/{{ document.document_id }}/move-to-trash" style="margin-bottom: 1rem;">
<button type="submit">Move to trash</button>
</form>
<h2>Document metadata</h2>
<ul>
<li>Type: {{ document.document_type }}</li>
<li>Source path: {{ document.source_path }}</li>
<li>Current path: {{ document.current_path }}</li>
<li>Share path: {{ document.share_path or "" }}</li>
<li>App URL: <a href="{{ app_url }}">{{ app_url }}</a></li>
<li>Original filename: {{ document.original_filename }}</li>
<li>Canonical filename: {{ document.canonical_filename }}</li>
<li>MIME type: {{ document.mime_type }}</li>
<li>File size: {{ document.file_size }}</li>
<li>Page count: {{ document.page_count }}</li>
<li>Storage status: {{ document.storage_status }}</li>
<li>Review status: {{ document.review_status }}</li>
<li>Created at: {{ document.created_at }}</li>
<li>Updated at: {{ document.updated_at }}</li>
</ul>
<h2>Saved PDF scaffolds</h2>
<form method="post" action="/documents/{{ document.document_id }}/save-ocr-corrected-pdf" style="display:inline;">
<button type="submit">Save OCR-corrected PDF</button>
</form>
<form method="post" action="/documents/{{ document.document_id }}/save-field-enriched-pdf" style="display:inline; margin-left: 1rem;">
<button type="submit">Save field-enriched PDF</button>
</form>
<h2>Document preview</h2>
{% if file_url %}
{% if document.mime_type == "application/pdf" %}
<iframe src="{{ file_url }}" width="900" height="700"></iframe>
{% elif document.mime_type in ["image/jpeg", "image/png"] %}
<img src="{{ file_url }}" alt="Document image" style="max-width: 900px; max-height: 700px;">
{% else %}
<p><a href="{{ file_url }}" target="_blank">Open file</a></p>
{% endif %}
{% else %}
<p>No preview available.</p>
{% endif %}
<h2>Document versions</h2>
{% if document.versions %}
<ul>
{% for version in document.versions %}
<li>
v{{ version.version_number }} —
{{ version.version_type }} —
{{ version.file_path }} —
{{ version.created_at }}
{% if version.notes %}<br><em>{{ version.notes }}</em>{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>No versions found.</p>
{% endif %}
<h2>Raw OCR</h2>
<form method="post" action="/documents/{{ document.document_id }}/rerun-ocr">
<button type="submit">Re-run OCR</button>
</form>
{% if raw_ocr %}
<p>
<strong>Text version:</strong> v{{ raw_ocr.version_number }}<br>
<strong>OCR engine:</strong> {{ raw_ocr.ocr_engine or "unknown" }}<br>
<strong>OCR engine version:</strong> {{ raw_ocr.ocr_engine_version or "unknown" }}<br>
<strong>Rerun source:</strong> {{ raw_ocr.rerun_source or "unknown" }}<br>
<strong>Quality score:</strong> {{ raw_ocr.quality_score if raw_ocr.quality_score is not none else "not scored yet" }}<br>
<strong>Quality flags:</strong> {{ raw_ocr.quality_flags if raw_ocr.quality_flags else [] }}<br>
<strong>Quality note:</strong> {{ raw_ocr.quality_note or "" }}
</p>
<pre>{{ raw_ocr.text_content }}</pre>
{% else %}
<p>No raw OCR text found.</p>
{% endif %}
<h2>Reviewed OCR</h2>
{% if reviewed_ocr %}
<p>
Current reviewed version saved at {{ reviewed_ocr.created_at }} —
v{{ reviewed_ocr.version_number }}
</p>
{% else %}
<p>No reviewed OCR saved yet.</p>
{% endif %}
<p>
Expected OCR lines: <span id="expected-lines">{{ expected_line_count }}</span><br>
Current editor lines: <span id="actual-lines">{{ actual_line_count }}</span>
<br><span id="line-warning" class="line-warning" {% if expected_line_count == actual_line_count %}style="display:none;"{% endif %}>
Line count mismatch may affect corrected PDF layout.
</span>
</p>
<form method="post" action="/documents/{{ document.document_id }}/review-text">
<div>
<label for="reviewed_text">Edit reviewed OCR text (one line per OCR line):</label>
</div>
<div class="editor-wrap">
<div class="line-numbers" id="line-numbers">{% for n in line_numbers %}{{ n }}
{% endfor %}</div>
<textarea id="reviewed_text" name="reviewed_text" rows="{{ [actual_line_count + 2, 20]|max }}" cols="100">{{ review_text_value }}</textarea>
</div>
<h3>Quality flags</h3>
<div>
{% for flag in quality_flag_options %}
<label style="display:block;">
<input
type="checkbox"
name="quality_flags"
value="{{ flag }}"
{% if flag in current_quality_flags %}checked{% endif %}
>
{{ flag }}
</label>
{% endfor %}
</div>
<h3>Quality note</h3>
<div>
<textarea id="quality_note" name="quality_note" rows="4" cols="100">{{ current_quality_note }}</textarea>
</div>
<div style="margin-top: 1rem;">
<button type="submit" id="save-reviewed-btn">Save reviewed OCR</button>
</div>
</form>
<script>
const textarea = document.getElementById("reviewed_text");
const expectedLines = parseInt(document.getElementById("expected-lines").textContent || "0", 10);
const actualLinesEl = document.getElementById("actual-lines");
const warningEl = document.getElementById("line-warning");
const saveBtn = document.getElementById("save-reviewed-btn");
const lineNumbersEl = document.getElementById("line-numbers");
function countLines(text) {
if (text.length === 0) return 0;
return text.split('\n').length;
}
function rebuildLineNumbers(lineCount) {
let nums = "";
for (let i = 1; i <= lineCount; i++) {
nums += i + "\n";
}
lineNumbersEl.textContent = nums;
}
function updateEditorState() {
const actual = countLines(textarea.value);
actualLinesEl.textContent = actual.toString();
rebuildLineNumbers(Math.max(actual, expectedLines));
const mismatch = expectedLines > 0 && actual !== expectedLines;
warningEl.style.display = mismatch ? "inline" : "none";
saveBtn.disabled = mismatch;
}
textarea.addEventListener("input", updateEditorState);
updateEditorState();
</script>
<h2>Extracted fields</h2>
{% if current_extracted %}
<p>Current extracted fields last updated at {{ current_extracted.updated_at }}</p>
{% else %}
<p>No extracted fields saved yet.</p>
{% endif %}
<form method="get" action="/documents/{{ document.document_id }}">
<input type="hidden" name="autofill_extracted" value="1">
<button type="submit">Auto-extract fields</button>
</form>
<form method="post" action="/documents/{{ document.document_id }}/save-extracted-fields" style="margin-top: 1rem;">
<div>
<label>Merchant raw:</label><br>
<input type="text" name="merchant_raw" value="{{ extracted_form.merchant_raw }}" size="80">
</div>
<div>
<label>Merchant normalized:</label><br>
<input type="text" name="merchant_normalized" value="{{ extracted_form.merchant_normalized }}" size="80">
</div>
<div>
<label>Transaction date:</label><br>
<input type="date" name="transaction_date" value="{{ extracted_form.transaction_date }}">
</div>
<div>
<label>Transaction time:</label><br>
<input type="text" name="transaction_time" value="{{ extracted_form.transaction_time }}" size="20">
</div>
<div>
<label>Subtotal:</label><br>
<input type="text" name="subtotal" value="{{ extracted_form.subtotal }}" size="20">
</div>
<div>
<label>Tax:</label><br>
<input type="text" name="tax" value="{{ extracted_form.tax }}" size="20">
</div>
<div>
<label>Total:</label><br>
<input type="text" name="total" value="{{ extracted_form.total }}" size="20">
</div>
<div>
<label>Currency:</label><br>
<input type="text" name="currency" value="{{ extracted_form.currency }}" size="10">
</div>
<div>
<label>Payment method:</label><br>
<input type="text" name="payment_method" value="{{ extracted_form.payment_method }}" size="40">
</div>
<div>
<label>Receipt number:</label><br>
<input type="text" name="receipt_number" value="{{ extracted_form.receipt_number }}" size="40">
</div>
<div>
<label>Location:</label><br>
<input type="text" name="location" value="{{ extracted_form.location }}" size="80">
</div>
<div>
<label>Counterparty:</label><br>
<input type="text" name="counterparty" value="{{ extracted_form.counterparty }}" size="80">
</div>
<div>
<label>Extra JSON:</label><br>
<textarea name="extra_json" rows="8" cols="100">{{ extracted_form.extra_json }}</textarea>
</div>
<div style="margin-top: 1rem;">
<button type="submit">Save extracted fields</button>
</div>
</form>
</body>
</html>