Project

General

Profile

Feature #92 » api_courier_added.py

api file used for wizard - Rajesh Gotur, 08/20/2026 11:00 AM

 
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")
(1-1/2)