Независимый аудит · 7 октября 2026

Алгоритм подбора вакансий VigGig: заключение по материалам и прогону mass-retrieval-v1

Проверено: mass-retrieval-v1 (13 файлов), compact-matching-v2_12, context-extraction-v1 Не открывалось: fresh-control-candidates.json, corpus.sqlite, config.json MiniLM, v2_3/reference.json Код, данные и разметка не менялись; ничего не обучалось и не запускалось
Вердикт: изменить

Схему retrieve → rerank не отвергать, но переставить приоритеты. По файлам видно, что retrieval уже даёт тематически чистый верх (в top-20 нет ни одного NO, 15 из 20 прямо про WordPress). Главная потеря не там: 11 из 20 кандидатов остались UNCERTAIN, и готовый cross-encoder эту долю не уменьшит, потому что он обучен на релевантности «запрос–пассаж», а не на обязательных требованиях и отрицаниях.

Сначала починить измерение, потом выбирать модель. Текущий baseline сравнения не ранжирует по тегам, а фактически выдаёт 20 самых свежих вакансий; результат «9 FIT против 1» ничего не говорит о пользе семантики. Все 40 оценок поставил автор прогона, и внутри них есть расхождения в одинаковых случаях.

Рекомендуемый путь: независимый гибрид BM25 + локальный dense по всему допустимому корпусу как кандидатная лента; разметка владельцем профиля (не агентом) в трёх состояниях «откликнусь / уточню / нет»; закрытый набор «блокеров» для данной L2 как отдельный узкий классификатор на уровне предложений; состояния профиля «не умею / не указывал / не хочу» из онбординга. Reranker подключать только если эксперимент покажет, что узкое место именно retrieval.

1 · Вердикт

Почему «изменить», а не «продолжить» и не «заменить»

Не «продолжить»: предложение §8 отвечает на вопрос «как лучше ранжировать похожие тексты», а нерешённый вопрос проекта (§9, первый пункт) звучит иначе: «как отличить тематическую релевантность от соответствия обязательным требованиям». Cross-encoder повышает первое и почти не трогает второе. Подключать его сейчас значит оптимизировать ту часть, где по собственным данным проекта потерь меньше всего.

Не «заменить»: инфраструктура прогона сделана аккуратно: параметры и shortlist заморожены до просмотра, цитаты проверяются побайтно, контроль не читался, ограничения перечислены честно. Эту дисциплину надо сохранить. Меняется состав следующего шага и источник разметки, а не вся линия.

Что меняется: (1) baseline и временные окна в протоколе сравнения; (2) разметчик: владелец профиля вместо агента; (3) цель следующей модели: не «лучше ранжировать», а «найти предложения-блокеры из закрытого списка»; (4) часть неопределённости переносится из модели в профиль через явные состояния.

2 · Проблемы

Обнаруженные проблемы: факт из файла, влияние, исправление

Все факты ниже проверены по загруженным файлам. Строки с пометкой «не проверено» означают, что файл в среде аудита отсутствовал.

#Факт из файлаВлияниеИсправление
1Baseline не ранжирует по тегам. run.py: overlap = |SKILLS ∩ tags| / |tags|. В baseline.jsonl все 200 строк имеют tag_overlap = 1.0; в top-20 у 12 вакансий ровно один тег (JavaScript, HTML5, WordPress). Поэтому порядок задаёт только свежесть: baseline top-20 — это 20 самых новых вакансий от 3–5 октября, у которых все теги попали в список из 14 навыков.критично Сравнение «9 FIT против 1» измеряет не семантику против тегов, а «лучшее совпадение за месяц» против «самое свежее за два дня». Вывод REPORT.md о превосходстве метода не обоснован этим сравнением.Baseline должен быть сильным текстовым: BM25 по тем же шести запросам (без эмбеддингов). Если нужен тег-baseline, нормировать на число навыков профиля и требовать ≥2 совпадений.
2Разные временные окна. Даты top-20 кандидатов: 7 сентября — 1 октября (ни одной за 2–5 октября). Даты top-20 baseline: 3–5 октября. У вакансии C10 дедлайн «September 12» уже прошёл на момент прогона.критично Месячный top-20 не моделирует ежедневную ленту; в реальной выдаче пользователь получит устаревшие вакансии. Протокол сам это признаёт (§9), но сравнение всё равно сделано на разных окнах.Оценивать по окнам: 4 недельных среза корпуса, top-10 на срез для каждой системы. Свежесть должна входить в выдачу как окно, а не как разрешение равенств.
3Единственный разметчик — агент, и метки внутри себя расходятся. review.py: reviewer = assistant_internal_author, метки захардкожены списком. C3 и C7 («contact form not sending») = FIT, но C12 (та же задача, в тексте есть слово «SMTP») и B1 («WooCommerce emails not sending») = UNCERTAIN. C18 (Figma → WordPress, 7 страниц, готовый дизайн) = UNCERTAIN из-за строки «SEO plugin configuration». C4 («Keep ElasticSuite», модуль только для Magento, слова WordPress нет) = UNCERTAIN, хотя это чужая платформа.важно Счёт FIT при той же выдаче колеблется от 8 до 12 в зависимости от строгости; доля UNCERTAIN завышена осторожностью разметчика, а не свойствами вакансий. Разметка не годится как ground truth даже для внутреннего решения.Разметчик — владелец реального профиля (§4), вслепую, в случайном порядке, тремя метками «откликнусь / уточню (что именно) / нет (причина)». Метки агента оставить вторым проходом и считать согласие.
4Семантика ограничена лексическим shortlist и фактически не влияет на состав. Pool = 1 048 из 8 753 допустимых. В pool 265 вакансий с флагами, из них 237 — other_platform (Shopify, Webflow, Wix и т.п.).важно До 23 % shortlist — вакансии про другие платформы, а WordPress-вакансии вне лексического top-200 по каждому запросу не могут быть найдены ничем. Recall по корпусу неизвестен и этой схемой неизмерим.Независимый dense-поиск по всем 8 753 (по измеренной скорости прогона 1 048 текстов укладываются в 102 с вместе с индексом; весь допустимый срез — порядка 10–15 мин на той же машине, однократно). Объединять ранги BM25 и dense через RRF до любого shortlist.
5Длинные ТЗ усредняются. encode(): чанки по 254 токена без перекрытия, взвешенное среднее. В pool 429 текстов длиннее ~1 000 символов, 117 длиннее 3 000. C5 (3 147 символов) содержит «Senior-Level Experience Required… do not apply» в конце.важно Критическое условие в хвосте размывается в векторе документа и не влияет на ранг. Это одна из причин, почему UNCERTAIN не ловятся автоматически.Для совместимости работать на уровне предложений/абзацев, а не документа. Для retrieval можно оставить среднее, но хранить и max-chunk score как диагностический признак.
6Regex-флаги одновременно слабы и шумны. В top-20 флагов 0 при 11 UNCERTAIN; в top-100 — 15; в pool — 265, из них 237 срабатывают на одно упоминание платформы.важно «Нет флага» ничего не значит, что REPORT.md корректно констатирует. Но это также показывает: закрытый список причин несовместимости существует (Divi, ElasticSuite/Magento, SMTP-инфраструктура, senior SQL, дизайн с нуля, SEO как услуга, performance как услуга), просто его нельзя ловить регулярками по упоминанию.Заменить regex на классификатор предложений с закрытым набором классов-блокеров (см. раздел 4). Упоминание ≠ требование: обучать на предложениях, где слово стоит в позиции требования.
7v2.12 обучался на 13 вакансиях. training.json: все головы — 13 jobs, ~200 строк; голова input — 9 положительных, optional — 15, negated — 11. results.json: input даёт 0 TP / 5 FN у tfidf и 1 TP у MiniLM; dev-оценка на 10 документах; интервалы для диагностических голов от −0,6 до +0,5.важно Именно различие «вход / результат» (§5 задания) нынешними данными не выучивается в принципе. Выводы «улучшение mandatory +13 п.п.» статистически слабы и не переносятся на полный документ. Агент в REPORT.md это сам пишет; но в задании (§6) результат подан мягче, чем он есть.Не продолжать эту ветку как есть. Либо резко сузить число классов (блокеры), либо добирать сотни документов, что по §3 недоступно.
8context-extraction — полноценная онтология. structured-pilot.json: 57 claims на 3 документа; SCHEMA-NOTES.md: связи related нетипизированы, составные объекты не разделены; status.json: 6 блокеров до обучения, модель не выбрана.отложено верно Это путь к extractor-у общего назначения; он требует сотен размеченных документов и второго разметчика. Решение приостановить правильное.Переиспользовать только словарь классов требований и уже размеченные предложения как seed для блокер-классификатора. Не завершать 76 разметок.
9Запросы — ручные списки ключевых слов для одного профиля. QUERIES в run.py: шесть строк вроде «WordPress custom theme development convert supplied Figma PSD…». Теги профиля SKILLS тоже вручную, с вариантами css 3/css3.важно Схема не переносится на другой профиль без ручной работы; это противоречит цели «без массового ручного описания». Нормализация тегов ненадёжна (у C2 в тегах React, в тексте нет).Строить запросы из подтверждённых направлений онбординга автоматически (одно направление = один запрос, текст направления уже есть в profile_positive_text). Теги использовать только как мягкий признак.
10Защита контроля: только точные ID/hash. run.py и verify.py читают из защищённого файла лишь job_id и content_hash; near-duplicate не проверяются (сказано в freeze.json/limitations).принято Для первичного аудита достаточно. Для следующего эксперимента недостаточно: переопубликованные ТЗ на Upwork частое явление.Перед разметкой исключать near-dup по косинусу MiniLM ≥ 0,95 или MinHash по 5-граммам к защищённым и к 76 документам инвентаря.
11Что сделано хорошо (чтобы не потерять): freeze.json пишется до просмотра; verify.py проверяет хеш кода, литеральность цитат, отсутствие контроля, монотонность рангов; REPORT.md прямо отказывается считать 45 % точностью и «нет флага» проверкой.сохранитьПеренести этот же набор проверок в протокол следующего эксперимента без изменений.
12Не проверено: corpus.sqlite (полнота полей, доля WordPress в допустимом срезе), hybrid-embeddings/config.json, review.html (содержимое совпадает с jsonl по коду генерации), compact-matching-v2_3/training-packet/reference.json, закрытый контроль (намеренно).— Утверждения о recall и о распределении корпуса в этом заключении опираются только на цифры summary.json.—
3 · Сравнение

Архитектуры, уместные на этом этапе

Оценка по четырём осям из задания. «Ручная подготовка» — что нужно от людей до первого результата; «риск» — чем это ломается на ваших данных. Опубликованные характеристики моделей отделены от ожиданий: ни одна из них не измерена на вакансиях.

ПодходЧто решаетРучная подготовкаСтоимостьГлавный рискСейчас
BM25 + dense (MiniLM) по всему срезу, RRFRetrieval: находит тематически близкое вне лексического top-200, честный baselineНет новой разметки; запросы = направления онбордингаОдин индекс на всех пользователей; dense для 110 тыс. однократно (часы CPU), далее по приростуНе отличает «нужно Divi» от «есть Divi»; это и не его задачаделать
Cross-encoder rerank (bge-reranker-v2-m3: Apache-2.0, 0,6 B параметров, многоязычный, обучен на query–passage релевантности; лёгкая альтернатива ms-marco-MiniLM-L6-v2: Apache-2.0, 22,7 M, английский, MS MARCO)Ранжирование внутри shortlist при длинных и расплывчатых запросахНет разметки для запуска; нужна разметка, чтобы доказать пользуЗависит от пары профиль–вакансия: не кешируется на вакансию. Оценка: 1 000 пользователей × 100 пар/день = 100 тыс. пар; 22 M-модель — десятки CPU-минут, 0,6 B — часы CPU или GPU (порядок величины, не измерение)Поднимает тематически похожее; отрицания в запросе («no React») не гарантируют штрафа. Опубликованные бенчмарки (BEIR, MIRACL) не про требования к исполнителюпосле эксперимента
Закрытый набор блокеров: классификатор предложений, 8–12 классов для L2 (платформа-назначение, фреймворк обязателен, оплата/доставка, дизайн с нуля, SEO/маркетинг как услуга, builder-специфика Divi/Bricks, серверные/безопасность/БД-операции, email-инфраструктура, дежурство/on-call)Compatibility: отделяет требование от упоминания, даёт пользователю конкретное «что проверить»~300–500 предложений: кандидаты набираются автоматически (слова-триггеры из flags() и v2.12-словарь), человек подтверждает «требование / упоминание / вход»; 2–3 часаОдин прогон на вакансию для всех пользователей одной L2, кешируетсяДлинный хвост причин вне списка; проверяется долей NO-причин «другое» в разметке владельцаделать
Zero-shot NLI на предложениях (deberta-v3-base-zeroshot-v2.0: MIT, 0,2 B, английский, 512 токенов, гипотеза задаётся текстом)То же, что блокеры, но без обучения: гипотезы «исполнитель должен владеть Divi» vs «у заказчика есть Divi»Только формулировки гипотез; разметка для оценкиN предложений × K гипотез на вакансию; кешируется на вакансиюКачество на коротких деловых фразах не опубликовано; английский только; легко переоценить по демокак запасной вариант к блокерам
Learning-to-rank на простых метках (LightGBM LGBMRanker)Объединяет BM25, dense, свежесть, блокеры, теги в один порядокНужны сотни пар с метками «откликнусь/нет» на несколько профилейДёшево в inferenceНа одном профиле переобучится; метки агента нельзя использоватьпосле 3+ профилей
SPLADE / ColBERTКонкретные потери retrieval на редких терминах или длинных текстахНетИндекс в разы тяжелее denseРешают проблему, наличие которой ещё не показанопреждевременно
Полное извлечение фактов / граф / Two-TowerУниверсальное понимание ТЗСотни документов, два разметчика, типизированные связиМесяцыПо §3 ресурсов нет; v2.12 показывает, что мелкие роли не выучиваются на малых данныхпрекратить
Про dense-модель. Текущая all-MiniLM-L6-v2 (Apache-2.0, 22,7 M, английский, усечение после 256 word pieces) достаточна для retrieval-этапа, и её уже проверили в вашем runtime. Менять энкодер ради «большей мощности» не рекомендую: это не причина ошибок в проверенных файлах.
4 · Архитектура

Рекомендуемая архитектура и минимальный рабочий вариант

A. Доступ и окно

  • Фильтр L1/L2 по подписке и явным настройкам (как сейчас).
  • Окно свежести задаётся продуктом: «за сутки / за неделю». Ранжирование внутри окна.

B. Retrieval

  • BM25 по полному тексту + skills; один запрос на подтверждённое направление онбординга.
  • Dense (MiniLM) по всем допустимым вакансиям, независимо от BM25.
  • RRF внутри направления, затем максимум по направлениям (как сейчас, это правильно).

C. Блокеры (на вакансию, один раз)

  • Разбиение на предложения; классификатор закрытого набора: класс + «требование / упоминание / вход» + цитата.
  • Хранится вместе с вакансией; переиспользуется для всех пользователей L2.

D. Профиль (на пользователя)

  • К каждому классу блокеров три состояния: умею / не хочу / не указывал. Задаются в онбординге из короткого списка, собранного по частоте в корпусе L2.
  • «Не указывал» никогда не превращается в «не умею» молча.

E. Политика ленты

  • Основная: нет блокеров-требований, либо все они в состоянии «умею».
  • Уточнить: есть блокер-требование в состоянии «не указывал»; показывается с цитатой и вопросом.
  • Скрыто: блокер-требование в состоянии «не хочу / не умею». Доступно по кнопке «показать скрытые».

F. Reranker (условно)

  • Подключается только если эксперимент (раздел 5) покажет потери на этапе B.
  • Никогда не используется как доказательство выполнения требований.

Минимальный рабочий вариант

A + B + E без C и D, где роль блокеров временно играет честно подписанный список «проверьте в ТЗ»: предложения с конструкциями требования (must / required / strong experience with / do not apply if) и термином вне подтверждённых навыков профиля. Это уже лучше нынешних флагов, потому что опирается на позицию требования, а не на упоминание, и не удаляет ничего автоматически. Такой вариант можно показывать как «кандидаты по вашим направлениям» уже сейчас.

Почему это отвечает на главный вопрос задания

Разделение «тематическая релевантность» / «обязательные требования» делается двумя разными механизмами с разными единицами: документ для retrieval, предложение для совместимости. Ручной каталог заменяется коротким закрытым списком классов на L2 (десяток, а не сотни), а остальная неопределённость честно уходит в состояние «уточнить» и в вопрос пользователю, а не в правило.

5 · Эксперимент

Один ограниченный эксперимент

Гипотезы

  • H1 (retrieval). Независимый гибрид BM25 + dense по всему срезу находит в недельном top-10 больше вакансий, на которые владелец профиля откликнулся бы, чем BM25 по тем же запросам, и не больше явных «нет».
  • H2 (reranker). Cross-encoder поверх гибрида добавляет ещё «откликнусь» в top-10. Если нет, reranker откладывается.
  • H3 (блокеры). Причины «нет» и «уточню» у владельца профиля в ≥70 % случаев попадают в закрытый список классов. Если нет, закрытый набор недостаточен.

Варианты

S0: BM25 (шесть запросов, максимум по направлениям). S1: S0 + dense по всем 8 753, RRF. S2: S1 + bge-reranker-v2-m3 на top-100 каждого направления. Параметры, запросы, версии моделей и хеш кода фиксируются в freeze.json до начала разметки, как в v1.

Единица оценки и выборки

  • Единица: пара «вакансия–профиль».
  • Пул выдачи: корпус режется на 4 недельных окна (5 сентября — 5 октября); для каждой системы top-10 на окно; объединение без дублей. Ожидаемо 150–250 вакансий.
  • Проба на recall: 120 случайных вакансий из допустимых 8 753 вне пула, стратифицированных по L2. Даёт оценку доли «откликнусь» в корпусе и порядок величины recall каждой системы. С такой выборкой интервал широкий; цель — отличить «1 %» от «10 %», не точность до процента.
  • Защита от утечки: исключить защищённый контроль по ID/hash и near-dup (косинус ≥ 0,95 или MinHash), исключить 76 документов инвентаря и их near-dup; никакой настройки после получения меток.

Разметка

Разметчик — владелец профиля. Вакансии в случайном порядке, без указания системы и ранга. Три метки: откликнусь / уточню (что именно, одной фразой) / нет (причина из списка классов + «другое»). Оценка объёма: 270–370 вакансий по 20–30 секунд — 2–3 часа, можно в два приёма. Второй проход агентом по тем же вакансиям вслепую; считать согласие (κ) и расхождения публиковать.

Метрики

  • «Откликнусь»@10 и «Нет»@10 по окнам, среднее по 4 окнам, парно между системами.
  • Доля «уточню» в top-10.
  • Оценочный recall: найдено «откликнусь» / (доля в пробе × 8 753).
  • Распределение причин «нет» и «уточню» по классам; доля «другое».

Критерии, фиксируемые до запуска (предлагаемые значения)

  • H1 принята, если S1 даёт в среднем на ≥1,0 «откликнусь»@10 больше S0 и не больше «нет»@10. Четыре окна не дают статистической значимости; это правило решения, и оно объявляется таковым.
  • H2 принята, если S2 добавляет ещё ≥1,0 «откликнусь»@10 к S1 и измеренная задержка на 100 пар укладывается в заранее названный бюджет на пользователя.
  • H3 принята, если «другое» ≤30 % причин.

Лимит итераций и действия при провале

  • Один прогон плюс один повтор только для исправления технической ошибки. Общее время владельца профиля ≤3 часов.
  • H1 не подтверждена: retrieval не узкое место; dense остаётся, работа над ранжированием прекращается, бюджет уходит в блокеры и профиль.
  • H2 не подтверждена: reranker откладывается без второй попытки с другой моделью.
  • H3 не подтверждена: закрытый список расширяется один раз по фактическим причинам; если и после этого «другое» >30 %, переход к zero-shot NLI на предложениях, а не к новым правилам.

Отдельная проверка для reranker: видит ли он блокер

Не требует новой разметки. Берутся ≥20 вакансий с меткой «откликнусь»; к каждой добавляется одно предложение-блокер («Strong Divi Theme Builder experience required») либо, в контроле, нейтральное предложение той же длины. Сравнивается сдвиг ранга. Затем обратное: из вакансий «нет» удаляется предложение-причина. Если медианный сдвиг ранга от блокера неотличим от контроля, reranker не чувствителен к обязательным требованиям и в схеме может отвечать только за тематику.

6 · Действия

Что делать сейчас, что отложить, что прекратить

Сейчас 1–2 недели

  • Исправить протокол сравнения: BM25-baseline, недельные окна, независимый dense.
  • Договориться с владельцем профиля о 2–3 часах разметки; подготовить слепую форму.
  • Собрать закрытый список блокеров для L2 Web/Ecommerce из частот в корпусе и из причин разметки.
  • Добавить в онбординг состояния «умею / не хочу / не указывал» для этого списка.
  • Провести эксперимент раздела 5.

Отложить до результатов

  • Cross-encoder любой модели: до итога H1/H2 и измерения задержки.
  • LTR и поведенческие сигналы: до 3+ профилей с метками владельцев.
  • Смена dense-энкодера.
  • SPLADE / ColBERT.

Прекратить не возвращаться

  • Дообучение голов v2.x на 13 документах (v2.12 сам это закрыл; подтверждаю).
  • Завершение 76 полных разметок как условие чего-либо.
  • Новые regex-правила по результатам просмотра.
  • Использование меток агента как ground truth для решений о модели.

Что можно запускать как продукт до решения compatibility

Лента «Кандидаты по вашим направлениям» в недельном или суточном окне, с указанием направления и цитаты, по которой вакансия попала, и блоком «проверьте в ТЗ» с цитатами предложений-требований. Интерфейс пока не должен: показывать процент совпадения или слово «подходит»; обещать, что все требования проверены; молча скрывать вакансии; приписывать пользователю навыки по тегам. Кнопка «не подходит» с выбором причины из закрытого списка начинает копить тот самый шумный сигнал, который потом пригодится для LTR, но сейчас ничего не меняет в ранжировании.

7 · Неизвестные

Неизвестные, которые действительно меняют решение

  • Метки владельца профиля. Если «откликнусь» среди нынешних UNCERTAIN окажется большинство (SMTP, Core Web Vitals, SEO-плагин — это рутинные задачи для WordPress-разработчика), то проблема в основном в профиле и в осторожности разметчика, и блокер-слой можно сделать совсем тонким. Если большинство окажется «нет», закрытый список нужен шире и раньше.
  • Доля релевантных в допустимом срезе. При 1–2 % retrieval должен быть очень точным, и rerank становится важнее; при 10 % и выше достаточно BM25 + dense, а ценность смещается к свежести и блокерам.
  • Доля причин «нет» вне закрытого списка. Решает между блокер-классификатором и zero-shot NLI.
  • Число пользователей и SLA. Решает, допустим ли вообще cross-encoder на CPU. Пока их нет, любые цифры задержки — сценарии.
  • Поля других источников. Если у Vollna или следующей площадки нет тегов и L2, схема должна работать на одном тексте; BM25 + dense + блокеры это выдерживают, тег-baseline нет.
  • Второй профиль. Один WordPress-сценарий не проверяет перенос запросов из онбординга и состава блокеров. Достаточно одного контрастного профиля (например, Shopify-разработчик в той же L2), чтобы увидеть, что из списка блокеров общее, а что профильное.

При каких условиях я сменил бы рекомендацию

  • Если H1 покажет, что S0 и S1 теряют заметную долю «откликнусь» из пробы на recall, а S2 её находит, reranker становится первым приоритетом, и тогда стоит измерять bge-reranker-v2-m3 против лёгкой 22 M-модели по задержке.
  • Если причины «нет» распределены по длинному хвосту без закрытого набора, узкий классификатор не оправдан; тогда уместнее NLI на предложениях, а при его провале — возвращение к ограниченному extractor-у, но уже с внешней разметкой.
  • Если владелец профиля размечает иначе, чем агент, более чем в трети случаев, все прежние внутренние цифры (включая v2.12) нужно считать недействительными, а не «смещёнными».
Приложение

Ответы на обязательные вопросы задания

№ВопросОтвет
1Верно ли поставлена цель?Цель «полезная лента, а не предсказание найма» верна. Но в разметке проект уже пытается оценить скрытые способности: UNCERTAIN ставится за SMTP, Core Web Vitals, настройку SEO-плагина, которых в профиле просто нет. Это не задача модели; это вопрос пользователю. Разделить состояния «не умею / не указывал / не хочу» в профиле нужно, и это дешевле любой модели.
2Какие выводы обоснованы, а какие чрезмерны?Обоснованно: retrieval воспроизводим; flags не решают compatibility; классифицировать заданный фрагмент проще, чем найти его. Чрезмерно: «9 FIT против 1» как свидетельство пользы метода (baseline = свежесть); «устойчивый рост MiniLM-голов» (13 обучающих документов, 10 проверочных); разметка 40 оценок как основание для чего-либо кроме демонстрации.
3Причины ошибокRetrieval: неизмерим, shortlist ограничен лексикой, 23 % pool про другие платформы. Ranking: в top-20 тематических ошибок нет; свежесть не учтена. Compatibility: главный источник UNCERTAIN (Divi, ElasticSuite, senior SQL, on-call). Профиль: вторая половина UNCERTAIN (SMTP, SEO-плагин, performance). Разметка: один автор, расхождения в одинаковых случаях. Один энкодер не лечит ни одну из этих причин.
4Сравнение альтернативРелевантны сейчас: BM25 + dense (retrieval), закрытый набор блокеров (compatibility), zero-shot NLI как запасной вариант. Условно: cross-encoder. Преждевременны: SPLADE/ColBERT, LTR, граф, полное извлечение, Two-Tower.
5Готовые моделиПеречислены в разделе 3 с лицензией, размером, языком и назначением по карточкам Hugging Face. Ни у одной нет опубликованных результатов на подборе вакансий; ожидания на ваших данных не заявляются.
6Видит ли reranker обязательное неподходящее требование?Скорее нет: обучен на релевантности query–passage, не на противоречиях с профилем. Проверка без новой разметки описана в разделе 5 (вставка/удаление предложения-блокера и сдвиг ранга).
7Минимальная ручная работа2–3 часа владельца профиля на 270–370 вакансий; 2–3 часа подтверждения 300–500 предложений для блокеров. Переиспользовать: словарь классов v2.x, уже размеченные предложения, протокол заморозки v1. Обязательно человеком: метки «откликнусь/нет» и подтверждение блокера как требования. Нельзя считать достоверными: метки агента, флаги regex, отсутствие флага.
8Один экспериментРаздел 5.
9Что запускать сейчасРаздел 6: кандидатная лента с цитатами и блоком «проверьте в ТЗ», без процентов и слова «подходит».
10Собственный путь и условия сменыРазделы 4 и 7.

Проверенные факты по файлам (для воспроизведения)

ПоказательЗначениеИсточник
Строк baseline с tag_overlap = 1.0200 / 200baseline.jsonl
Вакансий с одним тегом в baseline top-2012 / 20baseline.jsonl
Диапазон дат, top-20 кандидатов / top-20 baseline07.09–01.10 / 03.10–05.10candidates.jsonl, baseline.jsonl
Упоминание WordPress: top-20 / top-100 / pool15 / 76 / 638candidates.jsonl
Флаги: top-20 / top-100 / pool; из них other_platform0 / 15 / 265; 237candidates.jsonl
Тексты pool длиннее 1 000 / 3 000 символов429 / 117candidates.jsonl
Обучающих вакансий v2.12 (все головы)13training.json
Положительных строк head input / optional / negated9 / 15 / 11training.json
Head input на dev: TP / FN (tfidf)0 / 5results.json
Инвентарь / полный пилот / структурный пилот / claims76 / 6 / 3 / 57status.json (context-extraction)