blueradiance commited on
Commit
6f9c264
·
verified ·
1 Parent(s): cca37d8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +196 -0
app.py CHANGED
@@ -46,3 +46,199 @@ def apply_name_tags(text: str, names: list, start_index: int = 100) -> tuple[str
46
  mapping[tag] = name
47
  counter += 1
48
  return tagged_text, mapping
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  mapping[tag] = name
47
  counter += 1
48
  return tagged_text, mapping
49
+
50
+
51
+
52
+ # 📦 PART 2: 호칭/조사 확장기 + 태그 매핑 보정기
53
+
54
+ import re
55
+
56
+ # 호칭 + 조사 리스트
57
+ COMMON_SUFFIXES = [
58
+ # 📁 가정/관계 기반
59
+ '어머니', '아버지', '엄마', '아빠', '형', '누나', '언니', '오빠', '동생',
60
+ '딸', '아들', '조카', '사촌', '이모', '고모', '삼촌', '숙모', '외삼촌',
61
+ '할머니', '할아버지', '외할머니', '외할아버지', '장모', '장인', '며느리', '사위',
62
+ '부인', '와이프', '신랑', '올케', '형수', '제수씨', '매형', '처제', '시누이',
63
+
64
+ # 📁 사회/교육/직업 호칭
65
+ '학생', '초등학생', '중학생', '고등학생', '수험생', '학부모', '선생', '선생님', '교사',
66
+ '교감', '교장', '담임', '반장', '조교수', '교수', '연구원', '강사', '박사', '석사', '학사',
67
+ '보호자', '피해자', '아동', '주민', '당사자', '대상자', '담당자',
68
+
69
+ # 📁 직장/조직 직급
70
+ '대표', '이사', '전무', '상무', '부장', '차장', '과장', '대리', '사원', '팀장', '본부장',
71
+ '센터장', '소장', '실장', '총무', '직원', '매니저', '지점장', '사무장',
72
+
73
+ # 📁 의료/기타
74
+ '의사', '간호사', '간병인', '기사님', '어르신', '님', '씨'
75
+ ]
76
+
77
+ COMMON_JOSA = [
78
+ # 🧱 기본 주격/목적격/보격 조사
79
+ '이', '가', '을', '를', '은', '는', '의', '도',
80
+
81
+ # 📍 처소/이유/방향
82
+ '에', '에서', '에게', '으로', '로', '부터', '까지', '한테',
83
+
84
+ # 🌀 강조/비교/대조
85
+ '보다', '마저', '조차', '까지도', '조차도', '밖에', '만큼', '만큼은', '이라도', '이든지', '이나마',
86
+
87
+ # 🎯 연결/강조/기타
88
+ '이며', '이나', '이거나', '이라서', '이니까', '이라면', '이지만', '처럼', '대로', '하고', '그리고',
89
+
90
+ # 🧩 보조표현형 어미
91
+ '이기도', '이었던', '이었지만', '이어서', '이었다면', '인', '일', '임', '이란', '이라는',
92
+
93
+ # 📦 특수형 조사 (복합 + 종결형)
94
+ '같은', '같아서', '까지는', '뿐만 아니라', '와는', '와도', '하고도', '으로서', '으로써'
95
+ ]
96
+
97
+
98
+ def expand_variation_patterns(text: str, mapping: dict) -> str:
99
+ """
100
+ 👓 태그된 텍스트에서 성+이름+호칭+조사 형태를 다시 태깅
101
+ 예: '고은비학생이' → 'N100이'
102
+ """
103
+ for tag, base in mapping.items():
104
+ prefix = r'[\s\(\["'‘“]*' # 시작 구두점/공백 등
105
+ suffix = f"(?:{'|'.join(COMMON_SUFFIXES)})?"
106
+ josa = f"(?:{'|'.join(COMMON_JOSA)})?"
107
+ pattern = re.compile(rf'{prefix}{re.escape(base)}{suffix}{josa}', re.IGNORECASE)
108
+ text = pattern.sub(lambda m: m.group(0).replace(base, tag), text)
109
+ return text
110
+
111
+ def boost_mapping_from_context(text: str, mapping: dict) -> dict:
112
+ """
113
+ 📌 태깅된 텍스트에서 각 태그의 실제 확장된 표현 감지해 mapping 보정
114
+ 예: '고은비학생이' → 'N100', 매핑: N100 → '고은비학생이'
115
+ """
116
+ updated = {}
117
+ for tag, base in mapping.items():
118
+ idx = text.find(tag)
119
+ if idx == -1:
120
+ updated[tag] = base
121
+ continue
122
+ window = text[max(0, idx - 100): idx + 100]
123
+ pattern = re.compile(rf'([가-힣]+)?{re.escape(base)}(?:{"|".join(COMMON_SUFFIXES)})?(?:{"|".join(COMMON_JOSA)})?')
124
+ match = pattern.search(window)
125
+ if match:
126
+ updated[tag] = match.group(0)
127
+ else:
128
+ updated[tag] = base
129
+ return updated
130
+
131
+
132
+
133
+ # 📦 PART 3: 민감정보 마스커 + 학교/학년/학과 마스커
134
+
135
+ import re
136
+
137
+ def postprocess_sensitive_patterns(text: str) -> str:
138
+ """
139
+ 🔐 이메일, 주민등록번호, 계좌번호, 카드번호, 전화번호, 주소 마스킹
140
+ """
141
+ text = re.sub(r"[\w\.-]+@[\w\.-]+", "******@***.***", text) # 이메일
142
+ text = re.sub(r"(\d{6})[- ]?(\d{7})", "******-*******", text) # 주민번호
143
+ text = re.sub(r"(\d{3})[- ]?(\d{4})[- ]?(\d{4})", "***-****-****", text) # 카드/전화
144
+ text = re.sub(r"(\d{1,3})동", "***동", text)
145
+ text = re.sub(r"(\d{1,4})호", "****호", text)
146
+ return text
147
+
148
+ def to_chosung(text: str) -> str:
149
+ """
150
+ 🧠 초성 변환기: 학교명, 학과명 등에 적용
151
+ """
152
+ CHOSUNG_LIST = [chr(i) for i in range(0x1100, 0x1113)]
153
+ result = ""
154
+ for ch in text:
155
+ if '가' <= ch <= '힣':
156
+ code = ord(ch) - ord('가')
157
+ cho = code // 588
158
+ result += CHOSUNG_LIST[cho]
159
+ else:
160
+ result += ch
161
+ return result
162
+
163
+ def mask_school_names(text: str) -> str:
164
+ """
165
+ 🏫 학교명 → 초성 변환 마스킹 (연세대학교 → ㅇㅅ대학교)
166
+ """
167
+ def replace_school(m):
168
+ return to_chosung(m.group(1)) + m.group(2)
169
+ return re.sub(r"([가-힣]{2,20})(초등학교|중학교|고등학교|대학교)", replace_school, text)
170
+
171
+ def mask_department_names(text: str) -> str:
172
+ """
173
+ 🏢 학과명 → 초성 마스킹 (국문학과 → ㄱㅁ학과)
174
+ """
175
+ return re.sub(r"([가-힣]{2,20})학과", lambda m: to_chosung(m.group(1)) + "학과", text)
176
+
177
+ def mask_grade_class(text: str) -> str:
178
+ """
179
+ 🎓 학년/반 정보 마스킹 (2학년 3반 → *학년 *반)
180
+ """
181
+ return re.sub(r"(\d)학년(\s?(\d)반)?", "*학년 *반", text)
182
+
183
+
184
+
185
+ # 📦 PART 4: 기관 키워드 치환기 + Gradio UI 실행기
186
+
187
+ import re
188
+ import gradio as gr
189
+ from part1_name_extract_and_tag import extract_names, apply_name_tags
190
+ from part2_suffix_expansion_and_mapping import expand_variation_patterns, boost_mapping_from_context
191
+ from part3_sensitive_school_masker import (
192
+ postprocess_sensitive_patterns,
193
+ mask_school_names,
194
+ mask_department_names,
195
+ mask_grade_class
196
+ )
197
+
198
+ def replace_institution_keywords(text: str, keywords: list, replace_word: str) -> str:
199
+ """
200
+ 🏢 키워드 기반 기관명 → 치환어로 변경
201
+ """
202
+ for kw in keywords:
203
+ pattern = re.compile(rf'([\s\(\["'‘“]*){re.escape(kw)}([가-힣\s.,;:!?()"'”’]*)', re.IGNORECASE)
204
+ text = pattern.sub(lambda m: m.group(1) + replace_word + m.group(2), text)
205
+ return text
206
+
207
+ def apply_full_masking(text: str, keyword_str: str, replace_word: str):
208
+ # 1. 키워드 치환
209
+ keywords = [k.strip() for k in keyword_str.split(",") if k.strip()]
210
+ text = replace_institution_keywords(text, keywords, replace_word)
211
+
212
+ # 2. 민감정보 + 학교 학과 학년 마스킹
213
+ text = postprocess_sensitive_patterns(text)
214
+ text = mask_school_names(text)
215
+ text = mask_department_names(text)
216
+ text = mask_grade_class(text)
217
+
218
+ # 3. 이름 추출 + 태깅
219
+ names = extract_names(text)
220
+ tagged, mapping = apply_name_tags(text, names)
221
+
222
+ # 4. 파생 표현 확장
223
+ tagged = expand_variation_patterns(tagged, mapping)
224
+ mapping = boost_mapping_from_context(tagged, mapping)
225
+
226
+ # 5. 매핑 출력 정리
227
+ mapping_text = "\n".join([f"{k} → {v}" for k, v in mapping.items()])
228
+ return tagged, mapping_text
229
+
230
+ # UI 실행
231
+ with gr.Blocks() as demo:
232
+ gr.Markdown("🧠 **v5.0 마스킹 통합 시스템** — 키워드, 이름, 개인정보, 학교 마스킹")
233
+ input_text = gr.Textbox(lines=15, label="📄 원문 텍스트")
234
+ keyword_input = gr.Textbox(lines=1, label="기관 키워드 (쉼표로 구분)", value="굿네이버스, 사회복지법인 굿네이버스")
235
+ replace_input = gr.Textbox(lines=1, label="치환할 텍스트", value="우리기관")
236
+ run_button = gr.Button("🚀 실행")
237
+ masked_output = gr.Textbox(lines=15, label="🔐 마스킹 결과")
238
+ mapping_output = gr.Textbox(lines=10, label="🏷️ 태그 매핑", interactive=False)
239
+ run_button.click(fn=apply_full_masking, inputs=[input_text, keyword_input, replace_input], outputs=[masked_output, mapping_output])
240
+
241
+ demo.launch()
242
+
243
+
244
+