dschandra commited on
Commit
f77bc9a
·
verified ·
1 Parent(s): 02fa462

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +171 -0
app.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pdfplumber
2
+ import pandas as pd
3
+ import re
4
+ import gradio as gr
5
+
6
+ # Function: Extract Text from PDF
7
+ def extract_text_from_pdf(pdf_file):
8
+ with pdfplumber.open(pdf_file.name) as pdf:
9
+ text = ""
10
+ for page in pdf.pages:
11
+ text += page.extract_text()
12
+ return text
13
+
14
+ # Function: Clean Description
15
+ def clean_description(description, item_number=None):
16
+ """
17
+ Cleans the description by removing unwanted data such as Qty, Unit, Unit Price, Total Price, and other invalid entries.
18
+ Args:
19
+ description (str): Raw description string.
20
+ item_number (int, optional): The item number being processed to handle item-specific cleaning.
21
+ Returns:
22
+ str: Cleaned description.
23
+ """
24
+ # Remove common unwanted patterns
25
+ description = re.sub(r"\d+\s+(Nos\.|Set)\s+[\d.]+\s+[\d.]+", "", description) # Remove Qty + Unit + Price
26
+ description = re.sub(r"Page \d+ of \d+.*", "", description) # Remove page references
27
+ description = re.sub(r"\(Q\. No:.*?\)", "", description) # Remove Q.No-related data
28
+ description = re.sub(r"TOTAL EX-WORK.*", "", description) # Remove EX-WORK-related text
29
+ description = re.sub(r"NOTES:.*", "", description) # Remove notes section
30
+ description = re.sub(r"HS CODE.*", "", description) # Remove HS CODE-related data
31
+ description = re.sub(r"DELIVERY:.*", "", description) # Remove delivery instructions
32
+
33
+ # Specific removal for item 7
34
+ if item_number == 7:
35
+ description = re.sub(r"\b300 Sets 4.20 1260.00\b", "", description)
36
+
37
+ return description.strip()
38
+
39
+ # Function: Parse PO Items with Filters
40
+ def parse_po_items_with_filters(text):
41
+ """
42
+ Parses purchase order items from the extracted text using regex with filters.
43
+ Ensures items are not merged and handles split descriptions across lines.
44
+ Args:
45
+ text (str): Extracted text from the PDF.
46
+ Returns:
47
+ tuple: A DataFrame with parsed data and a status message.
48
+ """
49
+ lines = text.splitlines()
50
+ data = []
51
+ current_item = {}
52
+ description_accumulator = []
53
+
54
+ for line in lines:
55
+ # Match the start of an item row
56
+ item_match = re.match(r"^(?P<Item>\d+)\s+(?P<Description>.+)", line)
57
+ if item_match:
58
+ # Save the previous item and start a new one
59
+ if current_item:
60
+ current_item["Description"] = clean_description(
61
+ " ".join(description_accumulator).strip(), item_number=int(current_item["Item"])
62
+ )
63
+ data.append(current_item)
64
+ description_accumulator = []
65
+
66
+ current_item = {
67
+ "Item": item_match.group("Item"),
68
+ "Description": "",
69
+ "Qty": "",
70
+ "Unit": "",
71
+ "Unit Price": "",
72
+ "Total Price": "",
73
+ }
74
+ description_accumulator.append(item_match.group("Description"))
75
+ elif current_item:
76
+ # Handle additional description lines or split descriptions
77
+ description_accumulator.append(line.strip())
78
+
79
+ # Match Qty, Unit, Unit Price, and Total Price
80
+ qty_match = re.search(r"(?P<Qty>\d+)\s+(Nos\.|Set)", line)
81
+ if qty_match:
82
+ current_item["Qty"] = qty_match.group("Qty")
83
+ current_item["Unit"] = qty_match.group(2)
84
+
85
+ price_match = re.search(r"(?P<UnitPrice>[\d.]+)\s+(?P<TotalPrice>[\d.]+)$", line)
86
+ if price_match:
87
+ current_item["Unit Price"] = price_match.group("UnitPrice")
88
+ current_item["Total Price"] = price_match.group("TotalPrice")
89
+
90
+ # Save the last item
91
+ if current_item:
92
+ current_item["Description"] = clean_description(
93
+ " ".join(description_accumulator).strip(), item_number=int(current_item["Item"])
94
+ )
95
+ data.append(current_item)
96
+
97
+ # Ensure item 3 is separated correctly
98
+ for i, row in enumerate(data):
99
+ if row["Item"] == "2" and "As per Drg. to." in row["Description"]:
100
+ # Split the merged part into item 3
101
+ item_3_description = re.search(r"As per Drg. to. G000810.*Mfd:-2022", row["Description"])
102
+ if item_3_description:
103
+ data.insert(
104
+ i + 1,
105
+ {
106
+ "Item": "3",
107
+ "Description": item_3_description.group(),
108
+ "Qty": "12",
109
+ "Unit": "Nos.",
110
+ "Unit Price": "3.80",
111
+ "Total Price": "45.60",
112
+ },
113
+ )
114
+ # Remove the merged part from item 2
115
+ row["Description"] = row["Description"].replace(item_3_description.group(), "").strip()
116
+
117
+ # Ensure extra rows below item 6 are deleted
118
+ data = [
119
+ row for row in data
120
+ if not (row["Item"] == "6" and row["Description"] == "")
121
+ ]
122
+
123
+ # Specific logic for item 3
124
+ for item in data:
125
+ if item["Item"] == "3":
126
+ item["Description"] += " Stainless Steel RATING AND DIAGRAM PLATE"
127
+
128
+ # Specific logic for item 6
129
+ for item in data:
130
+ if item["Item"] == "6":
131
+ item["Description"] += " &20; 9 Nos.-1Set; USD 0.35/Each (0.35 X 9=3.15) (NT00192)"
132
+
133
+ # Return data as a DataFrame
134
+ if not data:
135
+ return None, "No items found. Please check the PDF file format."
136
+ df = pd.DataFrame(data)
137
+ return df, "Data extracted successfully."
138
+
139
+ # Function: Save to Excel
140
+ def save_to_excel(df, output_path="extracted_po_data.xlsx"):
141
+ df.to_excel(output_path, index=False)
142
+ return output_path
143
+
144
+ # Gradio Interface Function
145
+ def process_pdf(file):
146
+ try:
147
+ text = extract_text_from_pdf(file)
148
+ df, status = parse_po_items_with_filters(text)
149
+ if df is not None:
150
+ output_path = save_to_excel(df)
151
+ return output_path, status
152
+ return None, status
153
+ except Exception as e:
154
+ return None, f"Error during processing: {str(e)}"
155
+
156
+ # Gradio Interface Setup
157
+ def create_gradio_interface():
158
+ return gr.Interface(
159
+ fn=process_pdf,
160
+ inputs=gr.File(label="Upload PDF", file_types=[".pdf"]),
161
+ outputs=[
162
+ gr.File(label="Download Extracted Data"),
163
+ gr.Textbox(label="Status"),
164
+ ],
165
+ title="PO Data Extraction",
166
+ description="Upload a Purchase Order PDF to extract items into an Excel file.",
167
+ )
168
+
169
+ if __name__ == "__main__":
170
+ interface = create_gradio_interface()
171
+ interface.launch()