fix: inherit person data via request_number for individually uploaded pages

- Detect page 2+ from page_info field (e.g. صفحة ٢ من 5) with Arabic digit support
- Find sibling documents by matching request_number, not just pdf_group_id
- Works for both PDF splits AND individually photographed pages
- Inherit all person fields (name, father, family, registry, etc.) from confirmed sibling
- Update review banner to show for all subsequent pages
This commit is contained in:
Georges Haddad
2026-04-11 17:25:25 +03:00
parent 6ba5e7ee79
commit 123492ae90
2 changed files with 126 additions and 48 deletions
+105 -27
View File
@@ -1,5 +1,6 @@
import json import json
import asyncio import asyncio
import re
from fastapi import APIRouter, HTTPException, Request, status from fastapi import APIRouter, HTTPException, Request, status
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
@@ -15,6 +16,25 @@ templates = Jinja2Templates(directory="templates")
REVIEWABLE_DOCUMENT_STATUSES = {"extracted", "confirmed", "error"} REVIEWABLE_DOCUMENT_STATUSES = {"extracted", "confirmed", "error"}
# Map Arabic-Indic digits to Western
_ARABIC_DIGIT_MAP = str.maketrans("٠١٢٣٤٥٦٧٨٩", "0123456789")
def _is_subsequent_page(page_info: str) -> bool:
"""Return True if page_info indicates this is page 2 or later.
Handles formats like: 'صفحة ٢ من 5', '2 / 3', 'صفحة 2 من 3', '٢ من ٣'
"""
if not page_info:
return False
# Normalize Arabic-Indic digits to Western
normalized = page_info.translate(_ARABIC_DIGIT_MAP)
# Find the first number (the page number)
m = re.search(r"(\d+)", normalized)
if m:
page_num = int(m.group(1))
return page_num > 1
return False
def _get_document(doc_id: int) -> dict | None: def _get_document(doc_id: int) -> dict | None:
with get_db() as conn: with get_db() as conn:
@@ -42,33 +62,62 @@ def _get_document(doc_id: int) -> dict | None:
else: else:
doc["person"] = {} doc["person"] = {}
# For multi-page PDFs (page 2+), inherit person info & doc fields from page 1 # For multi-page documents (page 2+), inherit person info & doc fields from page 1.
# Works for both PDF splits (pdf_group_id) and individually uploaded images (via request_number + page_info).
doc["inherited_from_page1"] = False doc["inherited_from_page1"] = False
doc["page1_person_id"] = None
current_first = (doc["person"].get("first_name") or "").strip()
is_subsequent_page = False
sibling_doc = None
# Method 1: PDF group link
if doc.get("pdf_group_id") and (doc.get("page_number") or 0) > 1: if doc.get("pdf_group_id") and (doc.get("page_number") or 0) > 1:
page1 = conn.execute( is_subsequent_page = True
sibling_doc = conn.execute(
"""SELECT * FROM documents """SELECT * FROM documents
WHERE pdf_group_id=? AND page_number=1""", WHERE pdf_group_id=? AND page_number=1""",
(doc["pdf_group_id"],), (doc["pdf_group_id"],),
).fetchone() ).fetchone()
if page1:
page1 = dict(page1) # Method 2: Same request_number + page_info indicates page 2+
# Inherit person data if current page has no name if not sibling_doc and not current_first:
current_first = (doc["person"].get("first_name") or "").strip() page_info_str = doc.get("page_info") or ""
if not current_first: req_num = doc.get("request_number") or ""
if page1.get("person_id"): if req_num and _is_subsequent_page(page_info_str):
is_subsequent_page = True
# Find another document with same request_number that has person data
sibling_doc = conn.execute(
"""SELECT * FROM documents
WHERE request_number=? AND id != ? AND person_id IS NOT NULL
ORDER BY id LIMIT 1""",
(req_num, doc["id"]),
).fetchone()
if not sibling_doc:
# Page 1 may not be confirmed yet; look for one with raw extraction
sibling_doc = conn.execute(
"""SELECT * FROM documents
WHERE request_number=? AND id != ? AND raw_extraction_json IS NOT NULL
ORDER BY id LIMIT 1""",
(req_num, doc["id"]),
).fetchone()
if sibling_doc and not current_first:
sibling_doc = dict(sibling_doc)
if sibling_doc.get("person_id"):
person = conn.execute( person = conn.execute(
"SELECT * FROM persons WHERE id=?", (page1["person_id"],) "SELECT * FROM persons WHERE id=?", (sibling_doc["person_id"],)
).fetchone() ).fetchone()
if person: if person:
doc["person"] = dict(person) doc["person"] = dict(person)
doc["inherited_from_page1"] = True doc["inherited_from_page1"] = True
doc["page1_person_id"] = page1["person_id"] doc["page1_person_id"] = sibling_doc["person_id"]
elif page1.get("raw_extraction_json"): elif sibling_doc.get("raw_extraction_json"):
try: try:
p1_extracted = json.loads(page1["raw_extraction_json"]) sib_extracted = json.loads(sibling_doc["raw_extraction_json"])
p1_person = p1_extracted.get("person", {}) sib_person = sib_extracted.get("person", {})
if (p1_person.get("first_name") or "").strip(): if (sib_person.get("first_name") or "").strip():
doc["person"] = p1_person doc["person"] = sib_person
doc["inherited_from_page1"] = True doc["inherited_from_page1"] = True
except Exception: except Exception:
pass pass
@@ -80,8 +129,10 @@ def _get_document(doc_id: int) -> dict | None:
"applicant_name_raw", "applicant_name_raw",
] ]
for field in inherit_fields: for field in inherit_fields:
if not (doc.get(field) or "").strip() and (page1.get(field) or "").strip(): if not (doc.get(field) or "").strip() and (sibling_doc.get(field) or "").strip():
doc[field] = page1[field] doc[field] = sibling_doc[field]
doc["is_subsequent_page"] = is_subsequent_page
return doc return doc
@@ -294,13 +345,16 @@ async def confirm_document(doc_id: int, request: Request):
current_doc = dict(current_doc) current_doc = dict(current_doc)
# For multi-page PDFs (page 2+), resolve page 1's person and inherit name if needed # For multi-page documents (page 2+), resolve sibling document's person
# Works via pdf_group_id OR request_number + page_info
page1_person_id = None page1_person_id = None
is_subsequent_page = ( is_subsequent = (
current_doc.get("pdf_group_id") current_doc.get("pdf_group_id")
and (current_doc.get("page_number") or 0) > 1 and (current_doc.get("page_number") or 0) > 1
) )
if is_subsequent_page:
# Method 1: PDF group link
if is_subsequent:
page1_doc = conn.execute( page1_doc = conn.execute(
"""SELECT d.person_id, p.first_name, p.father_name, p.family_name, """SELECT d.person_id, p.first_name, p.father_name, p.family_name,
p.registry_number p.registry_number
@@ -311,14 +365,38 @@ async def confirm_document(doc_id: int, request: Request):
).fetchone() ).fetchone()
if page1_doc and page1_doc["person_id"]: if page1_doc and page1_doc["person_id"]:
page1_person_id = page1_doc["person_id"] page1_person_id = page1_doc["person_id"]
# Inherit name from page 1's person if not provided
if not first_name and page1_doc["first_name"]: # Method 2: Same request_number + page_info indicates page 2+
first_name = page1_doc["first_name"] if not page1_person_id and not first_name:
req_num = current_doc.get("request_number") or ""
page_info_val = current_doc.get("page_info") or ""
if req_num and _is_subsequent_page(page_info_val):
is_subsequent = True
sibling = conn.execute(
"""SELECT d.person_id, p.first_name, p.father_name, p.family_name,
p.registry_number
FROM documents d
LEFT JOIN persons p ON p.id = d.person_id
WHERE d.request_number=? AND d.id != ? AND d.person_id IS NOT NULL
ORDER BY d.id LIMIT 1""",
(req_num, doc_id),
).fetchone()
if sibling and sibling["person_id"]:
page1_person_id = sibling["person_id"]
# Inherit person fields from sibling if not provided
if page1_person_id and not first_name:
sibling_person = conn.execute(
"SELECT * FROM persons WHERE id=?", (page1_person_id,)
).fetchone()
if sibling_person:
first_name = sibling_person["first_name"] or ""
person_data["first_name"] = first_name person_data["first_name"] = first_name
# Also inherit other person fields if empty for field in ("father_name", "mother_name", "family_name",
for field in ("father_name", "family_name", "registry_number"): "family_origin", "nationality", "birth_date",
if not (person_data.get(field) or "").strip() and page1_doc[field]: "registry_number", "registry_place"):
person_data[field] = page1_doc[field] if not (person_data.get(field) or "").strip() and sibling_person[field]:
person_data[field] = sibling_person[field]
registry_number = (person_data.get("registry_number") or "").strip() or None registry_number = (person_data.get("registry_number") or "").strip() or None
if not first_name: if not first_name:
+3 -3
View File
@@ -54,14 +54,14 @@
<div class="form-panel"> <div class="form-panel">
<form id="reviewForm"> <form id="reviewForm">
{% if doc.get('pdf_group_id') and (doc.get('page_number') or 0) > 1 %} {% if doc.get('is_subsequent_page') %}
<div class="info-banner page-group-banner"> <div class="info-banner page-group-banner">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
هذه الصفحة {{ doc.page_number }} من مستند متعدد الصفحات. هذه صفحة تكميلية ({{ doc.get('page_info') or doc.get('page_number') or '' }}) من مستند متعدد الصفحات.
{% if doc.get('inherited_from_page1') %} {% if doc.get('inherited_from_page1') %}
بيانات الشخص مستوردة تلقائياً من الصفحة الأولى. بيانات الشخص مستوردة تلقائياً من الصفحة الأولى.
{% else %} {% else %}
يرجى التأكد من بيانات الشخص يدوياً. يرجى التأكد من بيانات الشخص يدوياً — لم يتم العثور على صفحة أولى مؤكدة بعد.
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}