diff --git a/deploy/services/drhider.py b/deploy/services/drhider.py index 4a10a30..ef06685 100644 --- a/deploy/services/drhider.py +++ b/deploy/services/drhider.py @@ -6,7 +6,7 @@ DrHider — обфускация документов (двухпроходна Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение. """ -DRHIDER_VERSION = "0.5" +DRHIDER_VERSION = "0.6" import io import csv import re @@ -40,7 +40,13 @@ ENTITY_PATTERNS: Dict[str, str] = { # Фирмы — обнаружение по шаблону COMPANY_PATTERN = re.compile( - r'(?:ООО|ЗАО|ОАО|АО|ПАО|ИП|ТОО)\s+(?:«[^»]+»|"[^"]+"|[А-ЯA-Z][\w\s\-\.]+)', + r'(?:ООО|ЗАО|ОАО|АО|ПАО|ИП|ТОО)\s+(?:«[^»]+»|"[^"]+"|\u201c[^\u201d]+\u201d|[А-ЯA-Z][\w\-\.]{2,40})', + re.IGNORECASE +) + +# ФИО — Фамилия + инициалы +PERSON_PATTERN = re.compile( + r'\b[А-ЯA-Z][а-яa-z]+\s+[А-ЯA-Z]\.[А-ЯA-Z]\.', re.IGNORECASE ) @@ -335,12 +341,20 @@ class TwoPassObfuscator: generator = ENTITY_GENERATORS.get(entity_type, lambda x: "XXX") self._mapping[original] = generator(original) - # Компании + # Компании (кроме НУБЕС — это Исполнитель) for match in COMPANY_PATTERN.finditer(text): original = match.group(0).strip() if original and original not in self._mapping: + if re.search(r'НУБЕС|NUBES', original, re.IGNORECASE): + continue # Исполнитель — не заменяем self._mapping[original] = generate_company(original) + # ФИО (Фамилия И.О.) + for match in PERSON_PATTERN.finditer(text): + original = match.group(0).strip() + if original and original not in self._mapping: + self._mapping[original] = generate_person(original) + def _scan_llm_ner(self, all_texts: Dict[str, str]): """LLM NER для обнаружения имён и адресов во всех файлах.""" combined = "\n\n---FILE---\n\n".join( @@ -384,19 +398,25 @@ class TwoPassObfuscator: # --- Проход 2: замена --- def _replace_in_docx(self, doc) -> bytes: - """Заменить сущности в docx-документе (in-place).""" - # Замена в параграфах + """Заменить сущности в docx-документе. Склеиваем runs → заменяем → пишем в первый run, остальные очищаем.""" for para in doc.paragraphs: - for run in para.runs: - run.text = self._apply_replacements(run.text) + full_text = "".join(run.text for run in para.runs) + replaced = self._apply_replacements(full_text) + if replaced != full_text and para.runs: + para.runs[0].text = replaced + for run in para.runs[1:]: + run.text = "" - # Замена в таблицах for table in doc.tables: for row in table.rows: for cell in row.cells: for para in cell.paragraphs: - for run in para.runs: - run.text = self._apply_replacements(run.text) + full_text = "".join(run.text for run in para.runs) + replaced = self._apply_replacements(full_text) + if replaced != full_text and para.runs: + para.runs[0].text = replaced + for run in para.runs[1:]: + run.text = "" buf = io.BytesIO() doc.save(buf) diff --git a/site/app.py b/site/app.py index 1dd49ee..510c711 100644 --- a/site/app.py +++ b/site/app.py @@ -133,7 +133,9 @@ def api_drhider(): def _process(): try: - zip_data, _csv = obfuscate_files(files) + from services.llm_client import HttpxLLMClient + llm = HttpxLLMClient(LLM_URL, LLM_KEY, LLM_MODEL) if LLM_KEY else None + zip_data, _csv = obfuscate_files(files, llm_client=llm) with _drhider_lock: _drhider_jobs[job_id] = {"done": True, "data": zip_data, "error": None} except Exception as e: diff --git a/site/services/drhider.py b/site/services/drhider.py index 4a10a30..ef06685 100644 --- a/site/services/drhider.py +++ b/site/services/drhider.py @@ -6,7 +6,7 @@ DrHider — обфускация документов (двухпроходна Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение. """ -DRHIDER_VERSION = "0.5" +DRHIDER_VERSION = "0.6" import io import csv import re @@ -40,7 +40,13 @@ ENTITY_PATTERNS: Dict[str, str] = { # Фирмы — обнаружение по шаблону COMPANY_PATTERN = re.compile( - r'(?:ООО|ЗАО|ОАО|АО|ПАО|ИП|ТОО)\s+(?:«[^»]+»|"[^"]+"|[А-ЯA-Z][\w\s\-\.]+)', + r'(?:ООО|ЗАО|ОАО|АО|ПАО|ИП|ТОО)\s+(?:«[^»]+»|"[^"]+"|\u201c[^\u201d]+\u201d|[А-ЯA-Z][\w\-\.]{2,40})', + re.IGNORECASE +) + +# ФИО — Фамилия + инициалы +PERSON_PATTERN = re.compile( + r'\b[А-ЯA-Z][а-яa-z]+\s+[А-ЯA-Z]\.[А-ЯA-Z]\.', re.IGNORECASE ) @@ -335,12 +341,20 @@ class TwoPassObfuscator: generator = ENTITY_GENERATORS.get(entity_type, lambda x: "XXX") self._mapping[original] = generator(original) - # Компании + # Компании (кроме НУБЕС — это Исполнитель) for match in COMPANY_PATTERN.finditer(text): original = match.group(0).strip() if original and original not in self._mapping: + if re.search(r'НУБЕС|NUBES', original, re.IGNORECASE): + continue # Исполнитель — не заменяем self._mapping[original] = generate_company(original) + # ФИО (Фамилия И.О.) + for match in PERSON_PATTERN.finditer(text): + original = match.group(0).strip() + if original and original not in self._mapping: + self._mapping[original] = generate_person(original) + def _scan_llm_ner(self, all_texts: Dict[str, str]): """LLM NER для обнаружения имён и адресов во всех файлах.""" combined = "\n\n---FILE---\n\n".join( @@ -384,19 +398,25 @@ class TwoPassObfuscator: # --- Проход 2: замена --- def _replace_in_docx(self, doc) -> bytes: - """Заменить сущности в docx-документе (in-place).""" - # Замена в параграфах + """Заменить сущности в docx-документе. Склеиваем runs → заменяем → пишем в первый run, остальные очищаем.""" for para in doc.paragraphs: - for run in para.runs: - run.text = self._apply_replacements(run.text) + full_text = "".join(run.text for run in para.runs) + replaced = self._apply_replacements(full_text) + if replaced != full_text and para.runs: + para.runs[0].text = replaced + for run in para.runs[1:]: + run.text = "" - # Замена в таблицах for table in doc.tables: for row in table.rows: for cell in row.cells: for para in cell.paragraphs: - for run in para.runs: - run.text = self._apply_replacements(run.text) + full_text = "".join(run.text for run in para.runs) + replaced = self._apply_replacements(full_text) + if replaced != full_text and para.runs: + para.runs[0].text = replaced + for run in para.runs[1:]: + run.text = "" buf = io.BytesIO() doc.save(buf) diff --git a/site/templates/drhider.html b/site/templates/drhider.html index 03caa5a..66699f0 100644 --- a/site/templates/drhider.html +++ b/site/templates/drhider.html @@ -64,7 +64,7 @@ Сверка договоров | DrHider - v0.5 + v0.6
diff --git a/site/templates/index.html b/site/templates/index.html index 656c40a..84922a8 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -73,7 +73,7 @@
Nubes - Сверка договоров — LLM AI-driven Event Sourcing v1.0.188-flask + Сверка договоров — LLM AI-driven Event Sourcing v1.0.189-flask
○ Загрузка ○ Классификация