Spaces:
Build error
Build error
Upload 4 files
Browse files
LICENSE
CHANGED
|
@@ -1,5 +1 @@
|
|
| 1 |
-
|
| 2 |
-
본 앱은 개인 및 비영리 목적으로 자유롭게 사용할 수 있으며,
|
| 3 |
-
치환 결과에 대한 법적 책임은 사용자에게 있습니다.
|
| 4 |
-
임의 수정 후 재배포를 금지합니다.
|
| 5 |
-
출처 명시: blueradiance / masking-app
|
|
|
|
| 1 |
+
비영리/개인 내부용. 무단 수정 및 재배포 금지. 출처 명시 필수 (blueradiance / masking-app)
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
emoji: 🛡️
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 4.16.0
|
| 8 |
app_file: app.py
|
|
|
|
| 1 |
---
|
| 2 |
+
title: 마스킹 앱 완전체
|
| 3 |
emoji: 🛡️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 4.16.0
|
| 8 |
app_file: app.py
|
app.py
CHANGED
|
@@ -49,11 +49,41 @@ def refactored_mask_names(original_text, names, start_counter=100):
|
|
| 49 |
counter += 1
|
| 50 |
return masked, mapping
|
| 51 |
|
| 52 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
keywords = [k.strip() for k in keyword_string.split(",") if k.strip()]
|
| 54 |
for kw in keywords:
|
| 55 |
pattern = rf"\b{re.escape(kw)}\b"
|
| 56 |
-
text = re.sub(pattern,
|
| 57 |
text = re.sub(r"(\d{3})-(\d{4})-(\d{4})", r"\1-****-\3", text)
|
| 58 |
text = re.sub(r"(\d{4})년 (\d{1,2})월 (\d{1,2})일", r"19**년 \2월 *일", text)
|
| 59 |
text = re.sub(r"(\d{1,3})번지", r"***번지", text)
|
|
@@ -74,24 +104,36 @@ def final_name_remask_exact_only(text, mapping_dict):
|
|
| 74 |
text = re.sub(pattern, tag, text)
|
| 75 |
return text
|
| 76 |
|
| 77 |
-
def
|
| 78 |
names = extract_names(text)
|
| 79 |
masked, mapping = refactored_mask_names(text, names)
|
| 80 |
-
sanitized = sanitize_sensitive_info(masked, keywords)
|
| 81 |
sanitized = final_name_remask_exact_only(sanitized, mapping)
|
| 82 |
mapping_table = "\n".join([f"{k} → {v}" for k, v in mapping.items()])
|
| 83 |
return sanitized, mapping_table
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
counter += 1
|
| 50 |
return masked, mapping
|
| 51 |
|
| 52 |
+
def to_chosung(text):
|
| 53 |
+
CHOSUNG_LIST = [chr(i) for i in range(0x1100, 0x1113)]
|
| 54 |
+
result = ""
|
| 55 |
+
for ch in text:
|
| 56 |
+
if '가' <= ch <= '힣':
|
| 57 |
+
code = ord(ch) - ord('가')
|
| 58 |
+
cho = code // 588
|
| 59 |
+
result += CHOSUNG_LIST[cho]
|
| 60 |
+
else:
|
| 61 |
+
result += ch
|
| 62 |
+
return result
|
| 63 |
+
|
| 64 |
+
def mask_school_names(text):
|
| 65 |
+
school_patterns = [
|
| 66 |
+
(r"(\b[가-힣]{2,20})(초등학교|중학교|고등학교)", True),
|
| 67 |
+
(r"(\b[가-힣]{2,20})\s(초등학교|중학교|고등학교)", False),
|
| 68 |
+
]
|
| 69 |
+
for pattern, attach in school_patterns:
|
| 70 |
+
text = re.sub(pattern, lambda m: to_chosung(m.group(1)) + (" " if not attach else "") + m.group(2), text)
|
| 71 |
+
return text
|
| 72 |
+
|
| 73 |
+
def mask_department(text):
|
| 74 |
+
text = re.sub(r"([가-힣]{2,20}학과)", lambda m: to_chosung(m.group(1)[:-2]) + "학과", text)
|
| 75 |
+
text = re.sub(r"([가-힣]{2,20})과", lambda m: to_chosung(m.group(1)) + "과" if not re.search(r"정신|의무|외래", m.group(1)) else m.group(0), text)
|
| 76 |
+
return text
|
| 77 |
+
|
| 78 |
+
def sanitize_sensitive_info(text, keyword_string, replace_word):
|
| 79 |
+
text = mask_school_names(text)
|
| 80 |
+
text = mask_department(text)
|
| 81 |
+
text = re.sub(r"(\d)학년\s?(\d)반", r"*학년 *반", text)
|
| 82 |
+
|
| 83 |
keywords = [k.strip() for k in keyword_string.split(",") if k.strip()]
|
| 84 |
for kw in keywords:
|
| 85 |
pattern = rf"\b{re.escape(kw)}\b"
|
| 86 |
+
text = re.sub(pattern, replace_word, text, flags=re.IGNORECASE)
|
| 87 |
text = re.sub(r"(\d{3})-(\d{4})-(\d{4})", r"\1-****-\3", text)
|
| 88 |
text = re.sub(r"(\d{4})년 (\d{1,2})월 (\d{1,2})일", r"19**년 \2월 *일", text)
|
| 89 |
text = re.sub(r"(\d{1,3})번지", r"***번지", text)
|
|
|
|
| 104 |
text = re.sub(pattern, tag, text)
|
| 105 |
return text
|
| 106 |
|
| 107 |
+
def apply_masking(text, keywords, replace_word):
|
| 108 |
names = extract_names(text)
|
| 109 |
masked, mapping = refactored_mask_names(text, names)
|
| 110 |
+
sanitized = sanitize_sensitive_info(masked, keywords, replace_word)
|
| 111 |
sanitized = final_name_remask_exact_only(sanitized, mapping)
|
| 112 |
mapping_table = "\n".join([f"{k} → {v}" for k, v in mapping.items()])
|
| 113 |
return sanitized, mapping_table
|
| 114 |
|
| 115 |
+
def remask_with_mapping(text, mapping_string):
|
| 116 |
+
mapping = {}
|
| 117 |
+
for line in mapping_string.strip().split("\n"):
|
| 118 |
+
if "→" in line:
|
| 119 |
+
tag, name = line.split("→")
|
| 120 |
+
mapping[tag.strip()] = name.strip()
|
| 121 |
+
for tag, name in mapping.items():
|
| 122 |
+
pattern = rf'(?<![\w가-힣]){re.escape(name)}(?![\w가-힣])'
|
| 123 |
+
text = re.sub(pattern, tag, text)
|
| 124 |
+
return text
|
| 125 |
+
|
| 126 |
+
with gr.Blocks() as demo:
|
| 127 |
+
gr.Markdown("🛡️ 민감정보 마스킹 [땡땡이 마스킹] : 이름 + 민감정보 + 초/중/고 마스킹기 (초성 기반)")
|
| 128 |
+
input_text = gr.Textbox(lines=15, label="📥 원본 텍스트 입력")
|
| 129 |
+
keyword_input = gr.Textbox(lines=1, label="기관 키워드 (쉼표로 구분)", value="굿네이버스, good neighbors, gn, 사회복지법인 굿네이버스")
|
| 130 |
+
replace_input = gr.Textbox(lines=1, label="치환할 텍스트", value="우리기관")
|
| 131 |
+
run_button = gr.Button("🚀 마스킹 실행")
|
| 132 |
+
masked_output = gr.Textbox(lines=15, label="🔐 마스킹된 텍스트")
|
| 133 |
+
mapping_output = gr.Textbox(lines=10, label="🏷️ 이름 태그 매핑 (수정 가능)", interactive=True)
|
| 134 |
+
reapply_button = gr.Button("🔁 매핑 재반영")
|
| 135 |
+
|
| 136 |
+
run_button.click(fn=apply_masking, inputs=[input_text, keyword_input, replace_input], outputs=[masked_output, mapping_output])
|
| 137 |
+
reapply_button.click(fn=remask_with_mapping, inputs=[masked_output, mapping_output], outputs=masked_output)
|
| 138 |
+
|
| 139 |
+
demo.launch()
|
requirements.txt
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
-
gradio
|
| 2 |
torch
|
| 3 |
transformers
|
|
|
|
| 1 |
+
gradio
|
| 2 |
torch
|
| 3 |
transformers
|