Metadata-Version: 2.4
Name: nl2prolog
Version: 1.0.2
Summary: Russian/English NL → Prolog facts & queries (Q&A + KB building)
Author-email: Andrey Vorfolomeev <binom0x@proton.me>
License: MIT
Project-URL: Repository, https://github.com/binom0x/nl2prolog
Project-URL: Documentation, https://github.com/binom0x/nl2prolog#readme
Project-URL: Changelog, https://github.com/binom0x/nl2prolog/blob/main/CHANGELOG.md
Keywords: nlp,prolog,russian,knowledge-graph,qa
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Natural Language :: Russian
Classifier: Natural Language :: English
Classifier: Topic :: Text Processing :: Linguistic
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pymorphy3>=2.0
Provides-Extra: ru
Requires-Dist: natasha>=1.6; extra == "ru"
Provides-Extra: en
Requires-Dist: spacy>=3.7; extra == "en"
Requires-Dist: en_core_web_sm; extra == "en"
Provides-Extra: ru-full
Requires-Dist: natasha>=1.6; extra == "ru-full"
Requires-Dist: spacy>=3.7; extra == "ru-full"
Requires-Dist: ru_core_news_sm; extra == "ru-full"
Provides-Extra: prolog
Requires-Dist: PyAgentProlog; extra == "prolog"
Provides-Extra: full
Requires-Dist: nl2prolog[en,prolog,ru]; extra == "full"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Dynamic: license-file

# nl2prolog

> **Русский / English** natural language → Prolog facts & queries.  
> Детерминированный парсер для Q&A и KB building в доменах с нормативной и научной лексикой.

[![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
[![QA Benchmark](https://img.shields.io/badge/QA%20benchmark-32%2F33%20query%20%C2%B7%2033%2F33%20answer-brightgreen)]()

---

## Что это

`nl2prolog` — **не** «ещё один NER» и **не** «семантический парсер». Это **детерминированный** преобразователь русского (и английского) текста в программу на Prolog: `edge/3`, `attribute/3`, `node/3`, `alias/2`.

Решает две задачи:

| Use case | Что делает | Пример |
|---|---|---|
| **Q&A** | NL-вопрос → Prolog-запрос → `Agent.stream()` | `Кто директор X?` → `edge(X, 'x', 'директор')` |
| **KB building** | NL-текст → Prolog-факты для графа знаний | `X основан в 2015 году.` → `attribute('x', 'год_основания', '2015')` |

**Ключевой принцип**: парсер отвечает только за **синтаксис**. Семантика (KB-правила, алиасы, метонимия) — слой выше.

---

## Зачем

Если вам нужно:

- ✅ **Воспроизводимый** парсинг нормативных и научных текстов (одно и то же на входе → одно и то же на выходе)
- ✅ **Аудит** — видно, какие правила сработали, какие термины отфильтрованы, почему
- ✅ **Прозрачная архитектура** — синтаксис, диалог, KB-семантика разделены
- ✅ **Расширение под домен** без форка — класс-атрибуты для всех списков
- ✅ **Детерминированный Q&A** поверх PyAgentProlog

Если вам нужно:

- ❌ «Просто спросить LLM» — используйте LLM
- ❌ «Извлечь вообще все сущности» — используйте NER-модели (Natasha NER, spaCy)
- ❌ «Понимать смысл, а не структуру» — используйте semantic parsing / LLM

`nl2prolog` — про **точность и повторяемость** там, где цена ошибки высока (нормативка, compliance, научные обзоры).

---

## Установка

```bash
# Минимальная (только pymorphy3, EN-fallback)
pip install nl2prolog

# Русский (Natasha — рекомендовано)
pip install "nl2prolog[ru]"

# Русский + PyAgentProlog (исполнение запросов)
pip install "nl2prolog[ru,prolog]"

# Всё: ru + en + prolog + dev
pip install "nl2prolog[full,dev]"
```

**Extra	Что даёт								Обязательность**
(core)	pymorphy3 — морфология RU				✅ обязательно
ru		natasha — сегментация, UD, NER для RU	⭐ сильно рекомендовано
en		spacy + en_core_web_sm					опционально
prolog	PyAgentProlog — исполнение запросов		опционально

Без natasha парсер деградирует до pymorphy3-fallback: не собирает compound-сущности, хуже размечает роли. Для production установите nl2prolog[ru].

Проверка:

```python
from nl2prolog import runtime_info
print(runtime_info())
# {'version': '1.0.0', 'natasha': True, 'pymorphy3': True,
#  'spacy_ru': False, 'spacy_en': False, 'prolog_native': True}
```
## Быстрый старт
### Q&A — вопрос → Prolog-запрос
```python
from nl2prolog import parse_query

q = parse_query("Кто директор технопарка Саратов?")
print(q)
# edge(X, 'технопарк_саратов', 'директор')
```
Запрос — это готовая цель для Prolog:

```prolog
?- edge(X, 'технопарк_саратов', 'директор').
X = 'иванов_иван'.
```
## KB building — текст → факты
```python
from nl2prolog import parse_facts

facts = parse_facts("Технопарк Саратов основан в 2015 году.")
print(facts)
# edge('технопарк_саратов', '2015', 'основать').
# node('технопарк_саратов', _, concept).
```
## Полный API — parse()
Когда нужны и факты, и запрос:

```python
from nl2prolog import TextToProlog

t2p = TextToProlog()

facts, query = t2p.parse(
    "Кто директор технопарка Саратов?",
    strict=True,                    # без fallback-субъекта «я»
    facts_from_questions=False,     # не тащить факты из вопроса в KB
)
print(query)  # edge(X, 'технопарк_саратов', 'директор')
print(facts)  # node('технопарк_саратов', _, concept). …
```
## Диалоговый слой
DialogTurn — coreference и эллипсис. Парсер stateless по дизайну, состояние живёт здесь.

```python
from nl2prolog import TextToProlog
from nl2prolog.dialog import DialogTurn

t2p = TextToProlog()
dlg = DialogTurn(t2p=t2p)

# Turn 1: обычный запрос — контекст обновляется автоматически
# (после успешного исполнения через dlg.ask)
dlg.last_entity = "иванов_иван"
dlg.last_query  = "edge(X, 'технопарк_саратов', 'директор')"

# Turn 2: «А сколько ему лет?» — coreference
q, ok, src = dlg.resolve("А сколько ему лет?")
print(q)   # attribute('иванов_иван', 'возраст', X)
print(ok)  # True

# Turn 3: «А у Казани?» — эллипсис
dlg.last_query = "attribute('технопарк_москва', 'площадь', X)"
q, ok, src = dlg.resolve("А у Казани?")
print(q)   # attribute('технопарк_казань', 'площадь', X)
```
Без контекста resolve() возвращает ("", False, "") — вызывающий код падает в обычный парсер.

## Публичный API
```python
import nl2prolog

# Высокоуровневые удобства
nl2prolog.parse_query(nl, strict=True)          # → str (Prolog query)
nl2prolog.parse_facts(text, strict=False)       # → str (Prolog facts)
nl2prolog.runtime_info()                        # → dict

# Класс
nl2prolog.TextToProlog()
    .parse(text, **kwargs)                      # → (facts, query)
    .convert(text, mode="graph"|"query"|…)      # → str
    .convert_answer(text, question_condition)   # → str
    .extract_conditions(text)                   # → list[Term]
    .extract_query(text)                        # → list[Term]

# Диалог
nl2prolog.DialogTurn(t2p=…, agent_factory=…)
    .ask(g, nl)          # → (answer_dict, status)
    .answer(g, nl)       # → (facts_str, status)
    .resolve(nl)         # → (query, is_resolved, source)
    .yes(g) / .no(g)
    .reset() / .reset_context()

# Термы
nl2prolog.Term(functor, args)
nl2prolog.Variable(name)
```
## Параметры parse()
**Параметр				Default		Назначение**
strict					False		Не подставлять «я» вместо пропущенного субъекта
facts_from_questions	True		Извлекать факты и из вопросов (для Q&A — False)
log_filtered			False		Заполнить t2p.filtered_atoms (аудит фильтрации)
link_components			False		Добавить alias/2 для UI-группировки
lang					"auto"		"ru" / "en" / автоопределение

## Архитектура
nl2prolog разделяет слои — это ключевая идея, дающая обобщаемость.

```text
NL-вопрос
    │
    ├─ dialog.py:  coref / ellipsis          (состояние диалога)
    │              если resolved → готовый query
    │
    └─ text_to_prolog.py:  parse
            ├─ grammar frames  (что/сколько/когда/есть/какие)
            ├─ UD-syntax       (compound, role-triple, wh-handler)
            └─ 3 уровня фильтрации
                    │
                    ▼
             edge / attribute / node / alias
                    │
                    ▼
            (ваш слой)
                ├─ KB-aliases  (FIO, аббревиатуры)
                ├─ KB-rules    (located_in, transitive closure)
                ├─ Query lift  (edge → rule invocation)
                └─ Agent (PyAgentProlog)
```
**Слой	        Отвечает за	     							Не отвечает за**
text_to_prolog	синтаксис, грамматические фреймы, compound	семантику, правила, диалог
dialog.py		coreference, эллипсис, контекст				извлечение фактов
KB (ваш)		правила вывода, типизацию, алиасы			разбор NL
Agent			исполнение									разбор NL
Правило: если знание можно записать как kb.pl-факт или как контекст в dialog.py — оно не должно быть в парсере.

## Расширение под домен
Все списки — леммы, а не словоформы. Расширение без форка:

```python
from nl2prolog import TextToProlog

# Научный домен
TextToProlog.ATTR_LABEL_NOUNS |= {
    'метрика', 'выборка', 'гипотеза', 'оценка',
}

# Отсечь ложные NER-срабатывания
TextToProlog.NER_ORG_BLACKLIST |= {
    'регламент', 'постановление', 'протокол',
}

# Защитить короткие термины от фильтра
TextToProlog.SHORT_PROTECTED |= {
    'оки', 'окио', 'мвт', 'гкал',
}

# Убрать adjectival noise — через noise_ru.txt
# (файл в каталоге пакета, одна лемма на строку)
```
**Класс-атрибут			Что расширяет**
NER_ORG_BLACKLIST		абстрактные существительные → concept вместо org/person
ATTR_LABEL_NOUNS		лейблы («наименование», «площадь») → атрибуты, не узлы
ATTR_ADJ_WHITELIST		Rel-прилагательные, которые парсер сохраняет
SHORT_PROTECTED			короткие (<5) осмысленные термины (аббревиатуры, единицы)
SKIP_LEFTOVER_NOUNS		служебные существительные, не идущие в граф
GENERIC_ADJ				fallback для generic-ADJ, если POS не сработал

Файл noise_ru.txt — расширение ATTR_ADJ_WHITELIST без правки кода:

```txt
# noise_ru.txt (леммы)
эмпирический
теоретический
экспериментальный
статистический
```
## Метрики
Эталонный Q&A-корпус (33 пары + 2 диалога, домен «технопарки»):

Метрика				Значение		Цель
Query extraction	32/33 = 97.0%	≥ 70% ✅
Answer accuracy		33/33 = 100.0%	≥ 85% ✅
Dialog turns		6/6 = 100.0%	≥ 50% ✅

По категориям (query ✓):

Категория									✓
who, where, when, how_many, what, which		18/18
yes_no (FIO через KB-alias)					4/4
transitive (KB-rule lift)					2/2
list (grammar frame + lift)					1/1
dialog (coref + ellipsis)					6/6
alias (metonymy)							1/2
Единственный провал — Кто директор технопарка в РФ? → ожидается технопарк_москва. Это metonymy («столичный технопарк»), решается на уровне KB / world knowledge, не в парсере.

## Что не делает
Явные ограничения, чтобы не было неожиданностей:

Не решает									Почему								Куда
Coreference вне диалога						парсер stateless					DialogTurn
Метонимия (РФ → москва)						world knowledge						KB-alias / embeddings
Связность графа = 1							NL-парсинг даёт лес компонентов		норма для KB
OCR-фрагменты (управа из «уп равляющая»)	pymorphy не отличает от слов		pre-processing
Семантическое сходство						вне NLP-слоя						metrics.py / embeddings
Многоязычность ≠ RU+EN						только RU и EN						—
Дискурс (эти темы, этот опыт)				вне синтаксиса						dialog.py / LLM

## Разработка
```bash
git clone https://github.com/you/nl2prolog.git
cd nl2prolog
pip install -e ".[full,dev]"

# Тесты (быстрые)
pytest -v -m "not slow"

# Полный прогон (включая corpus)
pytest -v

# Линтеры
ruff check src/ tests/
mypy src/nl2prolog/
```

## Структура репозитория
```text
nl2prolog/
├── pyproject.toml
├── README.md
├── LICENSE
├── CHANGELOG.md
├── src/nl2prolog/
│   ├── __init__.py
│   ├── _version.py
│   ├── _terms.py             # Term/Variable (fallback без PyAgentProlog)
│   ├── text_to_prolog.py     # парсер
│   ├── dialog.py             # coreference + ellipsis
│   ├── noise.py              # POS-фильтрация
│   ├── noise_ru.txt
│   ├── noise_en.txt
│   └── py.typed
└── tests/
    ├── conftest.py
    ├── test_noise.py
    ├── test_text_to_prolog_smoke.py
    ├── test_dialog.py
    └── data/reg_001.txt
```

## Совместимость
Компонент				Версия
Python					3.9+
pymorphy3				≥ 2.0
natasha (опц.)			≥ 1.6
spacy (опц., EN)		≥ 3.7
PyAgentProlog (опц.)	любая стабильная

Только Linux/macOS/Windows, Python с поддержкой pymorphy3 (CPython, PyPy ≥ 3.9).

## Лицензия

MIT 

## Ссылки

Issues / вопросы: https://github.com/binom0x/nl2prolog/issues
Changelog: CHANGELOG.md
PyAgentProlog: https://github.com/binom0x/PyAgentProlog (если используете)

## Что дальше
Пакет в состоянии 1.0.0 — стабильный QA-слой. Roadmap:
v1.1 — рефакторинг монолита text_to_prolog.py → 9 модулей (~30 мин/модуль)
v1.2 — публичный KnowledgeGraph API
v1.3 — интеграции: integrations.pyagentprolog, integrations.gradio
v2.0 — CLI: nl2prolog facts input.txt, nl2prolog query "…"

См. CHANGELOG.md для деталей.
