298 lines
11 KiB
HTML
298 lines
11 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 %}
|
|
|
|
<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>
|