Журнал изменений
0.8.1
2026-08-11- Исправления ошибок.
0.8.0
2026-08-11- Название стороны из одного слова или аббревиатура, которую документ вводит сам, теперь скрывается по всему тексту — если документ хотя бы раз называет её полностью:
NexoraPay, LLCпокрывает и последующиеNexoraPay, иSIGNED BY NEXORA, аthe American Arbitration Association (AAA)покрываетAAA, всё под одним токеном. - Термины договора остаются читаемыми:
Company,Party,DPAи другие определённые обозначения не скрываются, а бренд, ни разу не названный полностью, остаётся видимым. - Ручную маску можно применить сразу ко всем вхождениям: выделите текст и отметьте галочку — она показывает, сколько мест изменится. Без галочки маска остаётся ровно там, где её поставили.
- Ручная маскировка работает во всех форматах, включая PDF, и в PDF закрываются только выделенные символы, а не строка целиком.
- Попап показывает, сколько вхождений уже скрыто — включая маски самой программы, — и предлагает только оставшиеся места.
- Снятие маски касается только своего токена, а подсказка «Источник» считает все места, где имя действительно скрыто.
- Число с валютой остаётся числом: если распознана компания
3M, записиUSD 3Mи$ 3Mне трогаются.
0.7.0
2026-07-30- Денежные суммы можно скрывать. Новый переключатель в настройках, по умолчанию выключен; с ним
$3,500,000.00превращается в$[AMOUNT_1], а360 000 руб.— в[AMOUNT_1] руб. - Валютная пометка остаётся читаемой, поэтому в копии видно, что скрыта именно сумма, а не безымянное число.
- Число без валюты не трогается:
2022 - 360 000, номера счетов и договоров читаются как раньше, с переключателем и без. - Диапазон скрывается целиком:
$1,500-2,000превращается в$[AMOUNT_1]. - Суммы с любым разделителем тысяч, который встречается в договорах, скрываются целиком — точка, запятая, апостроф, любой пробел.
- Валюта, которую программа не покрывает, и запись, которую она не может прочитать целиком, остаются видимыми, и документ об этом предупреждает, а не скрывает половину.
0.6.3
2026-07-28- Веб-адреса маскируются: сайт компании и ссылка на профиль опознают сторону, а
linkedin.com/in/…содержит имя прямо в ссылке. - Испанские и мексиканские адреса находятся по форме, а не только когда модель знает улицу —
Calle Amapola 347,C/ Mayor, 12,CALLE AMAPOLA 347B. - Домашний адрес исполнителя теперь маскируется наравне с адресом компании.
- В таблицах маскировка больше не перевёрнута: заголовок
Colonia:остаётся читаемым, название района рядом с ним скрывается.
0.6.2
2026-07-28- Названия компаний с юридической формой словами маскируются одним фрагментом:
"PAK DESARROLLOS", SOCIEDAD ANÓNIMA DE CAPITAL VARIABLE. - Стандартный зачин мексиканского договора — город, дата, сторона — больше не «съедает» компанию датой.
- Обычные испанские слова больше не маскируются как даты:
suчиталось как воскресенье,mar— как март. - В английских датах сохраняется день в формах
15th March 2026и15, March 2026. - Спан даты больше не выходит за границу предложения.
0.6.1
2026-07-14- Поддержан итальянский: имена, организации, адреса, текстовые даты и телефоны.
- Маскируются итальянские идентификаторы с метками: codice fiscale (включая
C.F./cod. fisc.), Partita IVA, кадастровые ссылки. - Модель
it_core_news_lgустанавливается по явному запросу. - Нативное приложение для macOS (Apple Silicon): подписанный и нотаризованный DMG, языковые модели по требованию, автообновления.
- Обработка в приложении полностью локальная: документы не покидают компьютер.
0.6.0
2026-07-09- Новые форматы: обычный текст (
.txt), Markdown (.md), CSV, сохранённые веб-страницы и HTML, RTF. - Анонимизированная копия RTF выдаётся как
.docx. - Текстовые файлы сохраняют исходную кодировку, включая устаревшую Windows-1251.
- Криптоданные маскируются токенами
[CRYPTO_N]: адреса кошельков (Bitcoin, Ethereum, Tron и другие), хэши транзакций, приватные ключи (WIF, xprv). - Существующие форматы и языки не изменились.
0.5.1
2026-07-06- Настройки → Языки показывают то, что действительно установлено: после переустановки приложения ранее загруженный язык больше не висит с подсказкой «перезапустите для активации», когда модели уже нет.
- Для такого языка снова предлагается загрузка.
0.5.0
2026-07-03- Поддержаны немецкие документы. Модель устанавливается при установке или из Настройки → Языки.
- В немецком маскируются: имена (включая частицы von/zu и академические титулы), компании с немецкими юридическими формами (GmbH, AG, GmbH & Co. KG …), адреса с PLZ, немецкие форматы телефонов, даты словами (
15. Januar 2024). - Детекция email, IBAN и дат не изменилась.
- Немецкий полностью опционален: без установки модели ничего не меняется. Русский, английский и испанский не изменились.
0.4.3
2026-06-30- Внутренний архитектурный релиз: детекция имён и компаний по языкам полностью переехала в языковые паки, поэтому новый язык добавляется без правок ядра.
- Поведение детекции и результат обработки не изменились.
0.4.2
2026-06-29- Локальный веб-интерфейс отклоняет запросы с нелокальным заголовком Host (защита от DNS-rebinding).
- Большие таблицы быстрее разрешают перекрывающиеся находки.
- Поведение детекции и результат обработки не изменились.
0.4.1
2026-06-18- Детекция дат в русских документах точнее. Больше не маскируются как даты: десятичные числа с запятой (
14,7), отдельноегод/годабез года рядом, повисший предлог после года (2022 год в …). - Настоящие даты, включая одиночные годы вида
2022, не затронуты.
0.4.0
2026-06-10- Распознаются и маскируются мексиканские идентификаторы: RFC, CURP, счета CLABE.
- Испанские даты словами маскируются целиком:
15 de julio de 2025, без остатков. - Проценты по платежам больше не принимаются за даты.
- Улицы с датой в названии остаются частью адреса:
Calle 5 de Mayo. - Названия компаний, которые модель раньше пропускала, маскируются одним фрагментом:
GreenLeaf Organics Corp., S.A. de C.V. - Русские и английские документы не затронуты.
0.3.0
2026-06-09- Испанский доступен как дополнительный язык детекции. Модель ставится при установке или из Настройки → Языки, затем нужен перезапуск.
- В испанском маскируются: имена (включая двойные фамилии), компании, адреса.
- Детекция email, телефонов, IBAN и дат не изменилась.
- Русский и английский не изменились, испанский полностью опционален: без установки модели ничего не меняется.
0.2.32
2026-06-08- Убран одноразовый баннер первого запуска.
- Меню языка в верхней панели закрывается по клику вне него и по Esc.
0.2.31
2026-06-02- Надёжнее находятся повторяющиеся даты.
- Надёжнее находятся номера договоров со строчными буквами.
- Надёжнее находятся русские фамилии через дефис.
- Надёжнее находятся номера карт и IBAN с необычными пробелами или дефисами.
0.2.30
2026-05-29Changed
- Webapp: documents with
risky/unsafestructures (e.g. DOCX custom XML parts or bookmark names containing PII) are no longer blocked from download by default. Instead, the download button carries an amber warning indicator and a tooltip explaining that some structures were not anonymized, leaving the final decision to the reviewing lawyer. A second download button is now available at the top of the preview (status row). Hard blocking still applies when the app is started in strict mode (anonymize serve --strict); in that case/download/{doc_id}returns a readable HTML page instead of a JSON error that the browser would otherwise save as a cryptic file. - Webapp: the warning banner now lists human-readable descriptions instead of
raw warning codes (e.g. "custom XML parts are not anonymized" instead of
DOCX_CUSTOMXML_UNSUPPORTED), with localized ru/en strings for every DOCX/XLSX/PDF coverage warning and a graceful fallback for unknown codes. - Webapp: toggling strict mode in Settings now updates the download button and
block notice instantly via an HX-Trigger re-render, without re-processing the
document, and the choice is persisted to
~/.anonymizer/settings.jsonso it survives restarts (overridable viaANONYMIZER_SETTINGS_PATH). A CLI--strictflag still forces strict on for that run.
0.2.29
2026-05-28Fixed
- Installer UX: declining optional Tesseract OCR no longer leaves a confusing
OCR warning in the post-install doctor check. The installer now labels the
slow install/first-run verification steps and ends with the command to start
the app. The POSIX installer is also explicitly
sh-compatible for the publishedcurl ... | shentrypoint.
0.2.28
2026-05-28- Сканированные и гибридные PDF анонимизируются локально, если установлен Tesseract OCR с английским и русским паками.
- Приложение предупреждает о низкой уверенности OCR и о встроенных изображениях, требующих ручной проверки.
- Установщик и команда
doctorпомогают проверить или настроить OCR. - Точнее находятся юридические идентификаторы, фрагменты гонконгских адресов, названия компаний и международные разделители в телефонах.
0.2.22
2026-05-26Fixed
- Preview token-sources tooltip no longer disappears when the pointer crosses the gap from the token to the popup; it stays open for 220 ms so the user can reach it and click "remove mask". Crossing into a different token closes any stale popups. The host element is also taken out of the paragraph flow so its injected fragment never affects the spacing of surrounding text.
0.2.21
2026-05-26- Панели исходника и результата в предпросмотре можно менять по размеру, сворачивать и прокручивать по горизонтали для широких таблиц.
- Фрагменты с KPI, гонорарами, целями и периодичностью остаются видимыми, когда это не настоящие даты.
0.2.20
2026-05-26- Точнее обрабатываются русские отчётные документы с диапазонами дат и строками сумм, похожими на телефоны.
- Значения вида
24-28.02.2022,13.10.2022и2022 - 360 000остаются видимыми, если это не телефоны. - Настоящие телефоны по-прежнему маскируются.
0.2.19
2026-05-24- Внутренние проверки покрытия строже: критические категории сущностей держатся на требуемом уровне приоритета.
- Закрытые регрессии проверяются против ровно той синтетической сущности, которая имелась в виду.
0.2.18
2026-05-22- Русские имена: детектор больше не захватывает префикс К(Ф)Х в персону. В документах вида «Глава К(Ф)Х Иванов И.И.» маскируется только имя, а К(Ф)Х остаётся в тексте как обозначение крестьянского (фермерского) хозяйства.
- Обработка документов: редкие нестандартные случаи теперь обрабатываются безопаснее и точнее, снижая риск лишней маскировки или нестабильного результата в документах со сложной структурой.
0.2.17
2026-05-21- Исправлено падение только на Windows при документах с названиями компаний: словарь юридических форм не читался на русской Windows (кодовая страница cp1251) с
UnicodeDecodeError. - Файл теперь читается как UTF-8 независимо от системной локали.
0.2.16
2026-05-21Google Looker Studioбольше не маскируется как название контрагента в маркетинговых и adtech-контекстах — так же, как уже работала форма без префикса,Looker Studio.
0.2.15
2026-05-21- Таблицы (блоки подписей, реестры, графики) показываются в предпросмотре как таблицы, а не плоским списком абзацев — результат можно проверять построчно.
- Серии актов ЗАГС вида
II-МО/I-МКмаскируются как NUMBER; раньше они доходили до выходного DOCX, хотя номер свидетельства маскировался.
0.2.14
2026-05-20- DOCX с таблицами (блоки подписей, реестры, графики) обрабатываются корректно: из-за ошибки дедупликации в парсере большая часть ячеек крупных таблиц могла молча пропадать, и персональные данные проходили нетронутыми.
- Веб-приложение само завершается при закрытии вкладки браузера.
- Прогресс загрузки начинается сразу после переноса файла, со видимыми стадиями вместо ложного состояния ошибки.
0.2.13
2026-05-20- Индикатор прогресса по стадиям анимируется во время обработки; раньше все стадии приходили одним обновлением в конце, и маленькие документы прыгали сразу к результату.
0.2.12
2026-05-20- Индикатор прогресса остаётся видимым на всех стадиях обработки, в том числе для небольших документов, которые завершаются быстро.
- Ложный баннер «ошибка» больше не появляется при медленной загрузке страницы, пока обработка ещё идёт.
0.2.11
2026-05-20- PDF с заранее размеченными областями редактирования обрабатываются безопасно: закрытое содержимое вычищается полностью, а встроенный подменный текст в этих областях удаляется до анонимизации.
0.2.10
2026-05-20- В пилотных документах маскируются русские банковские счета, СНИЛС и ИНН.
- Государственные регуляторы и санкционные органы остаются читаемыми, чтобы санкционные оговорки не теряли смысл.
- Появилась офлайн-команда диагностики, которая сообщает о состоянии установки.
- Носители документа и диагностические логи больше не хранят исходный текст сущностей после анонимизации.
0.2.9
2026-05-19Added
passport_divisiondetector — context-anchored Russian regex that recognisesNNN-NNNpassport division codes (код подразделения) asNUMBERtokens. Anchored onкод подразделения/подразделение/выданso bareNNN-NNNsequences (product SKUs, tariff codes) are not hijacked. Registered with conflict precedence95— same tier aspassport_ru— so the code wins overcompany_rueven before the post-filter below drops the ORG span. Closes Fix #20 of the post-0.2.3 detector-improvement plan.AnonymizerConfig.mask_countries(defaultFalse) — opt-in flag for masking country names. In legal documents the country of citizenship is typically NOT what lawyers want masked (it provides useful jurisdiction context, never identifies anyone uniquely), so country GPE spans likeРоссия/Российская Федерация/Англия/Уэльс/United Kingdom/USAare dropped fromaddress_neroutput by default. Set the flag toTruefor paranoia workflows that want everything geographic anonymised. Closes Fix #19. PR #19 review pass: the country opt-in now runs before the noisy-location filter, so single-word inflected country forms ending in "е" (Уэльсе/Королевстве) are not eaten by the bare-prepositional RU LOC filter whenmask_countries=True.
Fixed
- DATE (
date) detector now drops Russian<spelled-number> (digit)legal phrasing as a false positive. The_DATEPARSER_SPELLED_NUMBERpattern previously listed only English cardinals (one..ninety), so the equivalent Russian forms used in contracts —в течение трёх (3) лет,в течение пяти (5) рабочих дней,в двух (2) экземплярах,десяти (10) дней,тридцать (30) календарных дней— were misclassified as DATE bydateparser.search. The extended regex enumerates the main inflected forms of one .. ninety (nominative + most common oblique cases — RU nominativeодинadded in PR #20 review pass) so the FP guard fires for both languages. The guard also no longer over-fires: a real long-form RU/EN date that sits next to a duration phrase (14 октября 2025 года ... в течение трёх (3) лет) survives because the spelled-number filter now only triggers whenrawlacks a month name. Surfaced on Рус_ДКП-2025.docx, where 5 such phrases were tagged as DATE; pipeline now emits 6 dates (the real ones) on that document. Closes Fix #17. - ADDRESS (
address_ner) detector now drops Natasha LOC/GPE/FAC spans that consist only of Russian contract / boilerplate vocabulary. Adds a_RU_LOC_STOP_TOKENSset covering contract roles (покупатель,продавец,сторона), structural terms (договор,запись,приложение) and Natasha noise tokens from ALL-CAPS section headers (что,включено,указанные,кроме,соответствующая,незамедлительно,исключается). Prepositions / conjunctions are stripped before the check so multi-word noise spans like"соответствующая Запись незамедлительно исключается из"are caught. The stop-list now runs against ANY language pack's NER output (not justru), because spaCy-EN occasionally emits Cyrillic noise on mixed-script blocks too. On the pilot Рус_ДКП-2025.docx this cutaddress_neroutput from 47 → 10 entries (only legitimate regions / raions remain). Closes Fix #15. - PERSON (
person_ru) detector now drops Natasha PER spans whose tokens are all contract-role labels (Продавца,Покупателю,Стороны). The set is symmetric with the address / company stop-lists. Closes Fix #16. - COMPANY (
company_ru) detector now drops Natasha ORG spans that consist exclusively of generic Russian contract / boilerplate vocabulary (СТОРОНЫ,СТОРОН,СПОРОВ,ГАРАНТИИ,ПОДПИСИ СТОРОН,СО СМЕРТЬЮ,ЕДИНОГО ГОСУДАРСТВЕННОГО, standaloneБанк,Стороны предпринимают, …). All inflected forms of each base word are enumerated in_RU_CONTRACT_TERMS; short prepositions and conjunctions are stripped before the check so mixed-case spans likeСО СМЕРТЬЮare caught. Multi-word organisations that contain a contract-vocab token but also non-boilerplate words (Совета Безопасности,Лондонский международный арбитражный суд) survive. Surfaced on the real pilot documentРус_ДКП-2025.docx, where Natasha tagged 11 such uppercase section headers and standalone party words as COMPANY. Closes Fix #13 of the post-0.2.3 detector-improvement plan. - COMPANY (
company_ru) detector now drops bareNNN-NNNspans that Natasha occasionally emits as ORG (e.g.503-024,230-001— passport-division codes). The newpassport_divisiondetector re-classifies them asNUMBER; this filter is a belt-and-braces guard so an ORG span never leaks into the token mapping even if the regex anchor misses. Closes Fix #14. - ADDRESS (
address) detector now recognises Russian addresses that start with the region / district / village block rather than theг.|городcity prefix, e.g.Московская область, Клинский район, д. Коробочкино, ул. Помещичья, д. 1. The new_RU_REGION_VILLAGE_STREET_RErequires a region marker (ская область/ий край/Республика <name>) plus a district marker (ский|ой|ого район/р-н) plus a village marker (д./деревня/с./село/пос./посёлок/х./хутор) so it only fires on real addresses and does not hijack arbitrary region mentions. Optional street + house suffixes consume the rest of the address greedily on a comma boundary. Before this fix the city-anchored regex missed the entire span and only the region/district survived as separateaddress_nerLOC spans — the village and the street stayed unmasked. Surfaced on the pilot Рус_ДКП-2025.docx; closes Fix #18.
0.2.8
2026-05-19Fixed
uv tool upgradenow uses the correct flag. The 0.2.6 hotfix added--refresh-package docs-anonymizerto bothanonymizer.updaterand theanonymize updateCLI, but that flag only exists onuv tool install—uv tool upgraderejects it withunexpected argument '--refresh-package' found. Every in-UI [Update] click on 0.2.6 / 0.2.7 wrote the rejection into~/.anonymizer/last_update_failure.jsonand the next start surfaced the 🟡 "Не удалось обновить" pill instead of the upgrade banner. Both call sites now pass--reinstall-package docs-anonymizerwhich implies--refresh-packageperuv tool upgrade --help.current_statusno longer hides a fresh update banner behind theupdate_failedpill. Previously a single failed upgrade attempt routed every subsequent request intostate="update_failed"even when a newer release was already on PyPI — exactly the case where the user most wants to retry. The failed pill still shows when the probe sees no newer release.
0.2.7
2026-05-19Changed
- Update banner UX overhaul:
- Topbar pill and
v<version>chip now hide while the banner is on screen — they duplicated the version pair the banner already shows ("v0.2.5 → 0.2.6"). - Banner text is now
v<current> → <latest> · Release notes, withRelease noteslinking to the canonicalnotes_urlfromversion.jsonschema v1. The previous template fell back tonetwork.bannerwheneverchangelogwas missing, printing the same text twice.
- Topbar pill and
Fixed
POST /update/applynow returns an HTML overlay instead of a JSON payload. htmx happily swapped the raw{"status":"shutting_down","message":"Updater spawned; this process will exit."}blob into the banner before. New overlay tells the user "Update started — close this tab, the new version opens in a fresh tab in a few seconds" and renders full-screen._read_update_failurenow drops stalelast_update_failure.jsonentries whosereasonreferences the pre-0.2.0anonymizerdistribution name (Caused by: \`anonymizer\` is not installed). These leftovers silently routedcurrent_statusintooffline_statusand kept the update banner hidden after thedocs-anonymizerrename. Pilot install on 0.2.5 hit this.
0.2.6
2026-05-19Added
anonymize updateCLI subcommand. Synchronous wrapper arounduv tool upgrade --refresh-package docs-anonymizer docs-anonymizerfor shell users who don't want to round-trip through the in-UI [Update] banner. Returns the upgrade exit code so it composes with shell scripts.
Fixed
- All upgrade paths now pass
--refresh-package docs-anonymizerto uv. Without it--force --reinstallandtool upgradehappily reinstall the same cached old version that PyPI's Simple API has already superseded — a fresh release can sit on PyPI for an hour while every install still resolves the previous version locally. Updated:installer/install.shandinstaller/install.ps1- the in-UI
anonymizer.updaterdetached process
0.2.5
2026-05-19Fixed
- In-UI update banner now actually fires when a newer version is
published.
anonymizer.webapp.network_status.probewas reading the non-existentlatestkey fromversion.jsonand silently flipping every probe intooffline_statusbecauseVersion("")raisedInvalidVersion. The canonicalhttps://anonymizer.site/version.jsonschema v1 exposes the key asversion(alongsidesha256/url/released_at/schema_version/notes_url); probe now readsversionwithlatestkept as a backward-compat fallback for older internal mirrors. Surfaced by pilot install on 0.2.3 not seeing the banner after the site published 0.2.4. Thetest_network_statussuite now mirrors the real site payload (full v1 keys) so this exact regression can't slip through unit tests again.
0.2.4
2026-05-19Added
- Vendor-neutral agent workflow infrastructure under
.agents/skillsso Claude Code, Codex, and generic agents share one canonical workflow source..claude/skillsentries are now compatibility shims that delegate to the canonical files, with adapter notes for Claude, Codex, and generic agents.
Fixed
- Agent workflow consistency tests now validate YAML frontmatter with
yaml.safe_load. This prevents invalid multilinedescriptionfields from silently breaking skill discovery while still keeping Claude shims and canonical.agentsskills in sync.
0.2.3
2026-05-19Fixed
Add
setuptools<81as a hard runtime dependency in[project] dependencies. Without the runtime pin,uv tool install docs-anonymizerend-user environments resolved the latest setuptools (>=81), which dropped the bundledpkg_resourcespackage; the first RU NER call then crashed with::File ".../pymorphy2/analyzer.py", line 114, in _iter_entry_points import pkg_resources ModuleNotFoundError: No module named 'pkg_resources'Surfaced in the pilot run on the first real RU
.docxdocument. The existing[tool.uv] constraint-dependenciesline was a uv-only override and did not propagate into the published wheel metadata. Lifting this pin requires upgrading natasha to a release that uses pymorphy3.
0.2.2
2026-05-19Added
anonymize --version(andanonymize -V) flag that printsanonymize <version>and exits. Previously the installer scripts' post-installanonymize --versionsmoke check failed because the flag did not exist.
Fixed
- Installer scripts (
installer/install.sh,installer/install.ps1, and the same pair hosted onanonymizer.site) now pin the tool environment to Python 3.12 viauv tool install --python 3.12. Without the pin uv used the user's default interpreter, which on a fresh macOS / Linux box with uv 0.9.x is 3.14 — the upper boundrequires-python = ">=3.11,<3.13"added in 0.2.1 was not enough becauseuv tool installselects the interpreter before resolving the package and only validates the bound at resolve time, which is too late: a tool environment built on 3.14 still attempts to compileblis 0.7.11from sdist (no cp313/cp314 wheels) and fails on the removed CPython internals. docs/installation.mdrecovery command updated to include the--python 3.12pin and the--withmodel URL.
0.2.1
2026-05-19Fixed
requires-pythonconstrained to>=3.11,<3.13.blis 0.7.11(transitive viaspacy 3.7→thinc 8.2) only publishes wheels for cp310..cp312; on Python 3.13 / 3.14uv tool install docs-anonymizerpreviously fell back to buildingblisfrom sdist and crashed against the CPython 3.14 C API (deleted_PyDict_SetItem_KnownHash, new_PyLong_AsByteArraysignature, etc.). Reported during pilot install on a machine where uv picked up its uv-managed Python 3.14. Lifting the upper bound requires a coordinated bump tospacy >= 3.8and a refresheden_core_web_lgmodel wheel.
0.2.0
2026-05-19First public release on PyPI under the neutral distribution name
docs-anonymizer (the Python import name remains anonymizer). Version
stays sub-1.0 because the product is still in pilot stage; promotion to
1.0.0 will follow once pilot feedback is incorporated. The previous
internal 1.0.0 tag (built against name = "anonymizer") was never
published to PyPI and is kept only as a historical marker. The reservation
stub at docs-anonymizer 0.0.1 is superseded by this release.
Detection metrics snapshot
Systematic 62-document corpus (60 synthetic testkit generate --count 60 --seed 42
- 2
golden_partiales/it) after the Tier P0/P1 fix queue below. Baseline was overall recall 0.886 / precision 0.679 / F1 0.769; reproduce viascripts/analyze_corpus.py --corpus /tmp/corpus_systematic --corpus tests/integration/fixtures/golden_partial.
| Type | Recall | Precision | F1 | Baseline F1 |
|---|---|---|---|---|
| 1.000 | 1.000 | 1.000 | 1.000 | |
| NUMBER | 1.000 | 1.000 | 1.000 | 0.870 |
| PHONE | 1.000 | 1.000 | 1.000 | 0.850 |
| DATE | 1.000 | 1.000 | 1.000 | 0.790 |
| PERSON | 1.000 | 0.963 | 0.981 | 0.750 |
| COMPANY | 1.000 | 0.992 | 0.996 | 0.710 |
| ADDRESS | 1.000 | 0.990 | 0.995 | 0.400 |
| Overall | 1.000 | 0.991 | 0.996 | 0.769 |
Residual FP (~7 entries) concentrate in PDF parsing artifacts (e.g.
Cardiff, ЛюбовьМихайловна — ligature/glue from the parser layer, not
detector logic) and are tracked separately in the detector-improvements
plan as Fix #12.
Added
- Sprint 7: Feedback hardening + agent infrastructure —
anonymize testkit analyze-feedbackaggregates PII-free feedback logs into a ranked HTML report; feedback analyzer unit/integration coverage exercises the analyze/reproduce/improve/score loop on synthetic data; agent docs now point at current code paths; local.claude/skills/*runbooks were finalized for feedback triage, known-failure reproduction, detector improvement, regression checking, and patch release;docs/it-policy.md,docs/feedback.md, anddocs/sprint-7-pilot-runbook.mddocument the operational close-out process. - Sprint 6: Webapp UI redesign — full UI per
docs/superpowers/specs/2026-05-18-anonymizer-ui-design.md:- Single-page workspace (replaces 3-screen wizard from tech-spec §9)
- shadcn-style visual language in plain CSS (no React, no node)
- Side-by-side preview with hover tooltips, pair highlight, sync scroll
- Manual mask via text selection → token type dropdown
- Unmask flow with
detector_overreachPII-free feedback event - Stage-based processing UI ("Reading file" → "Searching PII" → …)
- Warning banner + error states (unsupported format / corrupted / crash)
- Settings modal: detector toggles, strict-mode, open-logs-folder
- Feedback modal with sample/context fields
- Network status pill (4 states) + update banner template
- i18n: Russian (extended) + English UI translations via Babel + .po
- Language picker in topbar (
/session/langendpoint)
Fixed
- DATE detector now drops bare numeric spans that lack any year/month signal
in the surrounding 48-character window.
dateparserpreviously tagged fragments like7/10,1500, orfrom 9/10as DATE entities even when the document used them as ratios, amounts, or unit references; the new_has_date_contextguard requires either a 4-digit19xx/20xxyear or an English/Russian month name nearby. Closes Fix #7 of the systematic corpus plan. - ADDRESS detector now recognises Russian prospect abbreviations written as
пр-тand postfixed street-type forms such asНевский пр.andМалый Знаменский пер.in structuredг. <City>, <Street>, д. <House>addresses. Surfaced by the systematic testkit corpus plan. - ADDRESS detector now recognises numbered UK postcode addresses without an
explicit street type, alphanumeric UK building numbers such as
221B, and Russian implicit street forms such asг. Москва, Вавилова, д. 19. Structured address spans now outrank contained date-like fragments, so house numbers like7/10no longer displace the full address. - COMPANY detector now recognises Russian legal-form prefixes
АНО,АО,ЗАО,ПАО,ОАО,ОООwith quoted names andИП Фамилия И.О.patterns, covering misses from the systematic testkit corpus plan. - COMPANY legal-form regex spans now use a dedicated conflict precedence so
ИП Смирнов А.А.and quoted Russian legal entities survive containedPERSONspans and overbroad NER spans with adjacent taxpayer numbers. - COMPANY detector now ignores Cyrillic-only spans produced by the English NER
path in mixed RU contact blocks, and recognises English business-name forms
such as
Thornehill Estates,Cardiff Tech Partners, andIronbridge Logistics Co.from the systematic testkit corpus. - PERSON detector now ignores Cyrillic-only spans produced by the English NER
path in mixed RU/email contact blocks, recognises inverted English names
such as
Evans CharlotteandKing Isabella M., and adds a Russian full-name regex fallback for cases where Natasha splits surname and first-name/patronymic spans. Follow-up filters keep standalone two-word business phrases and Russian institutional title-case phrases out of PERSON. English COMPANY NER now drops unvalidated one-token ORG spans such asEvansthat can displace full person names. - ADDRESS NER fallback now drops noisy single-word Russian locative city
mentions such as
в г. Москве, while structuredг. <City>, <Street>address regex hits remain covered. Phone detection now scans RU, GB, and US national formats with span deduplication, and generated testkit manifests enumerate raw phone/passport surfaces instead of only canonical values. - COMPANY NER post-processing now drops unvalidated one-token spans, reducing
false positives such as
DOB,ДР,Evans, and standalone legal-form abbreviations while preserving legal-suffix and legal-form regex matches. - Contract-number detection no longer treats school/building identifiers such
as
Школа №57as contract numbers, preventing nestedNUMBERspans from displacing full company-name detections. - kf-0001: Russian passport detection now handles the
№sign separator between series and number (extendedpassport_ruregex). Surfaced while annotatingРус_ДКП-2025.docxfor golden_real corpus. - kf-0002: ADDRESS detector recognises UK-format addresses (
<number> <street> <Square|Crescent|...>, <City> <UK postcode>) and Russian-format addresses (г. <City>, ул. <Street>, д. <House>, кв. <Unit>, including hyphenated compound city names like "Санкт-Петербург"). Conflict-resolution precedence for structuredaddressregex raised abovecompany_en/company_ru, while NER fallback now uses a separateaddress_nerdetector name with lower precedence so single-token LOC spans like "London" cannot evict legitimate company spans like "Acme London Ltd". The RU regex street group is greedy (previously lazy matched only the minimum 2 chars, leaving most of the address unmasked). Passport№separator handles the no-space variant (45 23№786140). On the 12-doc synthetic corpus this lifted ADDRESS recall 0.29 → 0.54, precision 0.23 → 0.48, F1 0.25 → 0.51; overall recall 0.886 → 0.922.
1.0.0
2026-05-18Added
- Repository skeleton:
src/anonymizer/package layout per tech-spec §2. - Tooling:
pyproject.tomlwith uv, ruff, pyright, pytest configuration. - CI:
pr.yml,main.yml,release.ymlworkflows per tech-spec §16. - Binary guard:
tests/integration/test_no_network.pyenforces no-network rule foranonymizer.core(tech-spec §2, §22). - Installer skeletons:
installer/install.shandinstaller/install.ps1that fail with a clear message because MVP-0 is not yet shipped. - Sprint 0 complete: skeleton, CI, no-network guard, installer stubs, IT handoff doc. Sprint 1 (core foundations: docx parser/writer + email/phone regex + basic Token Manager) can begin.
- Sprint 1 complete: docx parser (body + headers/footers/footnotes/endnotes; accept-all + remove comments + IRM rejection), docx writer (body/header/footer run replacement plus footnote/endnote XML replacement), EmailDetector (regex) + PhoneDetector (phonenumbers), TokenManager with canonical-key normalization for EMAIL+PHONE,
process_documentorchestrator, and a workinganonymize <file>CLI. Sprint 2 (NER + LanguagePack + xlsx) can begin. - Sprint 2a complete: LanguagePack abstraction with RU + EN packs (Natasha + spaCy NER); lingua-py language hint on ParsedDocument; canonical-key normalizers for PERSON/COMPANY/ADDRESS/DATE/NUMBER/DATA; 5 NER detectors (PersonRu, PersonEn, CompanyRu, CompanyEn, Address). xlsx, P0 regex detectors (Date + INN/OGRN/KPP/SNILS/PassportRu/IBAN/Card/BIC/BICRu/ContractNumber), and conflict resolution land in Sprint 2b.
- Sprint 2b complete: DateDetector + 10 Russian taxpayer/banking regex detectors (INN, OGRN, KPP, SNILS, PassportRu, IBAN, Card, BIC, BICRu, ContractNumber); xlsx parser/writer; conflict resolution per tech-spec §6; pipeline + CLI accept docx/xlsx; integration coverage for mixed docx P0 masking and xlsx token reuse/comment removal/formula preservation. Sprint 3 (PyMuPDF, metadata cleaner, coverage warnings, strict mode) can begin.
- Sprint 3 complete: PDF text-layer parsing + true redaction via PyMuPDF,
metadata cleaners for docx/xlsx/pdf, format-coverage warnings with
informational/risky/unsafe tiers,
anonymize --strict, and PDF end-to-end coverage for redaction, AcroForm cleanup, and signature warnings. - PR #4 review hardening: PDF redaction now uses parser geometry instead of
page-wide text search, PDF coverage scanning moved out of
core.detection, malformed coverage scans fail closed, warning-blocked CLI outputs are removed, and filled PDF form widgets are explicitly removed rather than value-cleared. - Sprint 4 complete: testkit personas (20: 10 ru + 10 en, all INNs
checksum-valid), Generator protocol + DocxGenerator / XlsxGenerator /
PdfGenerator (text/manifest deterministic by seed; raw container bytes may
carry timestamps), metrics (recall/precision/F1) and corpus runner over
docx/xlsx/pdf; corpus-manifest JSONSchema
(
docs/agents/corpus-manifest-schema.yaml) + manifest reader/validator; feedback-log Pydantic models (ManualMaskAdded/ManualUnmask/DocumentSummary) mirroring §20 with contract test;tests/integration/known_failures/skeleton + template + discovery harness;golden_partiales/it corpus with full-precision gate (NER silent, regex tier active);golden_generatedreproducible synthetic corpus (seed=42, 24 docs); §18 warning-code end-to-end coverage viaformat_edge_casesintegration test;anonymize testkit generate / run / list-known-failuresCLI subcommands; hypothesis-driven fuzz on docx pipeline + CI gate activated. Sprint 5 (Web App + Manual mask) can begin. - Sprint 5 complete: local FastAPI + htmx webapp on
127.0.0.1(Upload -> Processing SSE -> Preview -> Download), session-token + CSRF + localhost-only CORS middleware, in-RAM session manager, detector toggles, offline network-status stub, manual-maskManualOverrideanchors for docx/pdf/xlsx materialized as first-class entities with conflict precedence, writer regression coverage formanual_override, PII-free crash/feedback JSONL audit logs,anonymizedefault webapp serve mode plusserve/stop, and integration guards for webapp E2E plus audit no-PII. Sprint 6 (update flow + distribution) can begin. - Sprint 6 release candidate: detached updater process, real webapp
version.jsonprobe with online/update/offline states,POST /update/apply,--no-update-check, finalized install scripts, canonical AGPL-3.0 LICENSE, Sprint 6 release runbook, and acceptance benchmark notes.
Changed
Language detection (
core.language.detection) now recognises Spanish and Italian alongside Russian / English. Documents in es / it surface aslanguage_hint="es"/"it"instead of being silently classified asen. NER detectors (person_ru,person_en,company_*,address) now respect the document-level hint via_document_language_blocks_detectorand stay silent on hint mismatches, keeping the regex tier as the sole acceptance surface on partial-language documents.Testkit runner now preserves nested corpus output paths, rejects ambiguous duplicate-stem manifests, enforces
expected_warnings, and supports multi-corpus CLI runs without writing_out/into fixture directories.Parser exception surface tightened:
DocxParsernow wrapszipfile.BadZipFile,lxml.etree.XMLSyntaxError, anddocx.opc.exceptions.PackageNotFoundErrorintoParseError. The "missingw:body" branch reclassifies fromEncryptedFileErrortoParseError(post-PR-2 review feedback).Pipeline now resolves cross-detector span overlap before token assignment (greedy leftmost-keep with longer-span tiebreaker on shared left edge); contract documented in
pipeline.py. Prepares for Sprint 2 NER landing.Pipeline drops the dead
AssertionErrorbranch ondetect_format: theLiteral["docx"]return type already narrows to docx, anything else raisesUnsupportedFormatErrorfrom within the detector.format_detection.detect_formatreads only the first 8 magic bytes via a stream (path.open("rb").read(8)) instead of loading the full file into memory — avoids unnecessary spikes on large inputs.sanitize_docx_package_bytesnow has a typedsource: bytes | ZipFilesignature (was untyped duck-typing).Note
block_idformat changes from<region>/p_<note_id>_<paragraph_idx>to<region>/p_<note_id>|<paragraph_idx>so the writer can split unambiguously without assuming integer note IDs. The|delimiter is private to footnotes/endnotes block IDs.RunPydantic model markedfrozen=Truefor symmetry withTextSpan/Entity(both already frozen).
Deferred
- Pyright
strictvsstandardrecorded asdocs/questions.mdQ-025 — default proposal: strict forcore/, standard elsewhere; resolve before Sprint 2 NER landing. golden_realhand-annotation for 4 documents fromdocs/examples/— recorded asdocs/questions.mdQ-026. Last remaining Sprint-4 task; needs Legal Lead sign-off and lands before the MVP-0 pilot.
Deprecated
- N/A
Removed
- N/A
Fixed
- DOCX output now persists track-change accept-all/comment removal and masks
PII detected in
word/footnotes.xml/word/endnotes.xml. - Sprint 5 webapp review hardening: manual-mask overrides now reprocess documents end-to-end, session reset emits the new cookie token, upload paths are sanitized, oversized uploads are rejected, and feedback context snippets redact partial entity fragments.
- PDF token replacements now preserve the source text font family and size instead of shrinking short-source replacements into mismatched labels.
- PDF lines touched by wider replacement tokens are rewritten as a line so following text shifts instead of being overprinted by the token.
- Web processing page now surfaces safe parser failures, including scanned PDFs without a text layer, instead of leaving the progress screen spinning.
- English legal PDFs now avoid masking service headings, section references, schedule labels, duration numbers, suite numbers, and BIC-like words as entities; EINs and full street addresses are masked as single spans.
- English DOCX legal agreements now mask
Contract Ref.identifiers, title-case legal-suffix company names, and directional street addresses while preserving role labels, fee headings, business-day ordinals, and routine channel/payment terms. - Sprint 5 PR review follow-up: marketing/adtech brand names are preserved
in service/account contexts, processing SSE reports parse failures before
generic progress, feedback responses no longer expose local log paths, and
anonymize stopverifies the pidfile target before signaling. - English company detection now preserves common privacy and data-protection law titles, including TDPSA / CCPA / GDPR-style act and regulation names.
- English company detection now also preserves common operational SaaS/tool and role/reporting terms such as DocuSign, Asana, CMS, CMO, QBR, and AAA rules.
Security
- Local CORS now pins same-origin requests to the served
127.0.0.1port.
Added (PR #3 — markup-system-foundation)
- Corpus manifest v2 (
anonymizer.testkit.manifest_v2) — Pydantic source of truth with per-entity char offsets, block_id anchors, occurrence tracking, tier classification, and JSONSchema regenerated automatically (scripts/regen_corpus_manifest_schema.py). anonymize annotate {prepare,compile,render}CLI for detector-first manifest authoring and synthetic-doc round-trip.compileaccepts--source {real,synthetic}; synthetic source additionally requires--style-refand--variant(plus optional--seed,--author) which populateManifest.generatordirectly — no manual YAML edit needed.- Runner v2 with overlap-based matching (≥50% rule + non_entities),
CorpusMetricscarrying overall/by_tier/by_type/per-corpus breakdowns,RunReportpreserving per-corpus identity. - Acceptance gates at two layers: per-document
manifest.acceptanceblock and per-corpus<corpus>/acceptance.yml.AcceptanceFailureexits 1; report still written. testkit run --only <basename>filter andtestkit diff --baseline --currentsubcommand.- Markdown run report in
working/testkit-runs/<timestamp>/report.mdwith per-tier + per-type tables and a "P0 misses" section driven by_diagnose_missheuristic. - Skills
annotate-corpusandgenerate-synthetic-docfor guided manifest authoring and synthetic-doc production respectively.
Changed (PR #3 — markup-system-foundation)
- Existing
golden_partialmanifests migrated from v1 ([TYPE, "text"]pairs) to v2 with full block_id + offset structure. regression-checkskill updated to point at new report location.
Migration
- v1 manifests are no longer accepted by the runner. Existing manifests must
be migrated via
Manifest.migrate_v1(seescripts/regen_corpus_manifest_schema.pyfor the migration pattern).