4 из 122 мин

Как я сохранил мозг системы и заменил ей транспорт

После закрытия соискательского API стало ясно, на чём вообще держится проект.

Если бы его логика была пришита к одному способу доступа к HH, систему пришлось бы выбросить.
Но к этому моменту у проекта уже был собственный внутренний каркас. Поэтому менять пришлось не смысл, а только транспорт.

Это хорошо видно по моделям, через которые проект описывает свою работу:

class VacancyCard:
    vacancy_id: str
    title: str
    company: str
    url: str

class VacancyDetails:
    vacancy_id: str
    title: str
    company: str
    description_text: str
    apply_available: bool

class ApplyDraft:
    vacancy_id: str
    resume_id: str
    cover_letter: str

class ApplyResult:
    vacancy_id: str
    success: bool
    status: str

class PrepareApplyOutcome(str, Enum):
    PREPARED_DRAFT = "PREPARED_DRAFT"
    NEEDS_HUMAN = "NEEDS_HUMAN"
    SKIP = "SKIP"
    RETRYABLE_TRANSIENT = "RETRYABLE_TRANSIENT"
    RATE_LIMITED_EXTERNAL = "RATE_LIMITED_EXTERNAL"

VacancyCard - это краткая модель вакансии на уровне поисковой выдачи.
vacancy_id - внутренний опорный идентификатор, через который система связывает поиск, отклик, логи и антидубли.
title - название вакансии, с которым дальше работает классификация и логика релевантности.
company - работодатель, чтобы проект понимал контекст и фиксировал, куда именно идет отклик.
url - ссылка на вакансию, через которую транспорт открывает карточку уже в браузере.

VacancyDetails - это та же вакансия, но уже после полного открытия и извлечения содержимого.
description_text - полный текст вакансии, на котором строятся анализ, письмо и ответы на вопросы.
apply_available - флаг, который сразу отделяет живую вакансию от уже недоступной для отклика.

ApplyDraft - это уже не вакансия, а подготовленный черновик конкретного отклика.
resume_id - указание, каким именно резюме система должна отвечать на эту вакансию.
cover_letter - готовый текст сопроводительного, собранный под конкретную позицию, а не взятый из шаблона.

ApplyResult - это финальная фиксация того, чем закончилась попытка отклика.
success - бинарный итог, который позволяет быстро отделить удачный проход от неудачного.
status - более точный слой результата, чтобы система различала не просто успех и провал, а конкретный исход.

PrepareApplyOutcome - это самая важная часть, потому что здесь система различает типы состояний до финального submit.
PREPARED_DRAFT - черновик отклика собран и система может двигаться дальше.
NEEDS_HUMAN - страница или форма требуют ручного решения и не должны ломать весь pipeline.
SKIP - вакансия осознанно отбрасывается как нерелевантная или неподходящая.
RETRYABLE_TRANSIENT - произошёл временный технический сбой, после которого можно пробовать снова.
RATE_LIMITED_EXTERNAL - внешний контур HH начал ограничивать процесс, и системе нужен cooldown, а не агрессия.

Вся ценность этого кода в том, что он описывает не интеграцию, а внутренний мир проекта: сущности, этапы и исходы.
Поэтому после закрытия API проект не пришлось пересобирать с нуля — старый транспорт исчез, но логика, модели и state остались.

Оглавление серии
  1. Автоматизация отклика как ответ на асимметрию рынка
  2. Как бот сравнивает меня с вакансией и собирает отклик под нее
  3. Почему закрытие соискательского API не убило проект, а заставило его повзрослеть
  4. Как я сохранил мозг системы и заменил ей транспорт
  5. Самое трудное в автоотклике, это не submit
  6. Автоотклик в видимом режиме
  7. Как LLM в моем проекте превращает вакансию в сопроводительное письмо
  8. Почему полная автономия оказалась слабее управляемой автономии
  9. Я дал системе не только действия, но и тормоза
  10. Как проект живёт после отклика: статусы, приглашения, переписки и ручной dashboard
  11. Dashboard
  12. Самая неожиданная вакансия, которую нашел мой бот