Пошук: канали, склейка, поріг¶
Пошук у прочитаному — не grep. Машинний текст скоропису XVIII–XIX ст. має
частку помилок у десятки відсотків, і потрібне прізвище виходить із рушія
спотвореним до невпізнання. Тому шукається схожість нормалізованих токенів,
а не підрядок.
Три сховища, а не три «канали»¶
search.run має поле where, і це вибір сховища, а не способу пошуку:
where |
що шукає | коли брати |
|---|---|---|
decode (дефолт) |
машинний текст усіх прогонів | шукаємо там, де ще ніхто не дивився |
pages |
прізвища, виписані оком у облік | перевірити, чи не знаходили це раніше |
records |
учасники розібраних записів (батьки, восприємники, свідки) | шукаємо роль, а не згадку |
Роль і тип акту — поля, а не читання очима. У where=records є role
(father, godfather, deceased…) і rtype (birth, death…): «хто був
батьком із цим прізвищем» і «хто був восприємником» — різні питання, і саме
заради них записи розбирають структурою. Фільтр, який не діє в обраному
сховищі, відмовляється, а не мовчить: тихо ширша вибірка виглядає як
звужена, і нуль по ній закриває напрям, якого не перевіряли.
Вісь: прізвище чи місце. axis=place (у pages і 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, а не пошук.