sid570 commited on
Commit
8469a31
·
verified ·
1 Parent(s): 43757c9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -142
app.py CHANGED
@@ -1,16 +1,18 @@
1
  import gradio as gr
2
  import re
3
- from entity_dictionary import ENTITY_DICTIONARY, WEEKDAY_MAP, MONTH_MAP
4
 
5
  # ============================================================
6
- # LOAD VOCABULARY (Bhojpuri common words)
7
  # ============================================================
8
  VOCAB_PATH = "vocab.txt"
 
9
  with open(VOCAB_PATH, "r", encoding="utf-8") as f:
10
  VOCAB = {w.strip().lower() for w in f if w.strip()}
11
 
 
12
  # ============================================================
13
- # PRIORITY ORDER Dictionary > Regex > UNKNOWN
14
  # ============================================================
15
  CATEGORY_PRIORITY = {
16
  "DISTRICT": 1,
@@ -19,48 +21,37 @@ CATEGORY_PRIORITY = {
19
  "LANDMARK": 4,
20
  "PERSON": 5,
21
  "PRODUCT": 6,
22
- "WEEKDAY": 7, # ✅ added
23
- "MONTH": 8, # ✅ added
24
- "TIME": 9,
25
- "DATE": 10,
26
- "PHONE": 11,
27
- "MONEY": 12,
28
- "NUMBER": 13,
29
  "UNKNOWN": 50
30
  }
31
 
 
32
  # ============================================================
33
- # NORMALIZE TEXT SPACING (removes double spaces + fixes . Tu)
34
  # ============================================================
35
  def normalize_sentence_spacing(text):
36
- # 1️⃣ Remove multiple spaces
37
  text = re.sub(r"\s+", " ", text)
38
-
39
- # 2️⃣ Remove spaces directly after punctuation
40
  text = re.sub(r"([.!?])\s+", r"\1", text)
41
-
42
- # 3️⃣ Trim leading/trailing spaces
43
  return text.strip()
44
 
45
- # ============================================================
46
- # HELPER — Split text into tokens, keeping punctuation
47
- # ============================================================
48
  def split_with_punctuation(text):
49
  return re.findall(r"\w+|[.!?]", text)
50
 
 
51
  # ============================================================
52
- # CAPITAL PHRASE DETECTION (UNKNOWN)
53
  # ============================================================
54
  def detect_capital_phrases(text, dictionary_entities):
55
  tokens = text.split()
56
  capital_entities = []
57
 
58
  dict_full = {e["text"].lower() for e in dictionary_entities}
59
- dict_words = {
60
- part
61
- for e in dictionary_entities
62
- for part in e["text"].lower().split()
63
- }
64
 
65
  i = 1 # skip first word
66
  while i < len(tokens):
@@ -73,21 +64,15 @@ def detect_capital_phrases(text, dictionary_entities):
73
  if len(w) > 1 and w[0].isupper():
74
  phrase = [w]
75
  j = i + 1
76
- while j < len(tokens) and len(tokens[j]) > 1 and tokens[j][0].isupper():
77
  phrase.append(tokens[j])
78
  j += 1
79
 
80
- full_phrase = " ".join(phrase)
81
- full_lower = full_phrase.lower()
82
 
83
- if (
84
- full_lower in dict_full
85
- or all(tok.lower() in dict_words for tok in phrase)
86
- ):
87
- i = j
88
- continue
89
 
90
- capital_entities.append({"text": full_phrase, "type": "UNKNOWN"})
91
  i = j
92
  continue
93
 
@@ -95,121 +80,87 @@ def detect_capital_phrases(text, dictionary_entities):
95
 
96
  return capital_entities
97
 
 
98
  # ============================================================
99
- # ENTITY DETECTION
100
  # ============================================================
101
  def detect_entities(text):
102
  entities = []
103
-
104
- # ---------- Dictionary detection ----------
105
  dictionary_entities = []
 
 
106
  for category, words in ENTITY_DICTIONARY.items():
107
  if category == "REGEX_PATTERNS":
108
  continue
109
 
110
  for w in words:
111
- pattern = re.compile(
112
- rf"(?<![A-Za-z]){re.escape(w)}(?![A-Za-z])",
113
- re.IGNORECASE
114
- )
115
  for m in pattern.finditer(text):
116
- dictionary_entities.append(
117
- {"text": m.group(), "type": category}
118
- )
119
 
120
  entities.extend(dictionary_entities)
121
 
122
- # ---------- Regex detection ----------
123
  for etype, pattern in ENTITY_DICTIONARY["REGEX_PATTERNS"].items():
124
  for m in re.findall(pattern, text):
125
  entities.append({"text": m, "type": etype})
126
 
127
- # ---------- Sentence-aware first-word UNKNOWN logic ----------
128
  text = normalize_sentence_spacing(text)
129
  tokens = split_with_punctuation(text)
130
- sentence_start = True
131
 
 
132
  for w in tokens:
133
- w = w.strip()
134
- if not w:
135
- continue
136
-
137
  if w in [".", "!", "?"]:
138
  sentence_start = True
139
  continue
140
 
141
- if not re.match(r"^[A-Za-z]+$", w):
142
  continue
143
 
144
  if sentence_start:
145
  lw = w.lower()
146
-
147
- if any(ent["text"].lower() == lw for ent in dictionary_entities):
148
- sentence_start = False
149
- continue
150
-
151
  if lw not in VOCAB:
152
  entities.append({"text": w, "type": "UNKNOWN"})
153
-
154
  sentence_start = False
155
 
156
- # ---------- Capital UNKNOWN detection ----------
157
  entities.extend(detect_capital_phrases(text, dictionary_entities))
158
 
159
  # ---------- Deduplicate ----------
160
- uniq = []
161
  seen = set()
162
- for ent in entities:
163
- key = (ent["text"].lower(), ent["type"])
 
164
  if key not in seen:
165
  seen.add(key)
166
- uniq.append(ent)
167
 
168
- # ---------- Remove NUMBER inside TIME ----------
169
- cleaned = []
170
- for e in uniq:
171
- if e["type"] == "NUMBER":
172
- if any(
173
- t["type"] == "TIME" and e["text"] in t["text"]
174
- for t in uniq
175
- ):
176
- continue
177
- cleaned.append(e)
178
 
179
- return cleaned
180
 
181
  # ============================================================
182
- # MASK ENTITIES
183
  # ============================================================
184
  def mask_entities(text):
185
  detected = detect_entities(text)
186
 
187
- sorted_entities = sorted(
188
- detected,
189
- key=lambda e: (
190
- CATEGORY_PRIORITY.get(e["type"], 999),
191
- -len(e["text"])
192
- )
193
  )
194
 
195
  masked = text
196
  entity_map = {}
197
  counters = {}
198
 
199
- for ent in sorted_entities:
200
- cat = ent["type"]
201
  counters[cat] = counters.get(cat, 1)
202
  placeholder = f"<{cat}_{counters[cat]}>"
203
  counters[cat] += 1
204
 
205
- masked = re.sub(
206
- re.escape(ent["text"]),
207
- placeholder,
208
- masked,
209
- flags=re.IGNORECASE
210
- )
211
-
212
- entity_map[placeholder] = ent["text"]
213
 
214
  return {
215
  "masked_text": masked,
@@ -217,68 +168,34 @@ def mask_entities(text):
217
  "entities": detected
218
  }
219
 
220
- # ============================================================
221
- # UNMASK ENTITIES (WEEKDAY + MONTH SAFE)
222
- # ============================================================
223
- def unmask_entities(masked_text, entity_map):
224
- unmasked = masked_text
225
-
226
- for ph in sorted(entity_map.keys(), key=len, reverse=True):
227
- original = entity_map[ph]
228
- lw = original.lower()
229
-
230
- if ph.startswith("<WEEKDAY_"):
231
- replacement = WEEKDAY_MAP.get(lw, original)
232
 
233
- elif ph.startswith("<MONTH_"):
234
- replacement = MONTH_MAP.get(lw, original)
235
-
236
- else:
237
- replacement = original
238
-
239
- unmasked = unmasked.replace(ph, replacement)
240
 
241
- return unmasked
242
 
243
  # ============================================================
244
- # GRADIO UI
245
  # ============================================================
246
  with gr.Blocks() as demo:
247
- gr.Markdown(
248
- "# 🧠 Entity Detection & Masking API\n"
249
- "### Bhojpuri ⇄ Kreol Morisien (Weekdays & Months Enabled)"
250
- )
251
 
252
  with gr.Tab("Detect Entities"):
253
- detect_in = gr.Textbox(label="Input Text")
254
- detect_out = gr.JSON(label="Detected Entities")
255
- gr.Button("Detect").click(
256
- detect_entities,
257
- detect_in,
258
- detect_out,
259
- api_name="detect_entities"
260
- )
261
 
262
  with gr.Tab("Mask Entities"):
263
- mask_in = gr.Textbox(label="Input Text")
264
- mask_out = gr.JSON(label="Masking Output")
265
- gr.Button("Mask").click(
266
- mask_entities,
267
- mask_in,
268
- mask_out,
269
- api_name="mask_entities"
270
- )
271
 
272
  with gr.Tab("Unmask Entities"):
273
- unmask_text = gr.Textbox(label="Masked Text")
274
- unmask_map = gr.JSON(label="Entity Map")
275
- unmask_out = gr.Textbox(label="Unmasked Output")
276
- gr.Button("Unmask").click(
277
- unmask_entities,
278
- [unmask_text, unmask_map],
279
- unmask_out,
280
- api_name="unmask_entities"
281
- )
282
 
283
  demo.queue()
284
  demo.launch()
 
1
  import gradio as gr
2
  import re
3
+ from entity_dictionary import ENTITY_DICTIONARY
4
 
5
  # ============================================================
6
+ # 📘 LOAD VOCABULARY
7
  # ============================================================
8
  VOCAB_PATH = "vocab.txt"
9
+
10
  with open(VOCAB_PATH, "r", encoding="utf-8") as f:
11
  VOCAB = {w.strip().lower() for w in f if w.strip()}
12
 
13
+
14
  # ============================================================
15
+ # 🔢 CATEGORY PRIORITY (lower = higher priority)
16
  # ============================================================
17
  CATEGORY_PRIORITY = {
18
  "DISTRICT": 1,
 
21
  "LANDMARK": 4,
22
  "PERSON": 5,
23
  "PRODUCT": 6,
24
+ "TIME": 7,
25
+ "DATE": 8,
26
+ "PHONE": 9,
27
+ "MONEY": 10,
28
+ "NUMBER": 11,
 
 
29
  "UNKNOWN": 50
30
  }
31
 
32
+
33
  # ============================================================
34
+ # TEXT NORMALISATION
35
  # ============================================================
36
  def normalize_sentence_spacing(text):
 
37
  text = re.sub(r"\s+", " ", text)
 
 
38
  text = re.sub(r"([.!?])\s+", r"\1", text)
 
 
39
  return text.strip()
40
 
41
+
 
 
42
  def split_with_punctuation(text):
43
  return re.findall(r"\w+|[.!?]", text)
44
 
45
+
46
  # ============================================================
47
+ # 🔍 CAPITAL PHRASE DETECTION (UNKNOWN)
48
  # ============================================================
49
  def detect_capital_phrases(text, dictionary_entities):
50
  tokens = text.split()
51
  capital_entities = []
52
 
53
  dict_full = {e["text"].lower() for e in dictionary_entities}
54
+ dict_words = {p for e in dictionary_entities for p in e["text"].lower().split()}
 
 
 
 
55
 
56
  i = 1 # skip first word
57
  while i < len(tokens):
 
64
  if len(w) > 1 and w[0].isupper():
65
  phrase = [w]
66
  j = i + 1
67
+ while j < len(tokens) and tokens[j][0].isupper():
68
  phrase.append(tokens[j])
69
  j += 1
70
 
71
+ full_phrase = " ".join(phrase).lower()
 
72
 
73
+ if full_phrase not in dict_full and not all(p.lower() in dict_words for p in phrase):
74
+ capital_entities.append({"text": " ".join(phrase), "type": "UNKNOWN"})
 
 
 
 
75
 
 
76
  i = j
77
  continue
78
 
 
80
 
81
  return capital_entities
82
 
83
+
84
  # ============================================================
85
+ # 🧠 ENTITY DETECTION PIPELINE
86
  # ============================================================
87
  def detect_entities(text):
88
  entities = []
 
 
89
  dictionary_entities = []
90
+
91
+ # ---------- Dictionary ----------
92
  for category, words in ENTITY_DICTIONARY.items():
93
  if category == "REGEX_PATTERNS":
94
  continue
95
 
96
  for w in words:
97
+ pattern = re.compile(rf"(?<![A-Za-z]){re.escape(w)}(?![A-Za-z])", re.IGNORECASE)
 
 
 
98
  for m in pattern.finditer(text):
99
+ dictionary_entities.append({"text": m.group(), "type": category})
 
 
100
 
101
  entities.extend(dictionary_entities)
102
 
103
+ # ---------- Regex ----------
104
  for etype, pattern in ENTITY_DICTIONARY["REGEX_PATTERNS"].items():
105
  for m in re.findall(pattern, text):
106
  entities.append({"text": m, "type": etype})
107
 
108
+ # ---------- Sentence-start UNKNOWN ----------
109
  text = normalize_sentence_spacing(text)
110
  tokens = split_with_punctuation(text)
 
111
 
112
+ sentence_start = True
113
  for w in tokens:
 
 
 
 
114
  if w in [".", "!", "?"]:
115
  sentence_start = True
116
  continue
117
 
118
+ if not w.isalpha():
119
  continue
120
 
121
  if sentence_start:
122
  lw = w.lower()
 
 
 
 
 
123
  if lw not in VOCAB:
124
  entities.append({"text": w, "type": "UNKNOWN"})
 
125
  sentence_start = False
126
 
127
+ # ---------- Capital UNKNOWN ----------
128
  entities.extend(detect_capital_phrases(text, dictionary_entities))
129
 
130
  # ---------- Deduplicate ----------
 
131
  seen = set()
132
+ uniq = []
133
+ for e in entities:
134
+ key = (e["text"].lower(), e["type"])
135
  if key not in seen:
136
  seen.add(key)
137
+ uniq.append(e)
138
 
139
+ return uniq
 
 
 
 
 
 
 
 
 
140
 
 
141
 
142
  # ============================================================
143
+ # 🎭 MASK / UNMASK
144
  # ============================================================
145
  def mask_entities(text):
146
  detected = detect_entities(text)
147
 
148
+ detected.sort(
149
+ key=lambda e: (CATEGORY_PRIORITY.get(e["type"], 999), -len(e["text"]))
 
 
 
 
150
  )
151
 
152
  masked = text
153
  entity_map = {}
154
  counters = {}
155
 
156
+ for e in detected:
157
+ cat = e["type"]
158
  counters[cat] = counters.get(cat, 1)
159
  placeholder = f"<{cat}_{counters[cat]}>"
160
  counters[cat] += 1
161
 
162
+ masked = re.sub(re.escape(e["text"]), placeholder, masked, flags=re.IGNORECASE)
163
+ entity_map[placeholder] = e["text"]
 
 
 
 
 
 
164
 
165
  return {
166
  "masked_text": masked,
 
168
  "entities": detected
169
  }
170
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
+ def unmask_entities(masked_text, entity_map):
173
+ for ph in sorted(entity_map, key=len, reverse=True):
174
+ masked_text = masked_text.replace(ph, entity_map[ph])
175
+ return masked_text
 
 
 
176
 
 
177
 
178
  # ============================================================
179
+ # 🖥️ GRADIO UI
180
  # ============================================================
181
  with gr.Blocks() as demo:
182
+ gr.Markdown("# 🧠 Entity Detection & Masking API")
 
 
 
183
 
184
  with gr.Tab("Detect Entities"):
185
+ text_in = gr.Textbox(label="Input Text")
186
+ out = gr.JSON()
187
+ gr.Button("Detect").click(detect_entities, text_in, out)
 
 
 
 
 
188
 
189
  with gr.Tab("Mask Entities"):
190
+ text_in = gr.Textbox(label="Input Text")
191
+ out = gr.JSON()
192
+ gr.Button("Mask").click(mask_entities, text_in, out)
 
 
 
 
 
193
 
194
  with gr.Tab("Unmask Entities"):
195
+ masked = gr.Textbox(label="Masked Text")
196
+ entity_map = gr.JSON(label="Entity Map")
197
+ out = gr.Textbox(label="Unmasked Output")
198
+ gr.Button("Unmask").click(unmask_entities, [masked, entity_map], out)
 
 
 
 
 
199
 
200
  demo.queue()
201
  demo.launch()