sammy786 commited on
Commit
03c920c
·
1 Parent(s): c2ddb81

Parser hardening + brand aliases: multi-issuer layouts, NUL/trim/control-char safety

Browse files
Files changed (1) hide show
  1. app/statement_parser.py +4 -3
app/statement_parser.py CHANGED
@@ -102,7 +102,7 @@ def _split_glued_city(s: str) -> str:
102
 
103
 
104
  def _clean_merchant(raw: str) -> str:
105
- original = (raw or "").strip()
106
  strip = lambda x: re.sub(r"^[\s*.,\-]+|[\s*.,\-]+$", "", x).strip()
107
  if re.search(r"paypal", original, re.IGNORECASE):
108
  m = re.search(r"paypal\s*\*?\s*([a-z][a-z0-9 &._-]{1,24})", original, re.IGNORECASE)
@@ -119,8 +119,8 @@ def _clean_merchant(raw: str) -> str:
119
  s = re.sub(r"\*+", " ", s)
120
  s = strip(re.sub(r"\s{2,}", " ", s))
121
  if len(s) < 2:
122
- return original[:40] or "Transaction"
123
- return _title(_split_glued_city(s))[:40]
124
 
125
 
126
  def _parse_date(s: str) -> Optional[str]:
@@ -213,6 +213,7 @@ def parse_csv(content: bytes) -> List[Dict]:
213
  skipped so only real spends feed the optimal-card analysis.
214
  """
215
  text = content.decode("utf-8-sig", errors="ignore")
 
216
  delim = _detect_delimiter(text)
217
  lines = [ln for ln in text.splitlines() if ln.strip()]
218
  rows = [[c.strip() for c in _split_line(ln, delim)] for ln in lines]
 
102
 
103
 
104
  def _clean_merchant(raw: str) -> str:
105
+ original = re.sub(r"[\x00-\x1f\x7f]+", " ", (raw or "")).strip() # never show control chars in a name
106
  strip = lambda x: re.sub(r"^[\s*.,\-]+|[\s*.,\-]+$", "", x).strip()
107
  if re.search(r"paypal", original, re.IGNORECASE):
108
  m = re.search(r"paypal\s*\*?\s*([a-z][a-z0-9 &._-]{1,24})", original, re.IGNORECASE)
 
119
  s = re.sub(r"\*+", " ", s)
120
  s = strip(re.sub(r"\s{2,}", " ", s))
121
  if len(s) < 2:
122
+ return original[:40].strip() or "Transaction"
123
+ return _title(_split_glued_city(s))[:40].strip() # trim AFTER truncation so a cut long name has no trailing space
124
 
125
 
126
  def _parse_date(s: str) -> Optional[str]:
 
213
  skipped so only real spends feed the optimal-card analysis.
214
  """
215
  text = content.decode("utf-8-sig", errors="ignore")
216
+ text = text.replace("\x00", "") # NUL bytes (corrupt/binary upload) make csv.reader raise
217
  delim = _detect_delimiter(text)
218
  lines = [ln for ln in text.splitlines() if ln.strip()]
219
  rows = [[c.strip() for c in _split_line(ln, delim)] for ln in lines]