Murali Chandran commited on
Commit
f2f7976
·
1 Parent(s): 28ddd07

updated unit_parser

Browse files
Files changed (2) hide show
  1. bot_inference.py +7 -4
  2. unit_parser.py +74 -43
bot_inference.py CHANGED
@@ -6,12 +6,15 @@ from bot_model import GPT
6
  # ----------------- Reuse the fixed unit/money parsing -----------------
7
  # Make sure unit_parser_fixed.py is on your PYTHONPATH or in the same package.
8
  from unit_parser import (
9
- MONEY_RE,
10
- UNIT_LIKE_RE,
11
- GROUP_LIKE_RE,
12
- try_solve_unit_like, # deterministic unit-price solver (fixed 2000 → 200 bug)
 
13
  )
14
 
 
 
15
  # ----------------- Token helpers -----------------
16
  def decode(ids, itos):
17
  return "".join(itos[i] for i in ids)
 
6
  # ----------------- Reuse the fixed unit/money parsing -----------------
7
  # Make sure unit_parser_fixed.py is on your PYTHONPATH or in the same package.
8
  from unit_parser import (
9
+ canonicalize_nl,
10
+ try_solve_unit_like,
11
+ is_addition_query,
12
+ canonicalize_addition,
13
+ sum_addends,
14
  )
15
 
16
+
17
+
18
  # ----------------- Token helpers -----------------
19
  def decode(ids, itos):
20
  return "".join(itos[i] for i in ids)
unit_parser.py CHANGED
@@ -1,42 +1,46 @@
 
1
  import re
2
  from typing import Optional
3
- from config import CFG, money
4
- # currency patterns
 
 
5
  CURRENCY_PREFIX = r"[$£€₹¥₩₦]|A\$|C\$|CHF|د\.إ"
6
- MONEY_RE = re.compile(rf"(?P<prefix>{CURRENCY_PREFIX})?\s*(?P<val>\d+(?:\.\d{{1,2}})?)\s*(?P<word>(?:[a-zA-Z]+(?:\s[a-zA-Z]+)?)s?)?", re.IGNORECASE)
 
 
 
7
 
 
 
 
8
  UNIT_LIKE_RE = re.compile(
9
  r"(?P<q1>\d+)\s+(?P<item>[\w\s]+?)\s+(?:is|are|cost|costs|priced at|=)\s+"
10
- r"(?P<cost>\d{1,3}(?:,\d{3})*(?:\.\d+)?|\d+(?:\.\d+)?(?:\s*\w+)?)"
11
- r"[,\.]?\s*(?:how much|what\s*'?s|price|cost).*(?P<q2>\d+)\s+(?P=item)\b",
12
  re.IGNORECASE,
13
  )
14
 
15
-
16
  GROUP_LIKE_RE = re.compile(
17
- r"(?P<q1>\d+)\s+(?P<item>\w+?s?)\s+for\s+(?P<cost>.+?)\b[,\.]?\s*(?:how much|what\s*'?s|price|cost).*(?P<q2>\d+)\s+(?P=item)\b",
18
  re.IGNORECASE,
19
  )
20
 
21
- ARITH_RES = {
22
- '+': re.compile(r"^\s*([\d\s\+]+)(?:=\s*\?\s*|\?|)$"),
23
- '-': re.compile(r"^\s*(\d+)\s*[-–]\s*(\d+).*?$"),
24
- 'x': re.compile(r"^\s*(\d+)\s*[x×*]\s*(\d+).*?$", re.IGNORECASE),
25
- '/': re.compile(r"^\s*(\d+)\s*[÷/]\s*(\d+).*?$"),
26
- }
27
-
28
- # Addition embedded in NL, e.g., "what is 2 + 2 = ?" or "find 3+5+7"
29
- ADD_LIKE_NL = re.compile(r".*?(?P<expr>\d+(?:\s*\+\s*\d+)+).*", re.IGNORECASE)
30
-
31
  ADD_RE = re.compile(r"^\s*([\d+\s=\?]+)\s*$")
32
  NUM_RE = re.compile(r"\d+")
33
 
 
 
 
 
 
 
34
  def parse_money_str(s: str) -> str:
35
  m = MONEY_RE.search(s)
36
  if not m:
37
  return s.strip()
38
  prefix = (m.group('prefix') or '').strip()
39
- val = float(m.group('val'))
40
  word = (m.group('word') or '').strip()
41
  if prefix:
42
  return f"{prefix}{money(val)}"
@@ -46,32 +50,12 @@ def parse_money_str(s: str) -> str:
46
  return f"{money(val)} {word}"
47
  return money(val)
48
 
 
 
 
49
  def canonicalize_nl(text: str) -> Optional[str]:
50
  t = text.strip()
51
 
52
- # Addition inside NL
53
- m_add_nl = ADD_LIKE_NL.match(t)
54
- if m_add_nl:
55
- expr = m_add_nl.group('expr')
56
- parts = [p.strip() for p in expr.split('+')]
57
- expr = " + ".join(parts)
58
- return f"q: {expr} a:"
59
-
60
- # quick arithmetic
61
- m = ARITH_RES['-'].match(t)
62
- if m:
63
- a, b = m.groups()
64
- return f"q: {a} - {b} a:"
65
- m = ARITH_RES['x'].match(t)
66
- if m:
67
- a, b = m.groups()
68
- return f"q: {a} × {b} a:"
69
- m = ARITH_RES['/'].match(t)
70
- if m:
71
- a, b = m.groups()
72
- return f"q: {a} ÷ {b} a:"
73
-
74
- # unit-like
75
  m = UNIT_LIKE_RE.search(t)
76
  if m:
77
  q1 = int(m.group('q1')); item = m.group('item').rstrip('.')
@@ -79,7 +63,6 @@ def canonicalize_nl(text: str) -> Optional[str]:
79
  q2 = int(m.group('q2'))
80
  return f"q: {q1} {item} cost {cost}. how much is {q2} {item}? a:"
81
 
82
- # group-like
83
  m = GROUP_LIKE_RE.search(t)
84
  if m:
85
  q1 = int(m.group('q1')); item = m.group('item').rstrip('.')
@@ -91,7 +74,8 @@ def canonicalize_nl(text: str) -> Optional[str]:
91
 
92
  def is_addition_query(text: str) -> bool:
93
  t = text.strip()
94
- if not ADD_RE.match(t): return False
 
95
  return ('+' in t) and (len(NUM_RE.findall(t)) >= 2)
96
 
97
  def canonicalize_addition(text: str) -> str:
@@ -100,3 +84,50 @@ def canonicalize_addition(text: str) -> str:
100
 
101
  def sum_addends(text: str) -> int:
102
  return sum(int(x) for x in NUM_RE.findall(text))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # unit_parser_fixed.py
2
  import re
3
  from typing import Optional
4
+
5
+ # =============================
6
+ # Currency / money patterns
7
+ # =============================
8
  CURRENCY_PREFIX = r"[$£€₹¥₩₦]|A\$|C\$|CHF|د\.إ"
9
+ MONEY_RE = re.compile(
10
+ rf"(?P<prefix>{CURRENCY_PREFIX})?\s*(?P<val>\d+(?:,\d{{3}})*(?:\.\d{{1,2}})?)\s*(?P<word>(?:[a-zA-Z]+(?:\s[a-zA-Z]+)?)s?)?",
11
+ re.IGNORECASE,
12
+ )
13
 
14
+ # =============================
15
+ # Unit / group price patterns
16
+ # =============================
17
  UNIT_LIKE_RE = re.compile(
18
  r"(?P<q1>\d+)\s+(?P<item>[\w\s]+?)\s+(?:is|are|cost|costs|priced at|=)\s+"
19
+ r"(?P<cost>\d+(?:,\d{3})*(?:\.\d+)?(?:\s*\w+)?)"
20
+ r"[,\.]?\s*(?:how much|what\s*'?s|price|cost).*?(?P<q2>\d+)\s+(?P=item)\b",
21
  re.IGNORECASE,
22
  )
23
 
 
24
  GROUP_LIKE_RE = re.compile(
25
+ r"(?P<q1>\d+)\s+(?P<item>\w+?s?)\s+for\s+(?P<cost>.+?)\b[,\.]?\s*(?:how much|what\s*'?s|price|cost).*?(?P<q2>\d+)\s+(?P=item)\b",
26
  re.IGNORECASE,
27
  )
28
 
 
 
 
 
 
 
 
 
 
 
29
  ADD_RE = re.compile(r"^\s*([\d+\s=\?]+)\s*$")
30
  NUM_RE = re.compile(r"\d+")
31
 
32
+ # =============================
33
+ # Helpers
34
+ # =============================
35
+ def money(v: float) -> str:
36
+ return str(int(round(v))) if abs(v - round(v)) < 1e-9 else f"{v:.2f}"
37
+
38
  def parse_money_str(s: str) -> str:
39
  m = MONEY_RE.search(s)
40
  if not m:
41
  return s.strip()
42
  prefix = (m.group('prefix') or '').strip()
43
+ val = float((m.group('val') or '').replace(',', ''))
44
  word = (m.group('word') or '').strip()
45
  if prefix:
46
  return f"{prefix}{money(val)}"
 
50
  return f"{money(val)} {word}"
51
  return money(val)
52
 
53
+ # =============================
54
+ # Canonicalizers
55
+ # =============================
56
  def canonicalize_nl(text: str) -> Optional[str]:
57
  t = text.strip()
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  m = UNIT_LIKE_RE.search(t)
60
  if m:
61
  q1 = int(m.group('q1')); item = m.group('item').rstrip('.')
 
63
  q2 = int(m.group('q2'))
64
  return f"q: {q1} {item} cost {cost}. how much is {q2} {item}? a:"
65
 
 
66
  m = GROUP_LIKE_RE.search(t)
67
  if m:
68
  q1 = int(m.group('q1')); item = m.group('item').rstrip('.')
 
74
 
75
  def is_addition_query(text: str) -> bool:
76
  t = text.strip()
77
+ if not ADD_RE.match(t):
78
+ return False
79
  return ('+' in t) and (len(NUM_RE.findall(t)) >= 2)
80
 
81
  def canonicalize_addition(text: str) -> str:
 
84
 
85
  def sum_addends(text: str) -> int:
86
  return sum(int(x) for x in NUM_RE.findall(text))
87
+
88
+ # =============================
89
+ # Deterministic solver
90
+ # =============================
91
+ _NUMBER_TOKEN_RE = re.compile(r"\d+(?:,\d{3})*(?:\.\d+)?")
92
+
93
+ def try_solve_unit_like(text: str) -> Optional[str]:
94
+ m = UNIT_LIKE_RE.search(text)
95
+ if not m:
96
+ return None
97
+
98
+ q1 = int(m.group('q1'))
99
+ q2 = int(m.group('q2'))
100
+ item = (m.group('item') or '').strip().rstrip('.')
101
+ cost_raw = (m.group('cost') or '').strip()
102
+
103
+ nums = _NUMBER_TOKEN_RE.findall(cost_raw)
104
+ if not nums:
105
+ return None
106
+ token = max(nums, key=lambda s: len(s.replace(',', '')))
107
+ cost_val = float(token.replace(',', ''))
108
+
109
+ if q1 == 0:
110
+ return "final answer: undefined"
111
+
112
+ unit = cost_val / q1
113
+ total = unit * q2
114
+
115
+ mcur = MONEY_RE.search(cost_raw)
116
+ prefix = (mcur.group('prefix') or '').strip() if mcur else ''
117
+ word = (mcur.group('word') or '').strip() if mcur else ''
118
+
119
+ def _fmt(v: float) -> str:
120
+ s = money(v)
121
+ if prefix:
122
+ return f"{prefix}{s}"
123
+ if word:
124
+ sing = word[:-1] if word.lower().endswith('s') and abs(v-1.0) < 1e-9 else word
125
+ return f"{s} {sing}"
126
+ return s
127
+
128
+ base_item = item[:-1] if item.lower().endswith('s') else item
129
+ return (
130
+ f"step 1: find unit price: {_fmt(cost_val)} / {q1} = {_fmt(unit)} per {base_item}. "
131
+ f"step 2: multiply by {q2}: {_fmt(unit)} × {q2} = {_fmt(total)}. "
132
+ f"final answer: {_fmt(total)}"
133
+ )