|
import os
|
|
import time
|
|
|
|
import frappe
|
|
|
|
from shipping_bill_extractor.services.pdf_analyzer import PDFAnalyzer
|
|
from shipping_bill_extractor.services.shipping_bill_parser import ShippingBillParser
|
|
|
|
|
|
@frappe.whitelist()
|
|
def analyze_shipping_bill(file_url):
|
|
"""
|
|
Analyze an uploaded Shipping Bill PDF and return structured JSON.
|
|
|
|
Flow:
|
|
Uploaded Frappe File
|
|
-> PDFAnalyzer
|
|
-> pdfplumber text/words/tables
|
|
-> ShippingBillParser
|
|
-> shipping_bill_json
|
|
|
|
No AI is used.
|
|
"""
|
|
|
|
start_time = time.perf_counter()
|
|
|
|
# ---------------------------------------------------------
|
|
# 1. VALIDATE REQUEST
|
|
# ---------------------------------------------------------
|
|
|
|
if not file_url:
|
|
frappe.throw("PDF file URL is required.")
|
|
|
|
if not isinstance(file_url, str):
|
|
frappe.throw("Invalid PDF file URL.")
|
|
|
|
file_url = file_url.strip()
|
|
|
|
if not file_url:
|
|
frappe.throw("PDF file URL is required.")
|
|
|
|
if not file_url.lower().endswith(".pdf"):
|
|
frappe.throw("Only PDF files are supported.")
|
|
|
|
# ---------------------------------------------------------
|
|
# 2. FIND FRAPPE FILE
|
|
# ---------------------------------------------------------
|
|
|
|
try:
|
|
file_doc = frappe.get_doc(
|
|
"File",
|
|
{"file_url": file_url},
|
|
)
|
|
except Exception:
|
|
frappe.log_error(
|
|
frappe.get_traceback(),
|
|
"Shipping Bill File Lookup Error",
|
|
)
|
|
frappe.throw("Unable to find the uploaded PDF file.")
|
|
|
|
if not file_doc:
|
|
frappe.throw("Uploaded PDF file does not exist.")
|
|
|
|
file_path = file_doc.get_full_path()
|
|
|
|
if not file_path:
|
|
frappe.throw("Unable to determine the uploaded PDF path.")
|
|
|
|
if not os.path.exists(file_path):
|
|
frappe.throw("Uploaded PDF file was not found on the server.")
|
|
|
|
if not os.path.isfile(file_path):
|
|
frappe.throw("Uploaded PDF path is not a valid file.")
|
|
|
|
if not file_path.lower().endswith(".pdf"):
|
|
frappe.throw("Only PDF files are supported.")
|
|
|
|
# ---------------------------------------------------------
|
|
# 3. ANALYZE PDF
|
|
# ---------------------------------------------------------
|
|
|
|
try:
|
|
analyzer = PDFAnalyzer(file_path)
|
|
analysis = analyzer.analyze()
|
|
|
|
except Exception as exc:
|
|
frappe.log_error(
|
|
frappe.get_traceback(),
|
|
"Shipping Bill PDF Analysis Error",
|
|
)
|
|
|
|
frappe.throw(f"Unable to analyze the uploaded PDF: {str(exc)}")
|
|
|
|
# ---------------------------------------------------------
|
|
# 4. VALIDATE ANALYZER RESULT
|
|
# ---------------------------------------------------------
|
|
|
|
if not isinstance(analysis, dict):
|
|
frappe.throw("PDF analyzer returned an invalid response.")
|
|
|
|
pages = analysis.get("pages") or []
|
|
|
|
if not pages:
|
|
frappe.throw("No readable pages were found in the uploaded PDF.")
|
|
|
|
total_pages = analysis.get(
|
|
"total_pages",
|
|
len(pages),
|
|
)
|
|
|
|
text_pages = analysis.get(
|
|
"text_pages",
|
|
0,
|
|
)
|
|
|
|
image_pages = analysis.get(
|
|
"image_pages",
|
|
0,
|
|
)
|
|
|
|
# ---------------------------------------------------------
|
|
# 5. PARSE SHIPPING BILL
|
|
# ---------------------------------------------------------
|
|
|
|
try:
|
|
parser = ShippingBillParser(pages)
|
|
parsed = parser.parse()
|
|
|
|
except Exception as exc:
|
|
frappe.log_error(
|
|
frappe.get_traceback(),
|
|
"Shipping Bill Parser Error",
|
|
)
|
|
|
|
frappe.throw(f"Unable to extract Shipping Bill data from the PDF: {str(exc)}")
|
|
|
|
# ---------------------------------------------------------
|
|
# 6. VALIDATE PARSER RESULT
|
|
# ---------------------------------------------------------
|
|
|
|
if not isinstance(parsed, dict):
|
|
frappe.throw("Shipping Bill parser returned an invalid response.")
|
|
|
|
shipping_bill_json = parsed.get("shipping_bill_json")
|
|
|
|
if not isinstance(shipping_bill_json, dict):
|
|
frappe.throw("Shipping Bill parser did not return valid JSON data.")
|
|
|
|
trace = parsed.get(
|
|
"_trace",
|
|
[],
|
|
)
|
|
|
|
if not isinstance(trace, list):
|
|
trace = []
|
|
|
|
# ---------------------------------------------------------
|
|
# 7. BUILD LIGHTWEIGHT API RESPONSE
|
|
# ---------------------------------------------------------
|
|
|
|
processing_time = round(
|
|
time.perf_counter() - start_time,
|
|
2,
|
|
)
|
|
|
|
return {
|
|
"shipping_bill_json": shipping_bill_json,
|
|
"file_id": file_doc.name,
|
|
"file_name": file_doc.file_name,
|
|
"file_url": file_doc.file_url,
|
|
"processing_time": processing_time,
|
|
"pdf_analysis": {
|
|
"total_pages": total_pages,
|
|
"pdf_type": analysis.get(
|
|
"pdf_type",
|
|
"",
|
|
),
|
|
"text_pages": text_pages,
|
|
"image_pages": image_pages,
|
|
"total_words": analysis.get(
|
|
"total_words",
|
|
0,
|
|
),
|
|
"total_tables": analysis.get(
|
|
"total_tables",
|
|
0,
|
|
),
|
|
},
|
|
"trace": trace,
|
|
}
|
|
|
|
|
|
# -------------------------------------------------------------
|
|
# COURIER BILL API
|
|
# -------------------------------------------------------------
|
|
|
|
@frappe.whitelist()
|
|
def analyze_courier_bill(file_url):
|
|
"""
|
|
Analyze an uploaded Courier Bill PDF and return structured JSON.
|
|
|
|
Flow:
|
|
Uploaded Frappe File
|
|
-> PDFAnalyzer
|
|
-> pdfplumber text/words/tables
|
|
-> CourierBillParser
|
|
-> courier_bill_json
|
|
|
|
No AI is used.
|
|
|
|
This function is added independently so the existing
|
|
Shipping Bill API remains unchanged.
|
|
"""
|
|
|
|
start_time = time.perf_counter()
|
|
|
|
# ---------------------------------------------------------
|
|
# 1. VALIDATE REQUEST
|
|
# ---------------------------------------------------------
|
|
|
|
if not file_url:
|
|
frappe.throw("PDF file URL is required.")
|
|
|
|
if not isinstance(file_url, str):
|
|
frappe.throw("Invalid PDF file URL.")
|
|
|
|
file_url = file_url.strip()
|
|
|
|
if not file_url:
|
|
frappe.throw("PDF file URL is required.")
|
|
|
|
if not file_url.lower().endswith(".pdf"):
|
|
frappe.throw("Only PDF files are supported.")
|
|
|
|
# ---------------------------------------------------------
|
|
# 2. FIND FRAPPE FILE
|
|
# ---------------------------------------------------------
|
|
|
|
try:
|
|
file_doc = frappe.get_doc(
|
|
"File",
|
|
{"file_url": file_url},
|
|
)
|
|
except Exception:
|
|
frappe.log_error(
|
|
frappe.get_traceback(),
|
|
"Courier Bill File Lookup Error",
|
|
)
|
|
frappe.throw("Unable to find the uploaded PDF file.")
|
|
|
|
if not file_doc:
|
|
frappe.throw("Uploaded PDF file does not exist.")
|
|
|
|
file_path = file_doc.get_full_path()
|
|
|
|
if not file_path:
|
|
frappe.throw("Unable to determine the uploaded PDF path.")
|
|
|
|
if not os.path.exists(file_path):
|
|
frappe.throw("Uploaded PDF file was not found on the server.")
|
|
|
|
if not os.path.isfile(file_path):
|
|
frappe.throw("Uploaded PDF path is not a valid file.")
|
|
|
|
if not file_path.lower().endswith(".pdf"):
|
|
frappe.throw("Only PDF files are supported.")
|
|
|
|
# ---------------------------------------------------------
|
|
# 3. ANALYZE PDF
|
|
# ---------------------------------------------------------
|
|
|
|
try:
|
|
analyzer = PDFAnalyzer(file_path)
|
|
analysis = analyzer.analyze()
|
|
|
|
except Exception as exc:
|
|
frappe.log_error(
|
|
frappe.get_traceback(),
|
|
"Courier Bill PDF Analysis Error",
|
|
)
|
|
|
|
frappe.throw(
|
|
f"Unable to analyze the uploaded PDF: {str(exc)}"
|
|
)
|
|
|
|
# ---------------------------------------------------------
|
|
# 4. VALIDATE ANALYZER RESULT
|
|
# ---------------------------------------------------------
|
|
|
|
if not isinstance(analysis, dict):
|
|
frappe.throw("PDF analyzer returned an invalid response.")
|
|
|
|
pages = analysis.get("pages") or []
|
|
|
|
if not pages:
|
|
frappe.throw("No readable pages were found in the uploaded PDF.")
|
|
|
|
total_pages = analysis.get(
|
|
"total_pages",
|
|
len(pages),
|
|
)
|
|
|
|
text_pages = analysis.get(
|
|
"text_pages",
|
|
0,
|
|
)
|
|
|
|
image_pages = analysis.get(
|
|
"image_pages",
|
|
0,
|
|
)
|
|
|
|
# ---------------------------------------------------------
|
|
# 5. PARSE COURIER BILL
|
|
# ---------------------------------------------------------
|
|
|
|
try:
|
|
# Import here so the existing Shipping Bill API and its
|
|
# current imports/code remain unchanged.
|
|
from shipping_bill_extractor.services.courier_bill_parser import (
|
|
CourierBillParser,
|
|
)
|
|
|
|
parser = CourierBillParser(pages)
|
|
parsed = parser.parse()
|
|
|
|
except Exception as exc:
|
|
frappe.log_error(
|
|
frappe.get_traceback(),
|
|
"Courier Bill Parser Error",
|
|
)
|
|
|
|
frappe.throw(
|
|
f"Unable to extract Courier Bill data from the PDF: {str(exc)}"
|
|
)
|
|
|
|
# ---------------------------------------------------------
|
|
# 6. VALIDATE PARSER RESULT
|
|
# ---------------------------------------------------------
|
|
|
|
if not isinstance(parsed, dict):
|
|
frappe.throw("Courier Bill parser returned an invalid response.")
|
|
|
|
courier_bill_json = parsed.get(
|
|
"courier_bill_json"
|
|
)
|
|
|
|
if not isinstance(courier_bill_json, dict):
|
|
frappe.throw(
|
|
"Courier Bill parser did not return valid JSON data."
|
|
)
|
|
|
|
trace = parsed.get(
|
|
"_trace",
|
|
[],
|
|
)
|
|
|
|
if not isinstance(trace, list):
|
|
trace = []
|
|
|
|
# ---------------------------------------------------------
|
|
# 7. BUILD LIGHTWEIGHT API RESPONSE
|
|
# ---------------------------------------------------------
|
|
|
|
processing_time = round(
|
|
time.perf_counter() - start_time,
|
|
2,
|
|
)
|
|
|
|
return {
|
|
"courier_bill_json": courier_bill_json,
|
|
"file_id": file_doc.name,
|
|
"file_name": file_doc.file_name,
|
|
"file_url": file_doc.file_url,
|
|
"processing_time": processing_time,
|
|
"pdf_analysis": {
|
|
"total_pages": total_pages,
|
|
"pdf_type": analysis.get(
|
|
"pdf_type",
|
|
"",
|
|
),
|
|
"text_pages": text_pages,
|
|
"image_pages": image_pages,
|
|
"total_words": analysis.get(
|
|
"total_words",
|
|
0,
|
|
),
|
|
"total_tables": analysis.get(
|
|
"total_tables",
|
|
0,
|
|
),
|
|
},
|
|
"trace": trace,
|
|
}
|
|
|
|
|
|
# -------------------------------------------------------------
|
|
# OPTIONAL DIRECT TEST
|
|
# -------------------------------------------------------------
|
|
|
|
if __name__ == "__main__":
|
|
print("Shipping Bill API loaded successfully")
|