Kndeh commited on
Commit Β·
10f3406
1
Parent(s): 3188836
fix: filtering logics
Browse files- src/models/ai_engine.py +25 -4
- src/pages/1_AI_OCR.py +8 -3
src/models/ai_engine.py
CHANGED
|
@@ -296,9 +296,19 @@ def is_valid_item(nm, price_str, cnt_str=None):
|
|
| 296 |
|
| 297 |
price_raw_clean = str(price_str).strip()
|
| 298 |
if len(re.findall(r'[a-zA-Z]', price_raw_clean)) > 3: return False
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
return False
|
|
|
|
| 302 |
return True
|
| 303 |
|
| 304 |
# ===============================
|
|
@@ -352,9 +362,16 @@ def parse_cord_to_schema(cord_json):
|
|
| 352 |
if isinstance(menu, dict): menu = [menu]
|
| 353 |
|
| 354 |
for item in menu:
|
| 355 |
-
|
|
|
|
|
|
|
| 356 |
for entry in entries:
|
| 357 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
price_raw = entry.get("price", "0")
|
| 359 |
cnt_raw = entry.get("cnt", "1")
|
| 360 |
if not is_valid_item(nm, price_raw, cnt_raw): continue
|
|
@@ -366,6 +383,8 @@ def parse_cord_to_schema(cord_json):
|
|
| 366 |
sub_total_node = cord_json.get("sub_total", {})
|
| 367 |
if isinstance(sub_total_node, list):
|
| 368 |
sub_total_node = sub_total_node[0] if sub_total_node else {}
|
|
|
|
|
|
|
| 369 |
subtotal = clean_price(sub_total_node.get("subtotal_price", "0"))
|
| 370 |
tax_amount = clean_price(sub_total_node.get("tax_price", "0"))
|
| 371 |
service_charge = clean_price(sub_total_node.get("service_price", "0"))
|
|
@@ -374,6 +393,8 @@ def parse_cord_to_schema(cord_json):
|
|
| 374 |
total_node = cord_json.get("total", {})
|
| 375 |
if isinstance(total_node, list):
|
| 376 |
total_node = total_node[0] if total_node else {}
|
|
|
|
|
|
|
| 377 |
total_amount = clean_price(total_node.get("total_price", "0"))
|
| 378 |
|
| 379 |
items_sum = sum(i["item_price"] for i in items)
|
|
|
|
| 296 |
|
| 297 |
price_raw_clean = str(price_str).strip()
|
| 298 |
if len(re.findall(r'[a-zA-Z]', price_raw_clean)) > 3: return False
|
| 299 |
+
|
| 300 |
+
# Reject only obvious transaction codes / serial IDs:
|
| 301 |
+
# must be all-caps, contain digits, AND contain a separator like / or - or be >= 8 digits
|
| 302 |
+
if re.match(r'^[A-Z0-9\s\-:/\.]{5,}$', nm_str) and not re.search(r'[a-z]', nm_str):
|
| 303 |
+
digit_count = len(re.findall(r'\d', nm_str))
|
| 304 |
+
has_separator = bool(re.search(r'[/\-:]', nm_str))
|
| 305 |
+
# Only reject if it looks like a code: has digits AND a separator, OR is >50% digits
|
| 306 |
+
word_count = len(nm_str.split())
|
| 307 |
+
if digit_count > 0 and has_separator:
|
| 308 |
+
return False
|
| 309 |
+
if word_count <= 2 and digit_count > 0 and digit_count >= len(nm_str.replace(' ', '')) * 0.4:
|
| 310 |
return False
|
| 311 |
+
|
| 312 |
return True
|
| 313 |
|
| 314 |
# ===============================
|
|
|
|
| 362 |
if isinstance(menu, dict): menu = [menu]
|
| 363 |
|
| 364 |
for item in menu:
|
| 365 |
+
if not isinstance(item, dict):
|
| 366 |
+
continue
|
| 367 |
+
entries = [item]
|
| 368 |
for entry in entries:
|
| 369 |
+
if not isinstance(entry, dict):
|
| 370 |
+
continue
|
| 371 |
+
nm = entry.get("nm", None)
|
| 372 |
+
# Skip items with no name at all β don't accept "Unknown" fallback
|
| 373 |
+
if not nm or str(nm).strip() in ("", "Unknown"):
|
| 374 |
+
continue
|
| 375 |
price_raw = entry.get("price", "0")
|
| 376 |
cnt_raw = entry.get("cnt", "1")
|
| 377 |
if not is_valid_item(nm, price_raw, cnt_raw): continue
|
|
|
|
| 383 |
sub_total_node = cord_json.get("sub_total", {})
|
| 384 |
if isinstance(sub_total_node, list):
|
| 385 |
sub_total_node = sub_total_node[0] if sub_total_node else {}
|
| 386 |
+
if not isinstance(sub_total_node, dict):
|
| 387 |
+
sub_total_node = {}
|
| 388 |
subtotal = clean_price(sub_total_node.get("subtotal_price", "0"))
|
| 389 |
tax_amount = clean_price(sub_total_node.get("tax_price", "0"))
|
| 390 |
service_charge = clean_price(sub_total_node.get("service_price", "0"))
|
|
|
|
| 393 |
total_node = cord_json.get("total", {})
|
| 394 |
if isinstance(total_node, list):
|
| 395 |
total_node = total_node[0] if total_node else {}
|
| 396 |
+
if not isinstance(total_node, dict):
|
| 397 |
+
total_node = {}
|
| 398 |
total_amount = clean_price(total_node.get("total_price", "0"))
|
| 399 |
|
| 400 |
items_sum = sum(i["item_price"] for i in items)
|
src/pages/1_AI_OCR.py
CHANGED
|
@@ -57,6 +57,7 @@ for k, v in {
|
|
| 57 |
"tess_json": None,
|
| 58 |
"preprocess_steps": [],
|
| 59 |
"img_arr": None,
|
|
|
|
| 60 |
}.items():
|
| 61 |
if k not in st.session_state:
|
| 62 |
st.session_state[k] = v
|
|
@@ -136,7 +137,7 @@ if img_arr is not None:
|
|
| 136 |
st.session_state.preprocess_steps = steps
|
| 137 |
st.session_state.donut_base_parsed = parsed
|
| 138 |
st.session_state.donut_base_json = jout
|
| 139 |
-
st.
|
| 140 |
except Exception as e:
|
| 141 |
st.error(f"Donut base failed: {e}")
|
| 142 |
with b3:
|
|
@@ -147,7 +148,7 @@ if img_arr is not None:
|
|
| 147 |
st.session_state.preprocess_steps = steps
|
| 148 |
st.session_state.donut_v6_parsed = parsed
|
| 149 |
st.session_state.donut_v6_json = jout
|
| 150 |
-
st.
|
| 151 |
except Exception as e:
|
| 152 |
st.error(f"Donut v6 failed: {e}")
|
| 153 |
with b4:
|
|
@@ -158,10 +159,14 @@ if img_arr is not None:
|
|
| 158 |
st.session_state.preprocess_steps = steps
|
| 159 |
st.session_state.tess_parsed = parsed
|
| 160 |
st.session_state.tess_json = jout
|
| 161 |
-
st.
|
| 162 |
except Exception as e:
|
| 163 |
st.error(f"Tesseract failed: {e}")
|
| 164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
if st.session_state.preprocess_steps:
|
| 166 |
st.subheader("Preprocessing Pipeline Visualization")
|
| 167 |
cols = st.columns(min(len(st.session_state.preprocess_steps), 6))
|
|
|
|
| 57 |
"tess_json": None,
|
| 58 |
"preprocess_steps": [],
|
| 59 |
"img_arr": None,
|
| 60 |
+
"last_success_msg": None,
|
| 61 |
}.items():
|
| 62 |
if k not in st.session_state:
|
| 63 |
st.session_state[k] = v
|
|
|
|
| 137 |
st.session_state.preprocess_steps = steps
|
| 138 |
st.session_state.donut_base_parsed = parsed
|
| 139 |
st.session_state.donut_base_json = jout
|
| 140 |
+
st.session_state.last_success_msg = "β
Donut Base extraction complete!"
|
| 141 |
except Exception as e:
|
| 142 |
st.error(f"Donut base failed: {e}")
|
| 143 |
with b3:
|
|
|
|
| 148 |
st.session_state.preprocess_steps = steps
|
| 149 |
st.session_state.donut_v6_parsed = parsed
|
| 150 |
st.session_state.donut_v6_json = jout
|
| 151 |
+
st.session_state.last_success_msg = "β
Donut v6 extraction complete!"
|
| 152 |
except Exception as e:
|
| 153 |
st.error(f"Donut v6 failed: {e}")
|
| 154 |
with b4:
|
|
|
|
| 159 |
st.session_state.preprocess_steps = steps
|
| 160 |
st.session_state.tess_parsed = parsed
|
| 161 |
st.session_state.tess_json = jout
|
| 162 |
+
st.session_state.last_success_msg = "β
Tesseract extraction complete!"
|
| 163 |
except Exception as e:
|
| 164 |
st.error(f"Tesseract failed: {e}")
|
| 165 |
|
| 166 |
+
if st.session_state.get("last_success_msg"):
|
| 167 |
+
st.success(st.session_state.last_success_msg)
|
| 168 |
+
st.session_state.last_success_msg = None
|
| 169 |
+
|
| 170 |
if st.session_state.preprocess_steps:
|
| 171 |
st.subheader("Preprocessing Pipeline Visualization")
|
| 172 |
cols = st.columns(min(len(st.session_state.preprocess_steps), 6))
|