Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -48,12 +48,11 @@ st.markdown("""
|
|
| 48 |
background: #F3F6FB !important;
|
| 49 |
border-radius: 999px;
|
| 50 |
}
|
| 51 |
-
.css-12w0qpk {padding-top: 0rem;}
|
| 52 |
-
.css-1kyxreq {padding-top: 0rem;}
|
| 53 |
</style>
|
| 54 |
""", unsafe_allow_html=True)
|
| 55 |
|
| 56 |
-
# --- Model Definitions ---
|
| 57 |
MODELS = {
|
| 58 |
"OpenAI GPT-4.1": {
|
| 59 |
"api_url": "https://api.openai.com/v1/chat/completions",
|
|
@@ -140,8 +139,72 @@ def get_extraction_prompt(model_choice, txt):
|
|
| 140 |
"Shipment/invoice-level fields such as CAR NUMBER, SHIPPING POINT, SHIPMENT NUMBER, CURRENCY, etc., must go ONLY into the 'invoice_header', not as line item fields.\n"
|
| 141 |
"Use this schema:\n"
|
| 142 |
'{\n'
|
| 143 |
-
' "invoice_header": {
|
| 144 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
'}'
|
| 146 |
"\nIf a field is missing for a line item or header, use null. "
|
| 147 |
"Do not invent fields. Do not add any header or shipment data to any line item. Return ONLY the JSON object, no explanation.\n"
|
|
@@ -150,9 +213,8 @@ def get_extraction_prompt(model_choice, txt):
|
|
| 150 |
)
|
| 151 |
|
| 152 |
def ensure_total_due(invoice_header):
|
| 153 |
-
# Prefer total_before_tax if total_due mismatches in scoring
|
| 154 |
if invoice_header.get("total_due") in [None, ""]:
|
| 155 |
-
for field in ["
|
| 156 |
if field in invoice_header and invoice_header[field]:
|
| 157 |
invoice_header["total_due"] = invoice_header[field]
|
| 158 |
break
|
|
@@ -210,9 +272,7 @@ def find_best_po_match(inv, po_df, weight_supplier, weight_po_number, weight_cur
|
|
| 210 |
inv_supplier = inv_hdr.get("supplier_name") or ""
|
| 211 |
inv_po_number = inv_hdr.get("purchase_order_number") or inv_hdr.get("po_number") or inv_hdr.get("order_number") or ""
|
| 212 |
inv_currency = inv_hdr.get("currency") or ""
|
| 213 |
-
# -- Try both total_due and total_before_tax for matching
|
| 214 |
inv_total_due = clean_num(inv_hdr.get("total_due"))
|
| 215 |
-
inv_total_before_tax = clean_num(inv_hdr.get("total_before_tax"))
|
| 216 |
inv_line_items = inv.get("line_items", [])
|
| 217 |
|
| 218 |
scores = []
|
|
@@ -252,15 +312,10 @@ def find_best_po_match(inv, po_df, weight_supplier, weight_po_number, weight_cur
|
|
| 252 |
"score": s_currency
|
| 253 |
})
|
| 254 |
|
| 255 |
-
|
| 256 |
-
s_total = 0
|
| 257 |
-
if inv_total_due is not None and po_total is not None and abs(inv_total_due - po_total) < 2:
|
| 258 |
-
s_total = 100
|
| 259 |
-
elif inv_total_before_tax is not None and po_total is not None and abs(inv_total_before_tax - po_total) < 2:
|
| 260 |
-
s_total = 100
|
| 261 |
field_details.append({
|
| 262 |
-
"field": "Total Due
|
| 263 |
-
"invoice": inv_total_due
|
| 264 |
"po": po_total,
|
| 265 |
"score": s_total
|
| 266 |
})
|
|
@@ -316,7 +371,7 @@ def find_best_po_match(inv, po_df, weight_supplier, weight_po_number, weight_cur
|
|
| 316 |
f"Supplier match: {s_supplier}/100 (invoice: '{inv_supplier}' vs PO: '{po_supplier}'), "
|
| 317 |
f"PO Number: {s_po_number}/100 ({'found anywhere in JSON' if s_po_number else 'not found'}), "
|
| 318 |
f"Currency: {s_currency}/100 (invoice: '{inv_currency}' vs PO: '{po_currency}'), "
|
| 319 |
-
f"Total: {'match' if s_total else 'no match'} (invoice: {inv_total_due
|
| 320 |
f"Line item best match: {int(line_item_score)}/100. {line_reason}"
|
| 321 |
)
|
| 322 |
|
|
@@ -330,8 +385,7 @@ def find_best_po_match(inv, po_df, weight_supplier, weight_po_number, weight_cur
|
|
| 330 |
"best_line_detail": best_line_detail,
|
| 331 |
"total_score": total_score,
|
| 332 |
"line_reason": line_reason,
|
| 333 |
-
"inv_total_due": inv_total_due
|
| 334 |
-
"inv_total_before_tax": inv_total_before_tax
|
| 335 |
}
|
| 336 |
scores.append((row, total_score, reason, debug))
|
| 337 |
|
|
|
|
| 48 |
background: #F3F6FB !important;
|
| 49 |
border-radius: 999px;
|
| 50 |
}
|
| 51 |
+
.css-12w0qpk {padding-top: 0rem;}
|
| 52 |
+
.css-1kyxreq {padding-top: 0rem;}
|
| 53 |
</style>
|
| 54 |
""", unsafe_allow_html=True)
|
| 55 |
|
|
|
|
| 56 |
MODELS = {
|
| 57 |
"OpenAI GPT-4.1": {
|
| 58 |
"api_url": "https://api.openai.com/v1/chat/completions",
|
|
|
|
| 139 |
"Shipment/invoice-level fields such as CAR NUMBER, SHIPPING POINT, SHIPMENT NUMBER, CURRENCY, etc., must go ONLY into the 'invoice_header', not as line item fields.\n"
|
| 140 |
"Use this schema:\n"
|
| 141 |
'{\n'
|
| 142 |
+
' "invoice_header": {\n'
|
| 143 |
+
' "car_number": "string or null",\n'
|
| 144 |
+
' "shipment_number": "string or null",\n'
|
| 145 |
+
' "shipping_point": "string or null",\n'
|
| 146 |
+
' "currency": "string or null",\n'
|
| 147 |
+
' "invoice_number": "string or null",\n'
|
| 148 |
+
' "invoice_date": "string or null",\n'
|
| 149 |
+
' "order_number": "string or null",\n'
|
| 150 |
+
' "customer_order_number": "string or null",\n'
|
| 151 |
+
' "our_order_number": "string or null",\n'
|
| 152 |
+
' "sales_order_number": "string or null",\n'
|
| 153 |
+
' "purchase_order_number": "string or null",\n'
|
| 154 |
+
' "order_date": "string or null",\n'
|
| 155 |
+
' "supplier_name": "string or null",\n'
|
| 156 |
+
' "supplier_address": "string or null",\n'
|
| 157 |
+
' "supplier_phone": "string or null",\n'
|
| 158 |
+
' "supplier_email": "string or null",\n'
|
| 159 |
+
' "supplier_tax_id": "string or null",\n'
|
| 160 |
+
' "customer_name": "string or null",\n'
|
| 161 |
+
' "customer_address": "string or null",\n'
|
| 162 |
+
' "customer_phone": "string or null",\n'
|
| 163 |
+
' "customer_email": "string or null",\n'
|
| 164 |
+
' "customer_tax_id": "string or null",\n'
|
| 165 |
+
' "ship_to_name": "string or null",\n'
|
| 166 |
+
' "ship_to_address": "string or null",\n'
|
| 167 |
+
' "bill_to_name": "string or null",\n'
|
| 168 |
+
' "bill_to_address": "string or null",\n'
|
| 169 |
+
' "remit_to_name": "string or null",\n'
|
| 170 |
+
' "remit_to_address": "string or null",\n'
|
| 171 |
+
' "tax_id": "string or null",\n'
|
| 172 |
+
' "tax_registration_number": "string or null",\n'
|
| 173 |
+
' "vat_number": "string or null",\n'
|
| 174 |
+
' "payment_terms": "string or null",\n'
|
| 175 |
+
' "payment_method": "string or null",\n'
|
| 176 |
+
' "payment_reference": "string or null",\n'
|
| 177 |
+
' "bank_account_number": "string or null",\n'
|
| 178 |
+
' "iban": "string or null",\n'
|
| 179 |
+
' "swift_code": "string or null",\n'
|
| 180 |
+
' "total_before_tax": "string or null",\n'
|
| 181 |
+
' "tax_amount": "string or null",\n'
|
| 182 |
+
' "tax_rate": "string or null",\n'
|
| 183 |
+
' "shipping_charges": "string or null",\n'
|
| 184 |
+
' "discount": "string or null",\n'
|
| 185 |
+
' "total_due": "string or null",\n'
|
| 186 |
+
' "amount_paid": "string or null",\n'
|
| 187 |
+
' "balance_due": "string or null",\n'
|
| 188 |
+
' "due_date": "string or null",\n'
|
| 189 |
+
' "invoice_status": "string or null",\n'
|
| 190 |
+
' "reference_number": "string or null",\n'
|
| 191 |
+
' "project_code": "string or null",\n'
|
| 192 |
+
' "department": "string or null",\n'
|
| 193 |
+
' "contact_person": "string or null",\n'
|
| 194 |
+
' "notes": "string or null",\n'
|
| 195 |
+
' "additional_info": "string or null"\n'
|
| 196 |
+
' },\n'
|
| 197 |
+
' "line_items": [\n'
|
| 198 |
+
' {\n'
|
| 199 |
+
' "quantity": "string or null",\n'
|
| 200 |
+
' "units": "string or null",\n'
|
| 201 |
+
' "description": "string or null",\n'
|
| 202 |
+
' "footage": "string or null",\n'
|
| 203 |
+
' "price": "string or null",\n'
|
| 204 |
+
' "amount": "string or null",\n'
|
| 205 |
+
' "notes": "string or null"\n'
|
| 206 |
+
' }\n'
|
| 207 |
+
' ]\n'
|
| 208 |
'}'
|
| 209 |
"\nIf a field is missing for a line item or header, use null. "
|
| 210 |
"Do not invent fields. Do not add any header or shipment data to any line item. Return ONLY the JSON object, no explanation.\n"
|
|
|
|
| 213 |
)
|
| 214 |
|
| 215 |
def ensure_total_due(invoice_header):
|
|
|
|
| 216 |
if invoice_header.get("total_due") in [None, ""]:
|
| 217 |
+
for field in ["invoice_total", "invoice_value", "total_before_tax", "balance_due", "amount_paid"]:
|
| 218 |
if field in invoice_header and invoice_header[field]:
|
| 219 |
invoice_header["total_due"] = invoice_header[field]
|
| 220 |
break
|
|
|
|
| 272 |
inv_supplier = inv_hdr.get("supplier_name") or ""
|
| 273 |
inv_po_number = inv_hdr.get("purchase_order_number") or inv_hdr.get("po_number") or inv_hdr.get("order_number") or ""
|
| 274 |
inv_currency = inv_hdr.get("currency") or ""
|
|
|
|
| 275 |
inv_total_due = clean_num(inv_hdr.get("total_due"))
|
|
|
|
| 276 |
inv_line_items = inv.get("line_items", [])
|
| 277 |
|
| 278 |
scores = []
|
|
|
|
| 312 |
"score": s_currency
|
| 313 |
})
|
| 314 |
|
| 315 |
+
s_total = 100 if inv_total_due is not None and po_total is not None and abs(inv_total_due - po_total) < 2 else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
field_details.append({
|
| 317 |
+
"field": "Total Due",
|
| 318 |
+
"invoice": inv_total_due,
|
| 319 |
"po": po_total,
|
| 320 |
"score": s_total
|
| 321 |
})
|
|
|
|
| 371 |
f"Supplier match: {s_supplier}/100 (invoice: '{inv_supplier}' vs PO: '{po_supplier}'), "
|
| 372 |
f"PO Number: {s_po_number}/100 ({'found anywhere in JSON' if s_po_number else 'not found'}), "
|
| 373 |
f"Currency: {s_currency}/100 (invoice: '{inv_currency}' vs PO: '{po_currency}'), "
|
| 374 |
+
f"Total Due: {'match' if s_total else 'no match'} (invoice: {inv_total_due} vs PO: {po_total}), "
|
| 375 |
f"Line item best match: {int(line_item_score)}/100. {line_reason}"
|
| 376 |
)
|
| 377 |
|
|
|
|
| 385 |
"best_line_detail": best_line_detail,
|
| 386 |
"total_score": total_score,
|
| 387 |
"line_reason": line_reason,
|
| 388 |
+
"inv_total_due": inv_total_due
|
|
|
|
| 389 |
}
|
| 390 |
scores.append((row, total_score, reason, debug))
|
| 391 |
|