rehan953 commited on
Commit
882ba3a
·
verified ·
1 Parent(s): ddc5a81

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -28
app.py CHANGED
@@ -87,37 +87,28 @@ def _resize_for_inference(img):
87
  return img.resize(new_size, Image.LANCZOS)
88
 
89
 
90
- def _build_table(rows: List[str]) -> str:
91
- parsed = []
92
-
93
- for row in rows:
94
- parts = re.split(r"\s{2,}", row.strip())
95
- if len(parts) >= 3:
96
- date = parts[0]
97
- amount = parts[-1] if re.search(r"\d+\.\d{2}", parts[-1]) else ""
98
- desc = " ".join(parts[1:-1]) if amount else " ".join(parts[1:])
99
- parsed.append((date, desc, amount))
100
-
101
- if not parsed:
102
- return "\n".join(rows)
103
-
104
  table = [
105
- "| POSTING DATE | DESCRIPTION | AMOUNT |",
106
- "| :--- | :--- | ---: |",
107
  ]
108
- for date, desc, amount in parsed:
109
- table.append(f"| {date} | {desc} | {amount} |")
110
  return "\n".join(table)
111
 
112
 
113
  def _normalize_transactions(text: str) -> str:
114
  """
115
- Convert loose transaction lines into structured markdown tables.
 
116
  """
117
  lines = text.split("\n")
118
  result: List[str] = []
119
  table_buffer: List[str] = []
120
  in_section = False
 
121
 
122
  for line in lines:
123
  if re.search(r"POSTING DATE.*DESCRIPTION.*AMOUNT", line, re.IGNORECASE):
@@ -127,7 +118,19 @@ def _normalize_transactions(text: str) -> str:
127
 
128
  if in_section and (line.strip() == "" or line.strip().startswith("Subtotal")):
129
  if table_buffer:
130
- result.append(_build_table(table_buffer))
 
 
 
 
 
 
 
 
 
 
 
 
131
  table_buffer = []
132
  in_section = False
133
  result.append(line)
@@ -139,7 +142,19 @@ def _normalize_transactions(text: str) -> str:
139
  result.append(line)
140
 
141
  if table_buffer:
142
- result.append(_build_table(table_buffer))
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
  return "\n".join(result)
145
 
@@ -162,7 +177,38 @@ def _html_to_markdown_tables(text: str) -> str:
162
  try:
163
  from bs4 import BeautifulSoup
164
  except Exception:
165
- return text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
  soup = BeautifulSoup(text, "html.parser")
168
 
@@ -188,8 +234,39 @@ def _html_to_markdown_tables(text: str) -> str:
188
  return str(soup)
189
 
190
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  def _clean_markdown(text: str) -> str:
192
  """Post-process markdown to fix table formatting and section structure."""
 
193
  text = _html_to_markdown_tables(text)
194
  text = _fix_missing_amounts(text)
195
  text = _normalize_transactions(text)
@@ -246,12 +323,9 @@ def _infer_image(image_path: str) -> str:
246
  {
247
  "type": "text",
248
  "text": (
249
- "Document Parsing to markdown.\n"
250
- "Rules:\n"
251
- "1) Preserve rows exactly in reading order.\n"
252
- "2) Keep transaction blocks as markdown tables.\n"
253
- "3) Do not invent rows or sample/template data.\n"
254
- "4) Keep right-most amount values."
255
  ),
256
  },
257
  ],
 
87
  return img.resize(new_size, Image.LANCZOS)
88
 
89
 
90
+ def _build_table_from_entries(entries: List[dict]) -> str:
91
+ if not entries:
92
+ return ""
 
 
 
 
 
 
 
 
 
 
 
93
  table = [
94
+ "| Date | Description | Amount |",
95
+ "|------|------------|--------|",
96
  ]
97
+ for row in entries:
98
+ table.append(f"| {row['date']} | {row['description']} | {row['amount']} |")
99
  return "\n".join(table)
100
 
101
 
102
  def _normalize_transactions(text: str) -> str:
103
  """
104
+ Convert loose transaction lines into structured markdown tables using:
105
+ MM/DD ... amount
106
  """
107
  lines = text.split("\n")
108
  result: List[str] = []
109
  table_buffer: List[str] = []
110
  in_section = False
111
+ pattern = re.compile(r"(\d{2}/\d{2})\s+(.*?)\s+([\d,]+\.\d{2})$")
112
 
113
  for line in lines:
114
  if re.search(r"POSTING DATE.*DESCRIPTION.*AMOUNT", line, re.IGNORECASE):
 
118
 
119
  if in_section and (line.strip() == "" or line.strip().startswith("Subtotal")):
120
  if table_buffer:
121
+ clean_rows = []
122
+ for raw in table_buffer:
123
+ normalized = " ".join(raw.split())
124
+ match = pattern.search(normalized)
125
+ if match:
126
+ date, desc, amount = match.groups()
127
+ clean_rows.append(
128
+ {"date": date, "description": desc, "amount": amount}
129
+ )
130
+ if clean_rows:
131
+ result.append(_build_table_from_entries(clean_rows))
132
+ else:
133
+ result.extend(table_buffer)
134
  table_buffer = []
135
  in_section = False
136
  result.append(line)
 
142
  result.append(line)
143
 
144
  if table_buffer:
145
+ clean_rows = []
146
+ for raw in table_buffer:
147
+ normalized = " ".join(raw.split())
148
+ match = pattern.search(normalized)
149
+ if match:
150
+ date, desc, amount = match.groups()
151
+ clean_rows.append(
152
+ {"date": date, "description": desc, "amount": amount}
153
+ )
154
+ if clean_rows:
155
+ result.append(_build_table_from_entries(clean_rows))
156
+ else:
157
+ result.extend(table_buffer)
158
 
159
  return "\n".join(result)
160
 
 
177
  try:
178
  from bs4 import BeautifulSoup
179
  except Exception:
180
+ # Fallback when bs4 is unavailable: regex-based conversion for simple tables.
181
+ table_re = re.compile(r"<table[\s\S]*?</table>", re.IGNORECASE)
182
+ row_re = re.compile(r"<tr[\s\S]*?</tr>", re.IGNORECASE)
183
+ cell_re = re.compile(r"<t[dh][^>]*>([\s\S]*?)</t[dh]>", re.IGNORECASE)
184
+ tag_re = re.compile(r"<[^>]+>")
185
+
186
+ def _clean_cell(cell: str) -> str:
187
+ cell = re.sub(r"<br\s*/?>", " ", cell, flags=re.IGNORECASE)
188
+ cell = tag_re.sub(" ", cell)
189
+ cell = cell.replace("&nbsp;", " ").replace("&amp;", "&")
190
+ cell = re.sub(r"\s+", " ", cell).strip()
191
+ return cell
192
+
193
+ def _convert(table_html: str) -> str:
194
+ rows = []
195
+ for tr in row_re.findall(table_html):
196
+ cols = [_clean_cell(c) for c in cell_re.findall(tr)]
197
+ cols = [c for c in cols if c != ""]
198
+ if cols:
199
+ rows.append(cols)
200
+ if not rows:
201
+ return table_html
202
+ width = max(len(r) for r in rows)
203
+ norm = [r + [""] * (width - len(r)) for r in rows]
204
+ md = []
205
+ md.append("| " + " | ".join(norm[0]) + " |")
206
+ md.append("| " + " | ".join(["---"] * width) + " |")
207
+ for r in norm[1:]:
208
+ md.append("| " + " | ".join(r) + " |")
209
+ return "\n".join(md)
210
+
211
+ return table_re.sub(lambda m: _convert(m.group(0)), text)
212
 
213
  soup = BeautifulSoup(text, "html.parser")
214
 
 
234
  return str(soup)
235
 
236
 
237
+ def _remove_prompt_echo_artifacts(text: str) -> str:
238
+ """
239
+ Remove model prompt-echo garbage blocks such as repeated:
240
+ 'N) Keep left-most amount values.' / 'N) Keep right-most amount values.'
241
+ """
242
+ lines = text.split("\n")
243
+ cleaned: List[str] = []
244
+ keep_line_re = re.compile(
245
+ r"^\s*\d+\)\s*Keep\s+(left-most|right-most)\s+amount\s+values\.?\s*$",
246
+ re.IGNORECASE,
247
+ )
248
+ repeat_run = 0
249
+
250
+ for line in lines:
251
+ if keep_line_re.match(line):
252
+ repeat_run += 1
253
+ continue
254
+ # Also drop explicit prompt/rules echoes if they appear as plain output.
255
+ if re.match(r"^\s*Document Parsing to markdown\.?\s*$", line, re.IGNORECASE):
256
+ continue
257
+ if re.match(r"^\s*Rules:\s*$", line, re.IGNORECASE):
258
+ continue
259
+ cleaned.append(line)
260
+
261
+ # If there was a long repeated run, ensure dangling separator spacing stays clean.
262
+ text = "\n".join(cleaned)
263
+ text = re.sub(r"\n{4,}", "\n\n\n", text)
264
+ return text
265
+
266
+
267
  def _clean_markdown(text: str) -> str:
268
  """Post-process markdown to fix table formatting and section structure."""
269
+ text = _remove_prompt_echo_artifacts(text)
270
  text = _html_to_markdown_tables(text)
271
  text = _fix_missing_amounts(text)
272
  text = _normalize_transactions(text)
 
323
  {
324
  "type": "text",
325
  "text": (
326
+ "Extract document text as markdown only. "
327
+ "Do not include instructions, examples, or template rows. "
328
+ "Preserve transaction lines exactly."
 
 
 
329
  ),
330
  },
331
  ],