250 откликов за 20 минут: как я автоматизировал процесс ответов на вакансии

в 13:24, , рубрики: chatgpt, large language models, ruvds_перевод, автоматизация, вакансии, поиск работы, порталы по поиску работы, резюме, собеседования

250 откликов за 20 минут: как я автоматизировал процесс ответов на вакансии - 1


Будем откровенны: поиск работы — это отстой.

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

Обратим внимание на следующее: повторяющиеся задачи + структурированный процесс = идеальный кандидат для автоматизации.

Поэтому я поступил так, как поступил бы любой разработчик в здравом уме — создал систему автоматизации всей этой фигни. В конечном итоге я смог разослать 250 откликов на вакансии за 20 минут. (Ирония заключается в том, что я получил оффер ещё до того, как закончил создавать эту систему. Подробнее об этом ниже.)

В статье я расскажу, как я это сделал.

Процесс отклика на вакансии поломан

Задумайтесь — каждый отклик на вакансию представляет собой один и тот же простой паттерн:

  1. Находим публикацию о вакансии.
  2. Проверяем, есть ли у нас нужные навыки.
  3. Изучаем информацию о компании (будем откровенны: большинство людей пропускает этот шаг).
  4. Отправляем резюме + сопроводительное письмо.
  5. Ждём… и ждём… и ждём...

Это похоже на очень скучную видеоигру, в которой ты снова и снова выполняешь один и тот же квест, надеясь на иные результаты.

Собираем Proof of Concept

Я начал с того, что набросал небольшие скрипты на Python, чтобы проверить, сработает ли эта безумная идея. Разбил я её на части следующим образом:

▍ Шаг 1: находим объявления о вакансиях (делается вручную)

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

Я попробовал передавать веб-страницы целиком модели LLM, чтобы она очистила данные, но:

  • Это оказалось ужасно затратным мероприятием.
  • Я не хотел, чтобы ИИ галлюцинировал о требованиях к кандидату (представьте, каково будет объяснять это на собеседовании).

Поэтому я выбрал олдскульный подход — ручное копирование HTML. Да, это примитивно. И да, это работает. Иногда лучшим решением оказывается самое простое.

▍ Шаг 2: очищаем сырой HTML

В сыром HTML творился бардак, поэтому мне нужно было структурировать данные следующим образом:

{
    "job_link": "https://example.com/job/12345",
    "job_id": "12345",
    "job_role": "software developer",
    "employer": "Tech Corp Inc",
    "location": "San Francisco, CA",
    "work_arrangement": "Remote",
    "salary": "$150,000"
}

Полезный совет: можно просто показать ChatGPT пример HTML и нужный вам формат вывода, тогда модель сама напишет скрипт парсинга.

▍ Шаг 3: получаем всю информацию о вакансии

Эта часть была простой, но потребовала небольших хитростей. Для каждой вакансии я выполнял GET-запрос для получения полного описания. Каждый запрос возвращал сырой HTML, содержавший все элементы веб-сайта — панели навигации, всплывающие окна, мусор внизу страницы и прочее.

Я написал простой парсер HTML для вырезания всего, кроме самого описания вакансии. Иногда возникали дополнительные трудности, например, приходилось нажать на кнопку, чтобы открыть почтовый адрес рекрутёра или информацию о компании. Хорошо то, что за раз мы работаем только с одной доской объявлений, поэтому разбираться со всеми этими паттернами придётся лишь однократно.

Полезный совет: всегда добавляйте паузы между запросами. У себя я установил задержку в 2-3 секунды. Да, это замедляет процесс, но гораздо хуже будет, если ваш IP забанят. Не будьте тем, кто DDOS-ит веб-сайты с вакансиями — я добавил паузы между запросами, потому что я не злодей.

▍ Шаг 4: преобразуем сырой HTML в структурированные данные

Здесь всё становится интереснее. Объявления о вакансиях похожи на людей — у всех них есть одинаковые основные части, но упорядочены они хаотически. В некоторых требуемые навыки указаны в начале, в других они сокрыты среди абзацев корпоративного новояза.

На помощь моей психике пришёл промт LLM:

const prompt = `Проанализируй этот HTML-контент из объявления о вакансии и извлеки информацию в структурированном формате JSON.

[... HTML-контент ...]

Отформатируй ответ в виде валидного объекта JSON именно с такими ключами:
- contact_email (почта для связи)
- application_instructions (инструкции по подаче отклика на вакансию)
- job_posting_text (текст объявления о вакансии, в markdown)
- job_posting_link (ссылка на объявление о вакансии)
- additional_info (дополнительная информация) (зарплата, местоположение и так далее)
- job_title (название должности)
- job_company (компания)
- job_department (отдел)
- job_location (местоположение офиса)
- job_skills (требуемые для работы навыки)
- job_instructions (инструкции по подаче заявления о приёме)

опциональные ключи

- hiring_manager_name (ФИО менеджера по найму)
- 
- job_portal (портал объявлений)
`

Оригинал промта на английском

const prompt = `Please analyze these HTML contents from a job posting and extract information into a structured JSON format.

[... HTML content ...]

Format the response as valid JSON object with these exact keys:
- contact_email
- application_instructions
- job_posting_text (in markdown)
- job_posting_link
- additional_info (salary, location, etc.)
- job_title
- job_company
- job_department
- job_location
- job_skills
- job_instructions (how to apply)

optional keys

- hiring_manager_name
- 
- job_portal
`

▍ Шаг 5: генерируем качественные сопроводительные письма

Что самое важное при написании хороших сопроводительных писем? Контекст. Я передал LLM своё резюме и подробности о вакансии. Благодаря этому ИИ сможет сопоставить мой рабочий опыт с требованиями компании. Внезапно все эти шаблонные письма «С радостью воспользуюсь этой возможностью» обретут какую-то конкретику.

Вот промт, который позволил это реализовать

const prompt = `Помоги мне написать профессиональное письмо с откликом на вакансию на основании следующей информации:

=== МОЁ РЕЗЮМЕ ===
${resumeMarkdown}

=== ПОДРОБНОСТИ О ВАКАНСИИ ===
Название должности: ${job_title}
Компания: ${job_company}
Отдел: ${job_department || ''}
Местоположение офиса: ${job_location || ''}
Описание должности: ${job_posting_text }
Требуемые навыки: ${job_skills?.join(', ') || ''}
Инструкции по подаче заявления: ${job_instructions || ''}

Дополнительный контекст:
- ФИО менеджера по найму: ${hiring_manager_name || ''}
- Источник ссылки: ${referral_source || 'Job board'}
- Портал объявлений: ${job_portal || ''}

Инструкции:
1. Создай сразу готовое к отправке электронное письмо, не содержащее никаких текстовых заглушек и не требующее правок
2. В случае отсутствия какой-то критически важной информации (например, названия компании или должности) отвечай сообщением об ошибке, а не генерируй незавершённый контент
3. Пропускай все опциональные поля, если они пусты, не добавляя вместо них текстовых заглушек
4. Используй естественную структуру предложений, а не очевидный язык шаблонов
5. Включай конкретные подробности из резюме и описания вакансии, чтобы продемонстрировать искренний интерес и пригодность кандидата к должности
6. Все ссылки и контактная информация должны быть правильно отформатированы и готовы к использованию

Отформатируй ответ в виде объекта JSON со следующими ключами:
{
  "status": "success" или "error",
  "error_message": "Присутствует, только если status равен error, с объяснением об отсутствии критически важной информации",
  "email": {
    "subject": "Строка темы письма",
    "body_html": "Тело письма в формате HTML с правильным форматированием",
    "body_text": "Версия письма в текстовом виде без форматирования",
    "metadata": {
      "key_points_addressed": ["список основных учтённых пунктов"],
      "skills_highlighted": ["список упомянутых навыков"],
      "resume_matches": ["конкретные навыки/опыт из резюме, соответствующие требованиям к кандидату"],
      "missing_recommended_info": ["опциональные поля, которые отсутствуют, но могли бы в случае своего наличия повысить убедительность поданного заявления"],
      "tone_analysis": "краткий анализ тональности письма"
    }
  }
}

Критичные обязательные поля (возвращай ошибку в случае их отсутствия):
- Название должности
- Название компании
- Описание вакансии
- Содержимое резюме

Рекомендованные, но опциональные поля:
- ФИО менеджера по найму
- Отдел
- Местоположение офиса
- Инструкции по подаче заявления
- Источник ссылки
- Список требуемых навыков

Проверь, что весь HTML в body_html имеет подходящие завершающие символы для JSON и что в нём использованы только основные теги форматирования (p, br, b, i, ul, li), чтобы обеспечить максимальную совместимость с клиентами электронной почты.
`

Оригинал промта на английском

const prompt = `Please help me write a professional job application email based on the following information:

=== MY RESUME ===
${resumeMarkdown}

=== JOB DETAILS ===
Job Title: ${job_title}
Company: ${job_company}
Department: ${job_department || ''}
Location: ${job_location || ''}
Job Description: ${job_posting_text }
Required Skills: ${job_skills?.join(', ') || ''}
Application Instructions: ${job_instructions || ''}

Additional Context:
- Hiring Manager Name: ${hiring_manager_name || ''}
- Referral Source: ${referral_source || 'Job board'}
- Application Portal: ${job_portal || ''}

Instructions:
1. Create an email that is ready to send without any placeholders or edits needed
2. If any critical information is missing (like company name or job title), respond with an error message instead of generating incomplete content
3. Skip any optional fields if they're empty rather than including placeholder text
4. Use natural sentence structure instead of obvious template language
5. Include specific details from both the resume and job description to show genuine interest and fit
6. Any links or contact information should be properly formatted and ready to use

Format the response as a JSON object with these keys:
{
  "status": "success" or "error",
  "error_message": "Only present if status is error, explaining what critical information is missing",
  "email": {
    "subject": "The email subject line",
    "body_html": "The email body in HTML format with proper formatting",
    "body_text": "The plain text version of the email",
    "metadata": {
      "key_points_addressed": ["list of main points addressed"],
      "skills_highlighted": ["list of skills mentioned"],
      "resume_matches": ["specific experiences/skills from resume that match job requirements"],
      "missing_recommended_info": ["optional fields that were missing but would strengthen the application if available"],
      "tone_analysis": "brief analysis of the email's tone"
    }
  }
}

Critical required fields (will return error if missing):
- Job title
- Company name
- Job description
- Resume content

Recommended but optional fields:
- Hiring manager name
- Department
- Location
- Application instructions
- Referral source
- Required skills list

Please ensure all HTML in body_html is properly escaped for JSON and uses only basic formatting tags (p, br, b, i, ul, li) to ensure maximum email client compatibility.
`

Промт выполняет следующие задачи:

  1. Заставляет структурировать выходные данные, никаких расхлябанных ответов.
  2. Отслеживает, какие ваши навыки соответствуют требованиям вакансии.
  3. Выявляет всю отсутствующую информацию, которая может сделать письмо более весомым.
  4. Генерирует версии и в HTML, и в тексте без форматирования (потому что некоторые порталы ненавидят форматирование).

Очень важно то, что он сразу выдаёт ошибку при отсутствии критически важной информации. Больше никаких писем «Я увидел вашу вакансию». Или в сопроводительном письме есть суть, или мы его не отправляем, точка.

(Я начинаю все свои промты с «please», чтобы после неизбежного захвата мира искусственным интеллектом он не считал меня врагом.)

▍ Шаг 6: отправляем письма (момент истины)

Последний этап — рассылка наших прекрасно структурированных писем. Вам кажется, это просто? Достаточно подключить сервис электронной почты и начать бомбардировку?

Не стоит торопиться. Мне нужно:

  • Отправлять профессионально выглядящие письма.
  • Отслеживать то, что отправляется.
  • Мониторить ответы (нельзя оставлять рекрутёров в подвешенном состоянии).
  • Не быть принятым за спам (крайне важно!).

Для проверки я сначала отправил все письма на тестовый аккаунт. Полезный совет: при рассылке писем рекрутёрам добавляйте себя в BCC. Нет ничего хуже, кроме как гадать «а получили ли вообще письмо?»

На этом этапе POC я просто воспользовался простым сервисом электронной почты Mailgun. Быстро, грязно, но эффективно. Не волнуйтесь, ниже я подробно расскажу о том, на что мне пришлось пойти для создания полнофункциональной системы управления электронной почтой.

Результаты

Proof of concept сработал лучше, чем я ожидал. Я мог извлекать вакансии с конкретных досок объявлений, парсить их и генерировать персонализированные письма. И для этого оказалось достаточно всего нескольких скриптов на Python.

Но это было лишь началом. В чём же заключалась истинная трудность? В превращении этих скриптов в приложение, которое могло бы:

  • Работать с множеством разных досок объявлений.
  • Отслеживать отправленные заявки.
  • Обрабатывать ответами по почте.
  • Предотвратить попадание в чёрные списки всех существующих HR-систем.

Вот, что я узнал: мечты умирают в пропасти между «это работающий POC» и «это работающее приложение». Но мы всё равно преодолеем эту пропасть.

От скриптов к системе

Скрипты на Python эволюционировали в разные типы скриптов в приложении, каждый из которых обрабатывал отдельную часть процесса. Каждая операция поиска работы превратилась в «кампанию» со своим собственным конвейером. Это работает следующим образом:

1. Хранилище сырого HTML: сюда мы сбрасываем сырой HTML с досок объявлений.

// Пример html из вакансии
<article id="article-42478761" class="action-buttons"><a href="/jobsearch/jobposting/42478761?source=searchresults"
            id="ajaxupdateform:j_id_31_3_3p:1:j_id_31_3_3r" class="resultJobItem">
            <h3 class="title">
                <span class="flag">
                    <span class="new">
                        New
                    </span><span class="telework">On site</span><span class="postedonJB">
                        Posted on Job Bank
                        <span class="description"><span class="fa fa-info-circle" aria-hidden="true"></span>This job was
                            posted directly by the employer on Job Bank.</span>
                    </span>

                </span>
                <span class="job-source job-source-icon-16"><span class="wb-inv">Job Bank</span></span>
                <span class="noctitle"> software developer

                </span>
            </h3>

            <ul class="list-unstyled">
                <li class="date">November 08, 2024
                </li>
                <li class="business">OMEGA SOFTWARE SERVICES LTD.</li>
                <li class="location"><span class="fas fa-map-marker-alt" aria-hidden="true"></span> <span
                        class="wb-inv">Location</span>

                    Scarborough (ON)

                </li>
                <li class="salary"><span class="fa fa-dollar" aria-hidden="true"></span>
                    Salary:
                    $50.00 hourly</li>
                <li class="source"><span class="job-source job-source-icon-16"><span class="wb-inv">Job
                            Bank</span></span>
                    <span class="wb-inv">Job number:</span>
                    <span class="fa fa-hashtag" aria-hidden="true"></span>
                    3146897
                </li>
            </ul>
        </a><span id="ajaxupdateform:j_id_31_3_3p:1:favouritegroup" class="float job-action">
            <a href="/login" data-jobid="42478761" class="favourite saveLoginRedirectURI"
                onclick="saveLoginRedirectURIListener(this);">
                <span class="wb-inv">software developer - Save to favourites</span>
            </a></span>
    </article>

2. Первоначальная очистка: скрипт превращает этот хаос в структурированный JSON:

 {
    "job_link": "https://www.jobbank.gc.ca/jobsearch/jobposting/42478761?source=searchresults",
    "job_id": "42478761",
    "job_role": "software developer",
    "employer": "OMEGA SOFTWARE SERVICES LTD.",
    "location": "Scarborough (ON)",
    "work_arrangement": "On site",
    "salary": "$50.00 hourly"
  }

3. Получение вакансии: ещё один скрипт переходит по каждому из URL вакансии и получает полную публикацию (с уважительными паузами между запросами, мы ведь не дикари).

4. Очистка данных вакансий: этот скрипт использует ИИ для превращения постов с вакансиями в чистые структурированные данные, содержащие:

  • Адрес почты для связи
  • Инструкции по подаче заявки
  • Полное описание вакансии в markdown
  • Дополнительные метаданные (зарплата, местоположение офиса, требования)

{
    "job_id": "42313964",
    "processed_timestamp": "2024-12-25T19:45:39.829Z",
    "original_fetch_timestamp": "2024-12-25T19:40:46.187Z",
    "job_json": {
      "contact_email": "careers@wiasystems.com",
      "application_instructions": "To apply, please send your resume and cover letter to careers@wiasystems.com.",
      "job_posting_text": "# Job Postingnn## Job Title: Software Engineernn**Job Description:**nn- Education: Bachelor's degree in Computer Science or related fieldn- Experience: 2 years to less than 3 yearsn- Location: Vancouver, BCn- Work Arrangement: Hybrid (in-person and remote)nn## Job Responsibilities:nn- Collect and document user's requirementsn- Coordinate the development, installation, integration and operation of computer-based systemsn- Define system functionalityn- Develop flowcharts, layouts, and documentation to identify solutionsn- Develop process and network models to optimize architecturen- Develop software solutions by studying systems flow, data usage, and work processesn- Evaluate the performance and reliability of system designsn- Evaluate user feedbackn- Execute full lifecycle software developmentn- Prepare plan to maintain softwaren- Research technical information to design, develop, and test computer-based systemsn- Synthesize technical information for every phase of the cycle of a computer-based systemn- Upgrade and maintain softwaren- Lead and coordinate teams of information systems professionals in the development of software and integrated information systems, process control software, and other embedded software control systemsnn## Required Skills and Qualifications:nn- Agilen- Cloudn- Development and operations (DevOps)n- Eclipsen- Jiran- Microsoft Visual Studion- HTMLn- Intranetn- Internetn- XML Technology (XSL, XSD, DTD)n- Serversn- Desktop applicationsn- Enterprise Applications Integration (EAI)n- Javan- File management softwaren- Word processing softwaren- X Windowsn- Servletn- Object-Oriented programming languagesn- Presentation softwaren- Mail server softwaren- Project management softwaren- Programming softwaren- SQLn- Database softwaren- Programming languagesn- Software developmentn- XMLn- MS Officen- Spreadsheetn- Oraclen- TCP/IPn- Amazon Web Services (AWS)n- Gitn- Atlassian Confluencen- GitHubn- Performance testingn- Postmann- Software quality assurancen- MS Exceln- MS Outlookn- MS SQL Servernn### Benefits:nn- Health benefits: Dental plan, Health care plan, Vision care benefitsn- Other benefits: Learning/training paid by employer, Other benefits, Paid time off (volunteering or personal days)nnFor more information about the position and to apply, please send your resume and cover letter to careers@wiasystems.com.",
      "job_posting_link": "https://www.jobbank.gc.ca/jobsearch/jobposting/42313964?source=searchresults",
      "additional_info": {
        "salary": "CAD 60.50 per hour",
        "location": "Vancouver, BC",
        "job_role": "Software Engineer",
        "company_name": "WIA Software Systems Inc.",
        "job_type": "Permanent, Full-time",
        "required_experience": "2 years to less than 3 years",
        "required_education": "Bachelor's degree in Computer Science or related field",
        "language_requirements": "English",
        "work_arrangement": "Hybrid (in-person and remote)"
      }
    },
    "raw_gpt_responce": ""
  },

5. Генерация писем: скрипт берёт резюме + данные вакансии и создаёт персонализированные заявки, которые не выглядят так, как будто составлены роботом.

6. Отправка писем: последний этап, позволяющий вашим письмам достичь адресатов.

250 откликов за 20 минут: как я автоматизировал процесс ответов на вакансии - 2

Каждая кампания изолирована от других. Кампания одновременно может выполнять только один скрипт (например, переходить от очистки к извлечению вакансии, а затем к генерации письма), разные кампании проводятся независимо. Это можно сравнить со множеством сборочных линий — даже если одна линия останавливается, остальные продолжают сборку. Скрипт, поломавшийся в одной кампании, не помешает задачам, выполняемым в другой.

Технологический стек

Я мог бы сказать, что каждый технологический элемент был выбран после тщательного изучения всех возможных вариантов. Но так ли это? На самом деле, для достижения цели я просто-напросто использовал то, что знал:

  • Фронтенд: Next.js с Shadcn для компонентов UI.
  • Бэкенд: Express.js и nodejs (с typescript).
  • База данных: MongoDB для данных вакансий.
  • Система очередей: Redis для фоновых задач.
  • Интеграция ИИ: модульная схема, поддерживающая различных поставщиков.

Приложение живёт на jaas.fun (Job Application Automation System — да, я здорово умею придумывать названия).

Система кампаний

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

  • Для работы с разными досками объявлений требуются разные скрипты.
  • Ограничения по частоте срабатывают в разное время.
  • Нам нужно проверять новые подходы, не ломая уже имеющиеся.

Схема кампании отслеживает всё:

  • Сырой HTML с досок объявлений.
  • Скрипты очистки.
  • Сгенерированный JSON.
  • Шаблоны писем.
  • Статус обработки.

Каждый тип скрипта получает конкретные функции на основании своей роли:

  • Скрипты очистки: доступ чтения сырого HTML и сохранения очищенного JSON.
  • Скрипты получения: сетевой доступ к доскам объявлений и хранилищу данных.
  • Скрипты генерации писем: доступ к моделям ИИ и данным резюме.
  • Скрипты отправки писем: доступ к почтовым сервисам и обновлениям статусов кампаний.

Ни один скрипт не может получать доступ к функциям вне своего типа — скрипт очистки не может отправлять письма, а почтовый скрипт не может получать новые данные вакансий. Мы как будто даём каждому работнику только нужные ему инструменты, и ничего сверх того.

Система выполнения скриптов

Вот это по-настоящему интересно. Помните, что мы должны безопасно выполнять потенциально ненадёжный код (скрипты очистки и обработки)? В этом нам поможет система выполнения скриптов.

Работает она следующим образом:

  1. Каждый скрипт встаёт в очередь Redis с указанием:
    • ID кампании.
    • Типа скрипта (очистка, получение, генерация писем).
    • Содержимого скрипта.
    • Входных данных.

  2. Процесс воркера непрерывно выполняется, ожидая новых задач. Он использует vm2 для создания среды-песочницы для каждого скрипта. Почему? Потому что выполнение произвольного JavaScript опасно, а я хочу спокойно спать по ночам.
  3. Каждый скрипт выполняется в своей собственной песочнице:
    • С собственным console.log, передаваемым в Redis.
    • С доступом только к своим входным данным.
    • В полной изоляции от основной системы.
    • Без искусственных ограничений по времени (потому что обработка 100 задач требует больше времени, чем обработка одной).

Система логгинга довольно удобна. Каждое сообщение лога вместо того, чтобы записываться в файл или в консоль:

  • Получает метку времени.
  • Сохраняется в Redis в соответствии с кампанией и типом скрипта.
  • В реальном времени передаётся в UI.

Эта система очень отказоустойчива. В случае сбоя скрипта кампания помечается как неудачная, но ничто другое не ломается. При сбое воркера он перезапускается и продолжает работу с того места, где закончил. Можно в буквальном смысле закрыть браузер, сходить выпить кофе, а может, подготовиться к тем собеседованиям, о которых мы договоримся благодаря этой системе.

Когда скрипт завершает выполнение, воркер:

  1. Получает вывод и сохраняет его в нужное место в MongoDB.
  2. Обновляет статус кампании.
  3. Удаляет все временные данные.
  4. Переходит к следующей задаче.

А поскольку вся система основана на очередях, вы можете запустить множество воркеров, если нужно обрабатывать большее количество кампаний.

Конвейер обработки данных

Давайте поговорим о том, как происходит поток данных через систему:

  1. Обработка сырого HTML:
    • Пользователь сбрасывает сырой HTML с досок объявлений в кампанию.
    • Скрипт, который использует Cheerio, извлекает основные подробности (ID вакансии, должность, зарплату).
    • Умная обработка ошибок выявляет на раннем этапе отсутствующие поля.
    • HTML минифицируется для экономии места (для каждой задачи объём снижается с 175 КБ до 32 КБ).

  2. Получение подробностей о вакансии:
    • Система стучится по каждому URL вакансии, используя при этом подходящие заголовки (чтобы выглядеть, как настоящий браузер).
    • Обрабатывает разные типы запросов (GET для основной страницы, POST для пункта «способ подачи заявки»).
    • Добавляет задержки между запросами (2-3 секунд), чтобы не нагружать доски объявлений.
    • Обрабатывает таймауты и публикации вакансий с истёкшим сроком.

  3. Очистка данных при помощи ИИ:
    • Превращает хаотичный HTML в структурированные данные задачи.
    • Извлекает всё, от зарплатной вилки до требуемых навыков.
    • Форматирует описания вакансий как чистый markdown.
    • В каждый ответ включаются метаданные о времени обработки и качестве данных.

  4. Генерация сопроводительного письма:
    • Получает резюме из заданного источника (в моём случае с GitHub).
    • Сопоставляет навыки с должностными требованиями.
    • Генерирует версии в HTML и в тексте без форматирования.
    • Даже добавляет метаданные о том, какие навыки совпали.
    • Выполняет аварийный выход на раннем этапе в случае отсутствия критически важной информации.

Система писем

Система генерации писем не просто отправляет формальные письма, но и создаёт полностью персонализированные заявки:

  1. Умная работа с резюме:
    • Извлекает резюме из заданного источника.
    • Парсит навыки и опыт.
    • Сопоставляет навыки пользователя с требованиями вакансии.

  2. Генерация без шаблонов:
    • Никаких шаблонных писем.
    • В каждом сопроводительном письме упоминаются конкретные подробности из вакансии.
    • Система отслеживает список основных учтённых пунктов.
    • Добавляет метаданные о навыках, соответствующих вакансии.

  3. Контроль качества:
    • Генерирует версии и в HTML, и в тексте без форматирования.
    • Выполняет аварийный выход в случае отсутствия критически важной информации.
    • Отслеживает отсутствующие рекомендованные поля.
    • Анализирует тональность и содержимое.

  4. Система отправки:
    • Автоматически обрабатывает ограничение по частоте.
    • Добавляет пользователя в BCC всех отправляемых писем.

Система даже добавляет метаданные о том, насколько опыт пользователя соответствует требованиям вакансии. Как будто у нас есть очень привередливый живой редактор, который работает очень быстро.

Что дальше

В следующей части статьи я расскажу о следующем:

  • Как мне отказывал AWS.
  • О проблемах SMTP-серверов в самостоятельном хостинге.
  • Почему крупные компании не хотят, чтобы вы рассылали автоматизированные отклики на вакансии.
  • Как я, наконец, нашёл работающее решение.

Кроме того, я поведаю, как получил оффер, ещё не завершив разработку этого проекта. (Спойлер: при этом я случайно загнал себя в угол автоматизацией.)

А пока можете зайти на jaas.fun, там есть:

  • Полный исходный код.
  • Руководство по написанию скриптов и использованию приложения (написанное с той же степенью внимания к деталям, что и мои сообщения к коммитам — «исправил всякое»).
  • Видеодемо системы в действии.

Автор: ru_vds

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js