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:
+105
-27
@@ -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:
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
Reference in New Issue
Block a user