Перейти до змісту

Пошук: канали, склейка, поріг

Пошук у прочитаному — не grep. Машинний текст скоропису XVIII–XIX ст. має частку помилок у десятки відсотків, і потрібне прізвище виходить із рушія спотвореним до невпізнання. Тому шукається схожість нормалізованих токенів, а не підрядок.


Три сховища, а не три «канали»

search.run має поле where, і це вибір сховища, а не способу пошуку:

where що шукає коли брати
decode (дефолт) машинний текст усіх прогонів шукаємо там, де ще ніхто не дивився
pages прізвища, виписані оком у облік перевірити, чи не знаходили це раніше
records учасники розібраних записів (батьки, восприємники, свідки) шукаємо роль, а не згадку

Роль і тип акту — поля, а не читання очима. У where=records є role (father, godfather, deceased…) і rtype (birth, death…): «хто був батьком із цим прізвищем» і «хто був восприємником» — різні питання, і саме заради них записи розбирають структурою. Фільтр, який не діє в обраному сховищі, відмовляється, а не мовчить: тихо ширша вибірка виглядає як звужена, і нуль по ній закриває напрям, якого не перевіряли.

Вісь: прізвище чи місце. axis=placepages і records) шукає не по прізвищу, а по поселенню — «які акти згадують це село, байдуже під яким прізвищем». Місце тримається двічі: на записі (де відбулась подія) і на учаснику (звідки він), і в шлюбі це різні села.

nysh records grep "Ковальський" --role father --json
nysh records grep "Городківка" --axis place --json

Шифра — це запит іншого роду

catalog.search (nysh find) розрізняє два питання. «Мястківка» — це «де взагалі є щось про моє село», і відповідь на нього перелік. «127-1078-1662» — це адреса конкретної справи, і відповідь одна.

Доти на адресу відповідав текстовий пошук, тобто не відповідав ніколи: три числа поспіль не трапляються в жодному заголовку. Нуль давала й «ДАВіО-172-4-112» — рядок, який пошук САМ друкує в кожному хіті як адресу справи, тобто показане не можна було набрати назад.

Адресний маршрут питає інші місця й у такому порядку: бібліотека (справа може вже лежати на цій машині) → реєстр опису (заголовок, роки, аркуші, звідки взяти) → каталоги, які вміють шукати за шифрою.

🔴 Це видно у відповіді, і мусить бути видно: coverage.searched містить рівно те, що питали, а попередження address_route каже прямо, що повнотекстового пошуку по каталогах НЕ було. Джерело, яке вміє лише текстом, іде в unavailable з причиною — його нуль не є нулем про цю справу.

Якщо адреса не знайшлась ніде, запит іде текстом як звичайний: рядок може бути лише схожий на шифру (дата «1858-03-14»). Примусово текстовий маршрут — nysh find … --text.

🔴 Нуль у кожному з трьох означає різне. «Немає в decode» — немає в машинному тексті, який міг це слово скалічити. «Немає в pages» — ніхто не виписував, і це майже нічого не каже про джерело. «Немає в records» — ніхто не розбирав записи, навіть якщо прізвище там є.

Тому знаменник у відповіді різний за змістом, і в звіті його треба називати разом зі сховищем, а не окремо.


Що робить пошук по декоду сам

Нормалізація обох сторін

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

🔑 Склейка розірваного прізвища

Прізвище на межі рядка розривається переносом, і жодна половинка сама по собі не схожа на ціле. Пошук тримає хвости трьох попередніх рядків і пробує зшити їх із початком поточного.

Чому саме трьох, а не одного: у табличних бланках (сповідки, ревізії, формуляри) сегментація зшиває колонки, і половинки розходяться на два-три рядки. З вікном в один рядок такі записи не знаходяться взагалі.

Далекі склейки суворіші за сусідню. Через рядок-два половинки злітаються вже не за законом переносу, а випадково — і без цієї суворості пошук почав би знаходити прізвище в будь-якому шумі.

Практичний наслідок для тебе: не роби склейку руками. Побачивши в тексті обірваний токен, не дописуй його — це вже зроблено, і зроблено з порогами, які перевірені на контрольних запитах.

🔑 Вікно замість рядка

search.run має поле context (типово 1): кожен хіт приходить із сусідніми рядками, а не голим рядком.

Це не зручність. Рядок сам по собі не розрізняє прізвищ зі спільним коренем, а в одній парафії їх буває кілька — заміряно на метриках одного села: 78 кандидатів верхівки розклались на три різні роди з тим самим коренем плюс причт, і за самим рядком вони зливаються в купу однаково правдоподібних хітів.

Розрізняє їх сусідство:

    ↑ Балтскаго Повѣта Села Ковалевскаго      ← географія стоїть вище
    » Дьячисъ Григорій Ковалевскій сынъ Свя   ← сам хіт обривається
    ↓ щунническій получившей удостость        ← слово продовжується нижче

Вікно розсувне, а не ±N рядків. Огризок контекстом не вважається: сусідній рядок «на», «и», «3» не пояснює нічого, тож вікно розсувається далі, доки не набереться змістовного. Без цього контекст виявляється порожнім рівно там, де він найпотрібніший — між колонками щільного формуляра.

🔑 Другий голос поруч

Якщо справу читано двома рушіями, до хіта додається читання другого на тому самому рядку:

    » Дьячисъ Григорій Ковалевскій сынъ Свя
      2-й голос: Дьячисъ Григр̆ Ковааевскіи сынъ све

Збіг голосів означає надійне читання; розбіжність — сигнал, а не шум. Рушій із мовною моделлю підставляє правдоподібне слово, а другий судить кадри незалежно й калічить локально, зберігаючи корінь. Розбіжність саме на прізвищі (як вище: «Ковалевскій» проти «Ковааевскіи») означає, що ознака в пікселях — і вирішувати має око, а не третій алгоритм.

🛑 Але два голоси підтверджують читання, а не приналежність. Те, що обидва прочитали прізвище однаково, не робить особу вашою.

Поріг схожості

thresh за замовчуванням 80 зі 100. Нижче — більше шуму, вище — губляться саме ті спотворені форми, по які пошук і кликали.

🔴 Не піднімай поріг, щоб «прибрати шум». Рушій калічить середину слова сильніше за краї, тож найцінніші кандидати мають бал ближче до порогу, ніж очевидні. Замість підняття порогу — ранжуй видачу (workflows.md, крок 3).

⚠ Окремо: короткі токени відсіюються довжиною (одиночний — від чотирьох літер, пара — від шести сумарно). Спроба додатково глушити «рядки-огризки» з волокон паперу перевірялась заміром і дала 0.1% менше кандидатів при нулі втрачених хітів — тобто фільтр не потрібен, а от глушіння такого рядка разом із його хвостом з'їдало справжні склейки.


Чого в пошуку немає

Це чесна межа версії, а не прогалина в документації.

немає що це було б що робити натомість
канал за топонімом у декоді «чи бачить машинний текст село, яке точно там є» — знаменник для нуля звичайний запит назвою села; географію звіряти з каталогом. У виписаному й розібраному топонім має власну вісь — axis=place
канал за складом двору пошук родини за сусідами, коли прізвище скалічене читати сторінку цілком навколо знайденого
канал за станом + іменем «однодворець + Іосиф» — фаззі по обох половинах два окремі запити, звіряти сторінки
розрив між колонками окремим каналом коли прізвище розірване не переносом, а версткою склейка в три рядки плюс вікно контексту покривають більшу частину цього
перевірка віку/дат кандидата «чи міг цей запис бути нашою особою» арифметика документа вручну; цифри з декоду не факт

🛑 Якщо людина просить «прочешіть повіт» або «знайдіть усі згадки роду в фонді» — скажи прямо, що в цій версії є один канал і три сховища. Обіцяти багатоканальний прочіс, а потім видати нуль з одного каналу — помилка, яку дорого виправляти: цей нуль виглядатиме як вичерпна відповідь.


Як читати нуль по кожному сховищу

Разом із результатом приходить попередження. Читай його, а не лише число:

  • zero_with_denominator — «не знайшлось у N прогонах (M сторінок)». Це справжній нуль, і в звіті він так і пишеться: з N і M.
  • no_denominator — не шукало ніщо. Звітувати «немає» не можна.
  • попередження про метод — якщо сторінки заносились із позначкою «читав лише декод», їхній вміст успадкував помилки рушія.

І головне, що не приходить попередженням: нуль у decode не покриває сторінок, яких ніхто не читав машиною. Скільки їх — питай cases.list і pages.status, а не пошук.