Техническо състояние на проекта
последна актуализация: 21.09.2026
136
изградени функционалности
3.08M
знака в индекса на чатбота
27 895
лица в родословната база
40
задачи в план за развитие
Изградено
136 функционалности · 9 модулаСайт
✓
Основен стек
WordPress + тема Blocksy, редакция през Gutenberg + Custom HTML блокове.
✓
Начална страница
Hero секция, шорткод за последни новини, преглед на галерия, weather widget (Open-Meteo API, без ключ), CTA-та за дарения.
✓
Раздел „История“
Отделна категория в блога с дедикирана display страница, изключена от общия поток новини чрез pre_get_posts hook.
✓
Странична лента (sidebar)
Widget за дарения и widget за кампанията „Дари спомен“.
✓
Контакти
Google Maps вграждане + контактна форма (WPForms Lite).
✓
Интерактивна викторина в „История“
Fisher-Yates разбъркване на отговорите, обяснение след всеки въпрос, прогрес лента с точки, финален екран с резултат; масив QUIZ_DATA с 8 въпроса (за разширяване).
✓
Дизайн система
Фиксирана цветова палитра, CSS класове с префикс .chk-, override-и на Blocksy defaults там, където темата налага собствени стилове.
Архив (Tainacan)
✓
Интеграция с Blocksy
Плъгин Tainacan Support for Blocksy — стандартният Tainacan Interface не пасваше на темата.
✓
Колекция „Пленерни картини“
Обединена колекция (ID 153, slug pleneri) с таксономия „Пленер / Година“ вместо отделни колекции по година.
✓
Колекция „Архив фотографии“
Основна колекция (ID 961) с каталогизирани стари фотографии и документи от селото; цел на релационните полета в останалите колекции.
✓
Проверка за дублирани елементи
Дедупликация по файлово име, с флагване на несигурни съвпадения (thumbnail за ръчен преглед) при ръчно преименувани елементи.
✓
Колекция „Дари спомен“
Колекция (ID 3636, slug dari-spomen) за анонимно публично качване на спомени; нови елементи влизат като Pending. Наследява таксономиите от колекция 961 (Вид на снимката, Десетилетие/Период, Източник — с нов термин по подразбиране „Дари спомен“).
✓
Отключен анонимен достъп до „Дари спомен“, пазен от индексиране
Колекцията мина от private на publish, за да може формата изобщо да се зареди за нелогнат посетител — Tainacan проверява само WP core публичния флаг на статуса, независимо от настройките за анонимно подаване. Пазена от търсачките с публичен noindex, nofollow през WPCode wp_head снипет — покрива страницата на колекцията, /dari-spomen/ и страницата за подаване.
✓
Заобикаляне на Tainacan 1.2.0 bug с default стойности
Счупеният set_default_value() е заобиколен с директно задаване през update_post_meta() при нови подадени елементи.
✓
Поле „Съгласие“ и скрито поле за свързана личност
„Съгласие“ като Selectbox с една опция (Tainacan няма нативен checkbox тип). Полето „Свързана личност/снимка“ (връзка към колекция 961) е скрито от публичната форма през enabledMetadata на submission блока — вижда се само от админ.
Местности (Tainacan)
✓
Колекция „Местности“
ID 4180, slug mestnosti, статус publish; анонимната REST заявка връща 200. Създадена скриптово срещу живия сайт, ID-тата са записани в mestnosti_state.json.
✓
Таксономия „Тип местност“
ID 4186, 48 термина в 8 родителски групи.
✓
Схема със 17 собствени полета
Варианти на името, polygon GeoJSON, confidence (точна / приблизителна / неизвестна), name_class, status (жива / изчезнала по документи / извън землището), first_attested, etym_source, part_of (връзка към самата колекция), person_origin, owner_hist, източник и връзка към колекция 961.
✓
Нативни координати
Полето coordinates използва вградения тип GeoCoordinate на Tainacan.
✓
Премахнато поле за надморска височина
elevation отпадна след извличането — в източниците няма данни за височина.
✓
Импорт на 190 местности
Собствен PHP скрипт вместо Tainacan CSV importer, чийто insert_hierarchy() игнорира allow_new_terms и който няма сух пробег. ID-тата на създадените елементи са в created_items.json.
✓
Rollback скрипт за импорта
Връща колекцията в изходно състояние по списъка в created_items.json.
✓
Правилен ред при създаване на елементи
Item::validate() проверява задължителните полета спрямо записаното в базата, затова нов елемент директно в publish пада. Ред: draft → Item_Metadata → publish + update(), както в самия CSV importer.
✓
Заобикаляне на slug bug в Tainacan 1.2.0
fetch([‘slug’ => …]) връща всички записи от типа, защото parse_fetch_args() подава невалидния аргумент post_name. Проверките за идемпотентност ползват get_posts([‘name’ => …]) и get_term_by().
✓
Коректни опции на Selectbox полета
Опциите се разделят по \n при рендиране, не по options_separator. Скриптовият запис е implode(„\n“, $values), без options_separator.
✓
Незадължителни координати
coordinates е трайно незадължително — 16 изчезнали местности от стари нотариални актове никога няма да имат координати. Напредъкът по геолокация се следи през confidence = „неизвестна“.
✓
Поле за разказа от том 1
Отделно Textarea поле narrative (ID 6296) с резюмета за 52 от 69-те местности, споменати в „Чокманово том 1“. location_desc остава дословната речникова статия от том 2.
✓
Проверка преди масов импорт
Скриптово създадените полета се проверяват визуално и през REST, не само по успешен insert. Праговете в скриптовете отчитат двете core полета (колекцията отчита 19, не 17).
Родословие
✓
Вход с 6-цифрен код
Удостоверяване без WordPress акаунт — 6-цифрен код за потвърждение + 30-дневна HMAC бисквитка, срещу JSON списък с разрешени имейли (premium_access.json).
✓
Корекция на имейли със знак „+“
+ в имейл адреса се декодираше като интервал в redirect параметрите. Поправено с rawurlencode() на всички места с add_query_arg(), които носят chk_email.
✓
Обработка на пълния износ от Agelong Tree
Парснати 27 895 лица в 164 рода в структурирани JSON файлове (people_core.json, families.json, notes.json, rod_tags_dict.json + overlay файлове).
✓
Филтриране на износа
~6 500 празни/служебни записа се отсяват; обхватът се ограничава до Чокманово по мястото в записа + разширяване по графа на връзките.
✓
Fingerprint валидация за overlay корекции
Agelong Tree преномерира ID-тата при всяко вмъкване, затова корекциите (corrections.json, moves.json, additions.json, local_families.json, family_additions.json) се съпоставят по отпечатък (име + фамилия + година на раждане), а не по ID.
✓
Твърда защита на съдържанието (hard gate)
Пропуска се през template_redirect hook, преди да се рендира каквото и да е съдържание — the_content филтърът се оказа ненадежден за Blocksy.
✓
Защита от индексиране в Google
Тъй като гейтваните страници не връщат съдържание на неавтентикирани заявки (включително ботовете на търсачките), личните и родословни данни не попадат в резултатите от търсене.
✓
Защита от привилегирован достъп (privilege escalation)
Формата за вход проверява съществуващата роля на потребителя, преди да зададе нова — за да не презапише случайно роля на администратор. Неотговарящи на условията акаунти получават общ отговор.
✓
Кеш изключения за защитените страници
LiteSpeed Cache no-cache сигнализация (litespeed_control_set_nocache + LSCACHE_NO_CACHE) и добавяне на URI-тата в списъка с изключения — за да не се показва кеширано съдържание на грешен потребител.
✓
Рамка за поверителност
Три нива: починали преди 1930 г. (свободно използваеми), починали в рамките на живата памет (с внимание), живи лица (никога не се показват).
✓
Търсене и разграничаване на лица
Search-first интерфейс с debounced AND-търсене и карти за разграничаване при съвпадащи имена (често срещано в малко село с повтарящи се имена през поколенията).
✓
Визуализация на родословното дърво
Интерактивно SVG дърво на предците (D3-style, ванилен JS) с bezier конектори, показване на братя/сестри по поколение, отделни render пътища за мобилно и десктоп.
✓
Timeline странична лента + zoom
Sticky timeline sidebar до дървото; мащабиране на изгледа през CSS custom property –chk-zoom.
✓
Печат и JSON износ
Възможност за печат на дървото и експорт на данните за лице/род в JSON формат.
✓
Коректно показване при родствени бракове
Поправен bug с потискане на легитимни повторения в дървото (pedigree collapse) — проследяващите масиви вече са локални за всеки клон, вместо споделени между двете родови линии.
✓
GEDCOM импорт и обновяване
Данни от родословната гора на Петър Камбарев (Agelong Tree), с работещ процес за периодично обновяване; парсерът поддържа структурни разлики между отделните export-и (наличие на SURN тагове, покритие на NOTE полета).
✓
Единен слой за връзките между поколенията
Всички конектори се чертаят в един SVG върху цялото дърво, след измервателен проход по реалните позиции на картите (getBoundingClientRect). Предишният вариант рисуваше отделен SVG на всяка лента с preserveAspectRatio=“none“, което деформираше кривите неравномерно при широки родове.
✓
Цветово кодиране на родовите линии
Бащината линия е зелена, майчината — теракота; и по конектора, и по горния ръб на картата. Легенда над дървото.
✓
Осветяване на пълната линия до корена
Посочване, фокус или клик върху карта осветява цялата верига до изходното лице и приглушава останалото дърво. Клик фиксира линията, Esc я освобождава.
✓
Фиксирана лента с текстовата родствена линия
Веригата се изписва в лента, закрепена в дъното на екрана (position:fixed) — извън потока на документа, за да не измества дървото при всяко минаване с мишката.
✓
Свиване и разгъване на клонове
Хапче на долния ръб на всяка карта скрива или връща предците ѝ; свитата карта показва броя скрити лица и получава пунктиран долен ръб. Бутонът „Разгъни всички“ се появява само когато има свити клонове, с брояч.
✓
Стабилни ключове по път на възлите
Всеки възел се идентифицира с път от корена (0, 0f, 0fm), а не с ID на лицето — така свитото състояние остава коректно и когато едно и също лице се появява в две родови линии.
✓
Запазване на позицията при пренареждане
Вертикалният скрол на страницата и хоризонталният скрол на дървото се възстановяват след всяко свиване, разгъване или смяна на мащаба.
✓
Автоматично побиране в екрана
Бутонът „Побери“ изчислява мащаба от броя колони и наличната ширина, вместо нивото да се търси на ръка със стъпки от 10%.
✓
Разделени режими: търсене и дърво
Панелът за търсене се скрива при отваряне на дърво и се заменя със sticky лента с името на лицето и бутон „Ново търсене“. Рестартът вече не изисква да се познае, че трябва да се пише в изчезналото поле.
✓
Приглушен изход от акаунта
„Изход“ беше най-видимият бутон на страницата и се бъркаше с рестарт на търсенето. Сега е второстепенен линк, а рестартът е основното действие в лентата.
✓
Йерархия на контролите в картата
Три конкуриращи се контрола (бележка, братя/сестри, свиване) са сведени до два с различен вид: подчертан текстов линк „подробности“ за съдържанието и хапче на ръба за структурата. Дългите бележки се отрязват с CSS line-clamp, а не с втори линк.
✓
Неутрализиране на Blocksy button стиловете
Темата боядисва всеки <button> в теракота, което правеше второстепенните контроли (×, „свий“) да изглеждат като главни действия. Всички собствени бутони в модула получават явен reset.
✓
Клавиатурна навигация в дървото
Всяка карта е фокусируема (tabIndex), Enter/Space фиксира линията, Esc я освобождава.
Дигитализация (OCR)
✓
OCR pipeline
Собствен pipeline: PyMuPDF за извличане на страници + Tesseract с поддръжка на български език (bul); преизползваем скрипт ocr_pipeline.py.
✓
„Чокманово том 2“ — напълно обработена
236 PDF страници → 470 книжни страници; 963 000+ разпознати знака; per-page JSON (текст, брой знаци, средна OCR увереност, флагнати страници).
✓
„Чокманово том 1“ — напълно обработена
267 страници, 740 чънка, 1 208 238 знака. Качеството е проверено преди качване: 78.5% кирилица, нула заместващи символи, нула лични данни; разликата спрямо източника е 9.8% умишлено припокриване между съседните чънкове.
✓
Две кратки книги — обработени
„Св. Архангел Михаил“ (4 стр.) и „Св. Пророк Илия“ (5 стр.), средна OCR увереност ~93.8–93.9%.
✓
Научна статия за райковските свещеници — обработена
12 страници на Златка Петрова (РИМ „Стою Шишков“ — Смолян) с бележки под линия и архивни препратки. Първият източник, който не е селищна монография.
✓
„Родопските комити разказват“ — обработена
204 страници, OCR + семантично нарязване през ragexport.py; 842 537 знака в 515 чънка с припокриване и запазени метаданни за книга и страница.
✓
Извличане на местностите от двата тома
190 местности от речниковата глава на том 2; 69 от тях са открити и в разказа на том 1, който няма собствен топонимен речник.
RAG чатбот (Cloudflare)
✓
Чатбот — демо версия
Статичен Custom HTML интерфейс с предварително написани въпроси и отговори — заменен от реалния бекенд във Фаза 4.
✓
RAG бекенд — инфраструктура (Фаза 0)
Проект chokmanovo-chatbot в Cloudflare Workers, D1 схема приложена (chokmanovo-db), Vectorize индекс създаден (chokmanovo-chunks, 1024-dim cosine), Workers AI binding за embeddings (@cf/baai/bge-m3).
✓
Защита на админ панела
/admin/* защитен с Cloudflare Zero Trust Access (One-time PIN); конфигурация в D1 с лимити daily_spend_limit_usd 2.0 и daily_ip_limit 20.
✓
Ingestion endpoint (Фаза 1)
POST /admin/ingest приема JSON от OCR инструмента, ембедва всеки чънк през Workers AI и записва вектора във Vectorize и реда в D1. Идемпотентен — повторно качване обновява, не дублира.
✓
Пилотната книга в индекса
Цялата книга заредена наведнъж: 515 чънка за ~36 s, 515 вектора × 1024 измерения. Проверено чрез сравнение D1 ↔ Vectorize, а не по код: 515 реда, 515 уникални id, сумата от знаците съвпада точно с износа.
✓
Единна идентичност на чънковете
${book_id}:${chunk_id} е едновременно primary key в D1 и vector id във Vectorize — това е връзката, която позволява на векторно съвпадение да върне текст и страница. 64-байтовият лимит на Vectorize се валидира преди запис.
✓
Проверено извличане от край до край
Заявка по vector id връща точно самосъвпадение 1.0000 с логични съседи, а върнатите id-та се разрешават обратно до текст и страници в D1.
✓
Публичен endpoint /chat (Фаза 2)
Отговор на български, стъпил единствено върху извлечения контекст, с цитати заглавие + страница. CORS ограничен само до digital-archive.site; /admin/* остава зад Access.
✓
Тристепенна защита на разходите
Глобален прекъсвач по дневен разход, дневен лимит на IP и твърд max_tokens — в този ред. Блокирана заявка не стига до Claude изобщо: отговаря се от константа на български.
✓
Атомарен брояч за лимита на IP
Проверката и увеличението са едно изявление (INSERT … ON CONFLICT … RETURNING), така че две едновременни заявки от един IP не могат и двете да минат под лимита.
✓
Отчитане на разхода по реални данни
cost_usd се смята от токените, които API-то само отчита, никога от оценка; цените на модела живеят в config до името му. Всяко повикване се записва в usage_log в същата заявка.
✓
Отказ без подвеждащи цитати
Извличането винаги връща съседи, затова честният отказ иначе би пристигнал със списък източници. Маркерът за отказ е в config до промпта; сравнението е устойчиво на форматиране, пренос на ред и главна буква.
✓
Чист текст без markdown
Правило в промпта против звездички и заглавия, които widget-ът би показал буквално. Проверено срещу девет markdown конструкции върху реален отговор.
✓
Устойчивост при смяна на модела
temperature се изпраща само на моделите, които я приемат — Opus 5, Sonnet 5 и 4.7/4.8 я отхвърлят с 400. Смяна на config.model вече не може да събори /chat.
✓
Отговор от константа при липса на контекст
Ако извличането не върне нищо, отговорът идва от константа — вместо да се плаща на Claude да каже, че не знае.
✓
По-подробни отговори
max_tokens 2048 и top_k 8, плюс пренаписано правило за изчерпателност (старото правило „пиши кратко“ беше истинската спирачка, не таванът). Измерено: ~$0.0101 на въпрос, ~198 въпроса на ден под непроменения таван от $2.
✓
Разходите в отговора на API-то
cost_usd, input_tokens и output_tokens се връщат заедно с отговора, от същите променливи, които се пишат в usage_log — мрежовият отговор и разходният регистър не могат да се разминат.
✓
Админ панел за чатбота (Фаза 3)
Страница /admin/panel с табло, дневник на заявките и редактор на config, зад същия Access. „Изхарченото днес“ чете същата константа TODAY_SQL, която ползва и прекъсвачът — двете не могат да се разминат за това къде започва денят.
✓
Предпазители при запис в config
config се чете при всяка заявка, затова лоша стойност важи за следващия посетител без деплой. Твърдите граници връщат 400; преценките — 409 с предупреждение на български, което трябва да се потвърди (сменен модел без цени, модел, който отхвърля temperature, липсващ маркер за отказ, лимит 0).
✓
Управление на архива от панела (Фаза 3.1)
Книги се качват и изтриват без терминал. DELETE /admin/api/books без confirm: true е сух пробег, който само отчита какво ще падне. Векторите се трият преди редовете в D1 — обратният ред би оставил сираци, които печелят top_k места и после не се разрешават до текст.
✓
Батчване по бюджет от знаци
Workers AI отказва bge-m3 заявка над 60 000 токена, а фиксиран брой чънкове не може да спази таван в токени — зависи от дължината им. Батчът вече се мери в знаци (EMBED_CHAR_BUDGET 55 000); прекалено голям товар се отхвърля при валидация, преди да е записано нещо.
✓
Позиционна проверка при отказ
Съдържателен отговор, който завършва с уговорка, губеше цитатите си. Маркерът се брои само ако започва в първите 200 знака — отказът води с фразата, уговорката я влачи накрая.
✓
Чат widget-ът свързан с бекенда (Фаза 4)
Custom HTML блокът вика реалния POST /chat вместо демо отговорите. Потвърдено от страната на API-то: заявки от втори посетител в usage_log, CORS работи за реалния произход и отказва останалите.
✓
Стойността на отговора във widget-а
Интерфейсът показва колко струва всеки отговор в долари, от cost_usd в тялото на отговора — същото число, което влиза в usage_log и което сумира дневният прекъсвач.
✓
Попъп с инструкции за чатбота
WPCode shortcode chk_chat_intro (PHP nowdoc): обхват на бота, поведение при липса на информация, лимити, предупреждение за лични данни. Показва се веднъж на сесия (sessionStorage), затваря се с бутон, Esc или клик извън прозореца.
✓
Целият архив в индекса
Шест източника, 1901 чънка, 3 083 871 знака — двата тома на „Чокманово“, „Родопските комити разказват“ и три статии. D1 и Vectorize се сверяват след всяко качване. Отговорите обхващат няколко книги наведнъж и цитират всяка поотделно.
✓
Разширяване до съседните откъси
Извличането по избор добавя откъса преди и след всяко съвпадение (neighbor_expansion_enabled, прозорец neighbor_window), за да не се къса разказ по средата на страница.
✓
Ограничение за разнообразие на резултатите
Таван на броя откъси от една и съща (застъпваща се) страница (diversity_limit_per_location), за да не измести една силно съвпадаща страница останалите относими източници.
✓
Речник на вариантите на имена
src/name-variants.json + флаг name_variants_enabled — съпоставя диалектни/исторически изписвания при търсене: редуване о/ъ, поколенчески наставки.
✓
Query expansion
Едно допълнително евтино повикване към Claude Haiku на въпрос, генериращо 3–5 алтернативни заявки за търсене (query_expansion_enabled).
✓
Multi-query сливане с RRF
Резултатите от оригиналната заявка и от вариантите се сливат с Reciprocal Rank Fusion.
✓
Резервирани места вместо тегла
От 8-те места 6 са запазени за оригиналната заявка и 2 за вариантите. Тегловното сливане позволяваше на вариантите да изместят точните съвпадения; резервираните места го изключват.
✓
Оценъчна среда за извличането (eval harness)
Отделен Cloudflare Worker (eval/eval-worker.ts) за сравняване на комбинации от флагове върху фиксиран набор контролни въпроси, преди да се включат в продукция. Съседните откъси и разнообразието са включени по подразбиране от 2026-09-03.
✓
Проверени еталони за оценка
Очакваното място на отговора в текста се потвърждава с SQL заявка срещу D1, преди контролният въпрос да влезе в набора.
✓
Флагване на грешен/непълен отговор
Бутон в widget-а под всеки отговор от бекенда; отваря поле за незадължителна бележка и праща доклад към нов POST /chat/flag. Не вика Claude и не пипа разходния прекъсвач.
✓
Нова таблица за докладвани отговори
answer_flags (миграция 0008) пази точния текст на флагнатия отговор — usage_log никога не пази текста на отговора, само въпроса, затова се записва само при флаг.
✓
Лимит срещу злоупотреба на flag endpoint-а
Отделна таблица flag_rate_limits със същия атомарен INSERT … ON CONFLICT … RETURNING модел като лимита на /chat, 10 доклада на IP на ден — публичен endpoint без автентикация.
✓
Преглед на флаговете в админ панела
GET/PATCH /admin/api/flags, филтри Всички/Нерешени/Решени и маркиране като „разгледан“ — списък за подобрения, изграден от реални доклади, не от предположения.
ИИ редактор (статии)
✓
Конвенция за машинно четими метаданни
WPCode снипет chk-ai-meta регистрира post meta _chk_ai и _chk_sources в REST — собствените статии стават индексируеми и от писателя, и от чатбота. Всяка нова колекция се закача за същата конвенция, не за нов механизъм.
✓
Webhook към Worker-а за статии
chk-writer-webhook вика отделния Cloudflare Worker chokmanovo-writer — държан настрана от чатбот Worker-а, за да не делят конфигурация, лимити и разходен прекъсвач.
✓
Вътрешна страница „ИИ редактор“
Снипет chk-ai-editor — работно място в админ частта за подготовка на статия по идея, без терминал и без достъп до Cloudflare.
Външни източници (Europeana)
✓
Проучване през Search и Record API
„Смолян“ е надеждната заявка: 171 записа, 170 с отворен лиценз. „Родопи“/„Rhodope“ е замърсена — най-големият доставчик е Музикална библиотека на Гърция (1248 записа за гръцка Родопа), плюс хербарни образци и римска епиграфика.
✓
Инвентар на 42-та източника от РБ Смолян
Всички TEXT, всички Public Domain Mark, част от „Дигитална колекция Паметта на Родопа планина“ — каноничните родопски източници 1884–1942, вкл. 7 вестника. Описанията са ръчно анотирани от библиотекарите със селата, които всеки източник покрива — затова заявката „Чокманово“ дава съвпадения по метаданни, не по OCR.
✓
Проверка за наличен пълен текст (IIIF)
Манифестите обявяват /search услуга, която връща 404 за всяка форма на параметрите; истинският показател е броят canvas-и — 2 значат корица и мостра, не дигитализирана книга. Search API търси само метаданни; пълният текст е зад отделните IIIF Fulltext и Newspapers API.
✓
Корекция на филтъра за лицензи
reusability=open покрива само PD, CC0, CC BY и CC BY-SA — CC BY-ND минава за „Permitted“. Постоянно включеният филтър скриваше половината от съществуващото за селото: от 8-те записа за вариантите на името 4 са иконите от „Св. пророк Илия“ (ИИИ при БАН през AMS Historica Болоня). Филтърът вече е параметър на заявката, не константа.
✓
Правна позиция
Метаданните на Europeana са CC0 — съхраними, индексируеми и публикуеми. Изображенията се хотлинкват към източника, никога не се копират в Media Library; задължително атрибуиране на институция, лиценз и линк към оригинала.
✓
Решение: една колекция, индексира се българският текст
Отхвърлени са масовият импорт на сурови записи, отделен Worker с адаптери по източник и IIIF deep-zoom (няма inode запас). Индексира се само българското описание, написано за всеки обект — каталожните стубове от 10–20 думи биха печелили съвпадения срещу 963 000-знаковия OCR корпус и биха давали отговори без цитат на том и страница.
✓
Колекция „Външни източници“
ID 6467, slug external-sources; core Title 6469, core Description 6471. Core полетата не могат да са multiple: yes — платформено ограничение на Tainacan.
✓
Схема с 14 собствени полета
orig_title, orig_desc, creator, year_text, external_id, source_url, thumb_url, media_url, rights, scope, source_type, institution, related_place (→ 4180) и related_photo (→ 961); всички са multiple: yes. Местата са релация към „Местности“, не втора паралелна таксономия.
✓
Две таксономии
source_type (ID 6475) със седем термина — изображение, книга, документ, периодика, звукозапис, видео, друго; source_institution (ID 6477) започва празна с allow_insert, термините се създават от харвестъра.
✓
Харвестър за Europeana
chk-scripts/europeana-harvest.php, пускан ръчно по SSH: –scope=village|smolyan|kaza [–apply] [–limit=N]. Сух пробег по подразбиране, реален запис само с –apply.
✓
Три обхвата и техният ред
Само вариантите на името на селото дават 8 записа, затова обхватът е разделен на три: village, smolyan (по dataProvider „Public Library – Smolyan“) и kaza (Ахъчелеби, Пашмакли, Устово, Райково, Смилян) — заедно 50–80 записа. Редът е smolyan → village → kaza: записите се застъпват и първият обхват ги заявява; обратният ред етикетира грешно.
✓
Само чернови, без път към публикуване
Харвестърът създава елементите единствено като DRAFT — в кода няма клон, който публикува. Празните чернови не се индексират и не се виждат на сайта; това е предпазителят срещу автоматично влизане на чужд каталожен текст в архива.
✓
Дедупликация през wp_option
Видените Europeana ID-та се пишат в chk_eu_seen постъпково и независимо от изхода, така че отхвърлен запис не се появява пак. Вариантите на имената и dataProvider низовете живеят в chk_eu_scopes, не в кода.
✓
Първите 30 записа от Europeana — прегледани и публикувани
И трите обхвата: village 8/8, smolyan 13/13, kaza 9 (след trash на 11 нерелевантни). Видими на сайта с оригиналното заглавие и описание от източника; собствен текст на български в Core Description предстои за всеки — виж „Предстои“.
✓
Изображения без нито един файл на диска
WPCode snippet chk-external-thumbs + споделена библиотека chk-scripts/lib/external-thumbs.php: по 2 WP attachment поста на запис, изцяло без реален файл — само мета сочещо към Europeana thumbnail proxy-то (фиксиран размер, ~400×604px). Филтри върху image_downsize/wp_get_attachment_url връщат външния URL при поискване. Изображенията вече се виждат навсякъде — grid на колекцията, item страницата, REST API — без да се пипат ограничените инодове на хостинга. Харвестърът вече ги създава автоматично за всеки нов запис, без нужда от отделен backfill.
✓
Атрибуция и защита от изрязване
Автор, институция, права и линк към оригинала под всяко изображение на item страницата. object-fit/background-size: contain навсякъде в колекцията — иконите са CC BY-ND, изрязване е дериват. Покрива и трети, отделен плъгин (Tainacan Extra View Modes), чийто изглед „Books“ рендира миниатюрата като background-image, не <img>.
✓
Открит мъртъв линк за пълноразмерните икони от BYZART
cmc.byzart.eu е мигрирал изцяло към нова платформа (Angular SPA, не сервира директно изображение) — оригиналният линк за 4-те икони вече не работи. Europeana thumbnail proxy-то остава единственото използваемо изображение за тях, 400×604 фиксиран таван.
Работен процес / инфраструктура
✓
Пряк достъп до продукцията
SSH достъп (порт 1022) + sshfs mount на хостинга, Claude Code за директна работа по файловете на живия сайт.
✓
Стабилна работна среда за Claude Code
Стартира се от локалната ~/dev/chokmanovo с –add-dir ~/sites/chokmanovo — стартът в sshfs папката води до Bun ENOENT грешки. CLAUDE.md живее локално.
✓
Скриптови промени без WP-CLI
WP-CLI липсва на хостинга, затова програмните промени в Tainacan минават през PHP CLI скрипт (8.4.25) с require wp-load.php, който вика директно \Tainacan\Repositories\… и \Tainacan\Entities\….
✓
Отделна Python среда на сървъра
Системният интерпретатор е Python 3.6.8 (EL7); проектните скриптове вървят във python3 -m venv, а не върху него.
✓
Планиране на фонови задачи без cron
На акаунта няма реален cron, а WP-Cron се задейства от посещения и е ненадежден при слаб трафик. Затова периодичните задачи (вкл. харвестъра) се пускат ръчно по SSH, вместо да се разчита на график, който не се изпълнява.
✓
Ограничение по брой файлове (inodes)
Няма запас за големи количества дребни файлове — това изключва IIIF плочки и всяко друго решение, което взривява броя файлове. Отчита се при избора на всяка нова функционалност.
✓
Правилно кодиране на URL параметри
add_query_arg() НЕ кодира аргументите, които добавя (build_query() вика _http_build_query с $urlencode = false). Кирилица, кавички и интервали влизат сурови, а nextCursor на Europeana със + или = чупи страницирането след първата страница — ползва се http_build_query() с конкатенация.
✓
Правилно броене на елементи в Tainacan
Няма общ тип tainacan-item — всяка колекция има свой tnc_col_{ID}_item. Броене по postmeta.collection_id без филтър по реалния post type връща силно завишени числа.
✓
Установени сирашки записи от изтрита колекция
20 публикувани записа с тип tnc_col_62_item при трайно изтрита колекция 62 — ранни тестови елементи от 22.07.2026, невидими в админа, безвредни (post ID-та не се преизползват).
✓
Валидацията не важи за чернови
Item::validate() се налага само за publish/future/private/pending — Repository::insert() я прескача за draft, затова черновата може да се вмъкне преди метаданните си. Item_Metadata_Entity::validate() е отделна и продължава да важи за всяко поле.
✓
Изтриване на полуготов елемент при провалена валидация
При грешка в задължително поле елементът се трие (wp_delete_post($id, true)), а не се оставя като чернова без това поле — ID-то вече е в списъка с видени и записът би се загубил безшумно завинаги.
✓
Snapshot guard преди и след скриптова промяна
Пазачът държи под наблюдение и собствените обекти, вместо да ги освобождава: освобождаването на колекцията и таксономията, в които се пише, означава непредвидена промяна там да остане незабелязана. Освобождава се само таксономията, за която се очаква да расте.
✓
Git за чатбот проекта
Версионна история на Cloudflare Workers кода.
✓
Дисциплина при промени
Резервно копие с UpdraftPlus преди всяка структурна промяна, LiteSpeed Cache purge след промяна, тестове на малки партиди преди bulk операции.
Предстои
40 задачиСледващи
•
Поправка на числовите грешки в OCR текста
Ботът стъпва строго върху разпознатия текст, затова сгрешено число става уверено изречен грешен факт: 1485 вместо 1885 в ктиторски надпис, ХХ в. вместо ХIХ в. за икони от 1830 г. Числата и римските цифри са там, където OCR е най-слаб и окото на коректора се плъзга.
•
Почистване на думи със смесена азбука
Tesseract понякога минава на латиница по средата на думата (OSMANСКО, grootелият). Откриване в целия корпус по [A-Za-z] + [А-Яа-я] в една дума — тези думи развалят ембедингите, не само четимостта.
•
Обработка на последната по-голяма книга
Тодор Хвойнев: „Моите спомени“ — 118 стр.
•
Дигитализация на още исторически източници
Цел: минимум 10 допълнителни източника, за да има чатботът достатъчно материал.
•
Български описания за публикуваните записи от Europeana
4 от 30-те публикувани записа вече имат собствен текст на български в Core Description — четирите икони от църквата „Св. пророк Илия“ (Димитриос Астериадис, 1892 г.; Спирос Михаил, 1836 г.), заснети от Георги Линков през 2005 г. за проекта BYZART. Остават 26 записа само с оригиналния текст от източника — той е и текстът, който ще влезе в индекса на чатбота, не каталожните метаданни.
•
Индексиране на публикуваните външни източници
Чънковете трябва да носят произход, за да може ботът да различава проверено местно знание от външен материал. Индексира се само българският текст, не каталожните метаданни.
•
Довършване на ИИ редактора
Генериране на форматирана статия за категория История, стъпила върху книгите и архива, с изображения и галерии; опростен режим „Новини“, в който екипът пише няколко изречения. Индексират се всички Tainacan колекции и собствените статии, за да не се появяват дублирани или противоречиви твърдения.
•
Писмо до РБ „Николай Вранчев“ — Смолян
Библиотеката държи пълните сканове на всичките 42 източника, докато в Europeana има само корици. Три искания: достъп до сканите със споменавания на Чокманово, взаимно линкване между двата архива и съвместна кандидатура по програма.
•
Карта на местностите
Кеширан GeoJSON endpoint /?chk_toponyms=1 (WPCode) + Leaflet страница с подложки OpenTopoMap и Esri World Imagery.
•
Геолокация на местностите
Координати за 6 опорни точки (петте махали + Св. Илия), останалите — по посока и разстояние от тях. Преглед на 62-те реда „спомената в текста“.
•
Повторен OCR на речника в том 2
В речниковата глава липсват страници — раздел А прескача от КАЛАМУНСКО на СИСТЕКОВО (Л, М, Н, О, П, Р). Нов OCR на тези страници с по-висока резолюция.
•
Front-end за „Дари спомен“
Публична страница за подаване + reCAPTCHA (самата колекция и workaround-ите вече са готови).
•
Дарителски канали
Акаунти в Patreon и Buy Me a Coffee.
•
Преглед на всички текстове на читалището
Редакция и актуализация на съществуващото съдържание в сайта.
Праг по релевантност при извличането
Извличането взима най-близките top_k съседа без праг по score, така че последните може да са хлабаво свързани. Праг би стеснил цитатите до наистина относимите източници.
Правило за съмнителни данни в промпта
Правило 4 лови информация, която липсва; правило 7 бута модела да обяснява връзките между фактите. Заедно значат, че повреден източник не създава видима дупка, а добре написано погрешно твърдение. Ново правило би искало от модела да отбележи очевидно противоречив факт, вместо да го помирява.
Отрязани отговори при широки въпроси
С шест източника и top_k 8 широк въпрос може да опре в тавана от max_tokens 2048 и да се среже по средата на изречението. Досега 1 от 27 отговора при средно 562 токена. Вариантите са по-висок таван (решение за пари), по-меко правило 7 или по-нисък top_k.
Разграничаване на съименници в чатбота
Двама различни Райчо Чакорови (дядо и внук) засега не могат да се разграничат в отговорите.
Cloudflare Turnstile за чатбота
Проверка за реален потребител при отваряне на чата; сесиен токен (HMAC, 60 мин.), проверяван в Worker-а преди всяко извикване на Claude API.
Живи камери в селото
1–2 уеб камери, стрийм в YouTube, вградени на сайта.
Аудио прикачени файлове в История
Възможност постовете да включват звукозапис.
Допълнително втвърдяване на входа за родословието
По-нататъшно подобряване на текущата система с 6-цифрен код + HMAC бисквитка.
Обединяване на родовете в един граф
164-те отделни дървета от износа — в единен граф.
Речник на имената за OCR корекция
Лексикон от имената в GEDCOM корпуса за автоматична корекция на разпознатите имена.
Демографски портрет
Обобщена статистика върху родословната база — първа публична функционалност от нея.
Калкулатор на родство
Определяне на връзката между две лица с българската роднинска терминология.
Карта на миграциите
Върху Leaflet слоя на картата на местностите.
Печатен семеен лист
PDF лист за лице/род.
Колекция „Личности“ в Tainacan
Native колекция, захранваща визуализацията през REST API; кандидат-материал са портретите с имена и години (1882–1930) от Фондация NALIS в Europeana, всички с Public Domain Mark. Дотогава релационното поле „Свързана личност“ в „Външни източници“ сочи само към колекция 961.
Микросайт „За читалището“
Вертикална странична навигация (Gutenberg Columns 30/70, Synced Pattern) със секции: Библиотека, Кафе-читалня, Дистанционна работа, Събития, Кино салон, Екип, Уъркшопи — списъкът със секции предстои да се потвърди.
Правни страници
Политика за поверителност, политика за бисквитки, условия за ползване, лиценз CC BY-NC-SA за съдържанието.
GDPR съответствие
План за анонимизация на живи лица в родословните данни.
Мониторинг и тест за възстановяване
Cloudflare Web Analytics, UptimeRobot, тримесечен тест за възстановяване на UpdraftPlus копие в локален Docker.
OAI-PMH износ на собствените колекции
Обратната посока към Europeana — колекциите на читалището да бъдат обхождани от агрегатори. Националният агрегатор за България е библиотеката във Варна; директното кандидатстване не е пътят.
Страница с отворени данни
Документирани Tainacan REST endpoints + износ в CSV/JSON.
Миграция на нов домейн
Преместване на целия сайт от текущия адрес.
NER извличане на имена
Автоматично извличане на имена/дати от OCR текста за захранване на родословието.
Решение за сирашките записи от колекция 62
20 записа tnc_col_62_item от 22.07.2026 — почистване или оставяне; отделно решение, не част от функционална работа.
Git репозиторий за целия сайт + локална Docker среда
Git вече покрива RAG чатбот проекта (Cloudflare Workers); липсва версионна история и тестова среда за самия WordPress сайт.
Автоматизирани тестове (Playwright)
Проверка на критичните функции при всяка промяна.
Технически стек
CMSWordPress + Blocksy
АрхивTainacan + REST API
ФормуляриWPForms Lite
Код/snippetsWPCode
КешLiteSpeed Cache
Резервни копияUpdraftPlus
OCRPyMuPDF + Tesseract (bg)
RAG износragexport.py
РодословиеGEDCOM парсър (PHP)
Родословие — вход6-цифрен код + HMAC бисквитка
Родословие — дървованилен JS + SVG
МестностиTainacan GeoCoordinate
Карти (план)Leaflet + OpenTopoMap / Esri
Чатбот — инфраструктураCloudflare Workers + D1 + Vectorize
LLM (генерация)Claude Haiku 4.5
EmbeddingsWorkers AI (bge-m3)
Извличанеmulti-query + RRF
Чатбот — индекс6 източника · 1901 чънка
Чатбот — админ гейтCloudflare Zero Trust Access
Чатбот — лимити$2/ден · 20 заявки/IP
СтатииWorker chokmanovo-writer
Външни източнициEuropeana Search / Record API
ХарвестърPHP CLI 8.4.25 (ръчно по SSH)
ХостингSuperHosting
ВремеOpen-Meteo API
