Metadata-Version: 2.5
Name: sakhaspell
Version: 0.1.0
Summary: Проверка орфографии якутского языка: возвращает буквы ҕ ҥ ө һ ү и находит опечатки
Project-URL: Homepage, https://sakhaspell.michill.ru
Project-URL: Source, https://github.com/EgorovM/sakhaspell
Project-URL: Issues, https://github.com/EgorovM/sakhaspell/issues
Author-email: Michil Egorov <egorovmichil9@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: nlp,orthography,sakha,spellchecker,spelling,turkic,yakut
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Natural Language :: Russian
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Text Processing :: Linguistic
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Provides-Extra: server
Requires-Dist: fastapi>=0.110; extra == 'server'
Requires-Dist: pydantic>=2; extra == 'server'
Requires-Dist: uvicorn>=0.27; extra == 'server'
Provides-Extra: tagger
Requires-Dist: torch>=2.0; extra == 'tagger'
Description-Content-Type: text/markdown

# sakhaspell — спелчекер якутского языка

**[sakhaspell.michill.ru](https://sakhaspell.michill.ru) — попробовать в браузере**

Проверка орфографии для якутского (саха тыла). Два слоя: словарный работает
офлайн на CPU, нейросетевой добавляет контекст.

Спелчекера для якутского до этого не было. Есть морфология —
[`apertium-sah`](https://github.com/apertium/apertium-sah) и
[`yakutmorph`](https://github.com/nicolascortegoso/yakutmorph), обе GPL-3 —
но hunspell-словаря, расширения для редакторов и нейрокорректора нет; ни
Яндекс.Спеллер, ни LanguageTool якутский не поддерживают. Разбор поля —
[`docs/research.md`](docs/research.md).

## Что умеет

```
$ python -m sakhaspell check "Ого уорэгэ кисиэхэ сана кыагы биэрэр"
1:0   Ого      →  Оҕо      [lexicon]   ещё: Ооо, Оо, Ошо
1:4   уорэгэ   →  үөрэҕэ   [lexicon]   ещё: үөрэнэ, үөрэрэ, үрэҕэ
1:11  кисиэхэ  →  киһиэхэ  [lexicon]   ещё: кимиэхэ, киниэхэ, биһиэхэ
1:19  сана     →  саҥа     [lexicon]   ещё: аана, саха, сара
1:24  кыагы    →  кыаҕы    [lexicon]   ещё: кыаны, кыргы, кыаһы
```

Три сценария:

- **восстановление ҕ ҥ ө һ ү** — текст набран с русской раскладки;
- **постобработка OCR и ASR** — машинные искажения (см. оговорку ниже);
- **проверка при наборе** — подчёркивание с подсказками.

## Результаты

SakhaSpellBench, **отложенный test**, 800 предложений на задачу. Метрики
пословные, как в [SAGE](https://github.com/ai-forever/sage).

| задача | F1 (словарь) | F1 (+ тэггер) | детекция | ложные |
|---|---|---|---|---|
| `clean` — чистый текст | — | — | — | **1.33%** |
| `denorm_full` — без раскладки | 93.05% | **96.51%** | 99.44% | 1.51% |
| `denorm_mixed` — как пишут | 92.78% | **95.87%** | 99.67% | 1.68% |
| `mixed` — опечатки поверх | 87.68% | **90.23%** | 96.13% | 1.46% |
| `typo` — опечатки | **76.30%** | 76.00% | 86.39% | 1.35% |
| `real` — ошибки из корпуса | **67.15%** | 66.94% | 65.35% | 1.78% |

Тэггер добавляет 3.5 пункта на деноминализации и **не стоит ничего** в ложных
срабатываниях: 1.33% с ним и без него. На задачах опечаток он корректно
бездействует — там ошибка не в спецбуквах.

Главная колонка — последняя. Ложные срабатывания на заведомо правильном тексте:
**1.33%** против 8.96% у apertium-sah (LREC 2022) и 4.89% помеченных токенов
у [bashspell](https://github.com/AigizK/bashspell), где ~46% помеченного ложно.
Именно эта метрика решает, оставит пользователь спелчекер включённым или нет.

Скорость на CPU: проверка слова **0.0016 мс** (у bashspell 0.17–0.21 мс),
подсказки 51 мс медиана (у bashspell 115–132 мс), 392 слова/с на сплошном
тексте. Память 470 МБ. Тэггер — 10.8M параметров, посимвольный F1 0.9919.

### Оговорка про ASR

Задача постобработки ASR собрана из настоящих выходов распознавателя проекта
STT, и чекер на ней проваливается: F1 1.9%. Причина не в чекере — **эталонные
расшифровки грязнее, чем выход модели**. В 50.9% расхождений не является словом
именно эталон (против 13.6% у гипотезы), и 19.1% эталонов — деноминализованные
написания (против 0.7%).

Полный аудит 8 417 расшифровок: **46.0% содержат орфографическую ошибку**,
6.74% всех слов. Артефакты для починки — `data/asr_audit/ref_issues.jsonl` и
`top_bad_words.tsv`. Для проекта ASR это значит, что WER завышен: модель
штрафуется за слова, которые написала правильно.

## Устройство

```
L0  нормализация      NFC, невидимые символы, чужие кириллические буквы
                      (ѳ→ө, ң→ҥ, ғ→ҕ), латинские гомоглифы внутри слова
L1  словарь           301k словоформ из корпуса + фильтр теней искажения,
                      кандидаты через префиксное дерево со взвешенным
                      расстоянием, CPU, миллисекунды
L2  тэггер            посимвольная разметка: для каждой буквы решает, это «г»
                      или «ҕ». Неавторегрессивный, 10.8M параметров
```

Три решения, которые определили результат:

**Первичный акцептор — словоформы, а не FST.** У apertium-sah точность 98.52%,
но наивное покрытие на газетах 91.04%: FST-акцептор подчёркивает 9% правильного
текста. Корпусный лексикон даёт 1.33%.

**Фильтр теней искажения.** Систематическая ошибка письма без раскладки
проникает в лексикон: `сана` (378 вхождений), `киси` (21), `ого` (17) попали в
словарь, потому что так пишут во всех источниках сразу, и правило «подтверждено
двумя источниками» против этого бессильно. Отдельная проверка находит формы,
которые являются искажением гораздо более частой формы, и убирает их. Это дало
**+27.7 пункта F1** и обрушило долю «невидимых без контекста» ошибок
деноминализации с 41.2% до 1.5%.

**L2 — разметка, а не переписывание.** Задача восстановления ҕҥөһү посимвольная
по своей природе, и разметка ей точно соответствует: модель физически не может
изменить символ, которому предсказала «оставить». Seq2seq переписывает текст
целиком и правит то, о чём не просили.

Подробности с цифрами — [`docs/experiments.md`](docs/experiments.md).

## Установка

```bash
pip install sakhaspell
```

Словарь на 300 304 словоформы встроен в пакет — скачивать и настраивать нечего.
У словарного слоя нет зависимостей вообще, только стандартная библиотека.

```bash
sakhaspell check "Ого уорэгэ кисиэхэ"       # показать ошибки
sakhaspell fix --file статья.txt --in-place # исправить на месте
sakhaspell repl                             # интерактивно
cat текст.txt | sakhaspell fix > исправлено.txt
```

Из Python:

```python
from sakhaspell import SpellChecker, Lexicon

checker = SpellChecker(Lexicon.load())
checker.correct("Ого уорэгэ кисиэхэ")       # 'Оҕо үөрэҕэ киһиэхэ'

for issue in checker.check("Мин огом онгор"):
    print(issue.token.start, issue.token.text, issue.best)
```

Позиции правок в исходном тексте — для подсветки в редакторе:

```python
from sakhaspell import Pipeline

p = Pipeline()
for c in p.corrections("Ого уорэгэ кисиэхэ"):
    print(c.start, c.end, c.before, "→", c.after, c.alternatives)
```

### Контекстная модель

Даёт +3.5 пункта F1 на восстановлении ҕҥөһү. Чекпоинт в пакет не входит:
обучается за 33 минуты на одной H200 через `scripts/train_tagger.py`.

```bash
pip install "sakhaspell[tagger]"
sakhaspell --tagger runs/tagger_v2 check "текст"
```

### HTTP-сервис

```bash
pip install "sakhaspell[server]"
uvicorn sakhaspell.server:app --port 8080
```

`POST /check` — проверка текста с позициями, `POST /spell` — быстрый вердикт по
словам для подсветки, `POST /suggest` — подсказки по требованию. Разнесено
намеренно: проверка стоит 0.0016 мс на слово, подсказки — 51 мс.


## Сборка с нуля

Данные и обучение — на поде H200 (`docs/experiments.md`). Корпус — 8.2 млн
предложений из 18 источников.

```bash
python scripts/prep_sentences.py  --corpus corpus_sah.jsonl --out data/sent --procs 90
python scripts/build_lexicon.py   --sent data/sent --out data/lexicon
python scripts/mine_errors.py     --sent data/sent --lexicon data/lexicon --out data/errors
python scripts/find_shadows.py    --lexicon data/lexicon --out data/lexicon
python scripts/build_bench.py     --lexicon data/lexicon --errors data/errors --out data/bench
python scripts/eval_bench.py      --lexicon data/lexicon --bench data/bench --split dev
python scripts/train_tagger.py    --sent data/sent --out runs/tagger_v2 --steps 30000
```

Готовый словарь лежит в `sakhaspell/data/` сжатым и подхватывается сам;
`Lexicon.load("свой/каталог")` берёт другой.

## Сборка и выкладка

| Что | Когда | Куда |
|---|---|---|
| `test.yml` | push и PR | pytest на 3.10–3.13, плюс macOS и Windows |
| `publish.yml` | релиз с тегом | PyPI через доверенную публикацию |
| `deploy.yml` | изменения в `docs/` | GitHub Pages и sakhaspell.michill.ru |

Тесты проверяют не только код: отдельный шаг грузит встроенный словарь и
прогоняет через него фиксированные пары, чтобы порча данных не проехала молча.

## Страница

`docs/` — статическая страница: [sakhaspell.michill.ru](https://sakhaspell.michill.ru),
зеркало на [GitHub Pages](https://egorovm.github.io/sakhaspell/). Словарь пережат во
фронт-кодированный формат и весит **864 КБ** на все 300 тысяч форм, поэтому
проверка идёт целиком в браузере и текст никуда не отправляется. Пересборка:

```bash
python scripts/build_web_assets.py --lexicon data/lexicon --out docs/data
```

## Что не работает

- **Постобработка ASR.** Не потому, что чекер плох: эталонные расшифровки
  оказались грязнее выхода модели (см. оговорку выше).
- **Постобработка OCR не проверена** — нет пар «выход против эталона». Модель
  ошибок OCR добыта (52 015 пар), но проверить не на чем.
- **Опечатки — 76% F1.** Ошибку видим в 86% случаев, но верный вариант ставим
  первым не всегда: ранжирование учитывает цену правки и частоту, но не
  контекст.
- **Пунктуация и регистр** не восстанавливаются.

## Лицензии

Код проекта свой. `apertium-sah` и `yakutmorph` — GPL-3, в рантайме не
используются: лексикон построен из корпуса, поэтому результат не заражается.
