diff --git a/vacancies/main/bot.py b/vacancies/main/bot.py index d360fd9..a6bca98 100644 --- a/vacancies/main/bot.py +++ b/vacancies/main/bot.py @@ -1,12 +1,14 @@ +import asyncio import io import os -import asyncio import traceback +from typing import Literal from asgiref.sync import sync_to_async from langchain.agents import create_agent from langchain_openai import ChatOpenAI from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver +from pydantic import BaseModel from pypdf import PdfReader from telegram import ( InlineKeyboardButton, @@ -23,8 +25,6 @@ from telegram.ext import ( filters, ) -from pydantic import BaseModel -from typing import Literal from vacancies.conf.settings import DB_URI from vacancies.main.models import Customer, CustomerCV, JobTitle from vacancies.main.recommendations import get_next_vacancy @@ -59,7 +59,7 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): async def next_vacancy(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(update.effective_chat.id, "📝 Обрабатываю твой запрос. Пожалуйста, подождите...") + await context.bot.send_message(update.effective_chat.id, "⏳ Обрабатываю твой запрос. Пожалуйста, подождите...") customer_cv = await CustomerCV.objects.filter(customer__telegram_id=update.effective_user.id).afirst() if not customer_cv: @@ -75,7 +75,8 @@ async def next_vacancy(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message( chat_id=update.effective_chat.id, - text=vacancy.content, + parse_mode="Markdown", + text=vacancy.get_formatted_response(), reply_markup=InlineKeyboardMarkup([[ InlineKeyboardButton("Откликнуться", url=vacancy.link), ]]), @@ -91,7 +92,7 @@ async def prompt(update: Update, context: ContextTypes.DEFAULT_TYPE): checkpointer=checkpointer, ) - message = await context.bot.send_message(update.effective_chat.id, "📝 Обрабатываю твой запрос. Пожалуйста, подождите...") + message = await context.bot.send_message(update.effective_chat.id, "⏳ Обрабатываю твой запрос. Пожалуйста, подождите...") response = await agent.ainvoke( input={"messages": [{"role": "user", "content": f'user_id = {update.effective_user.id}\n{update.message.text}'}]}, @@ -107,7 +108,7 @@ async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> N async def handle_document(update: Update, context: ContextTypes.DEFAULT_TYPE): - message = await context.bot.send_message(update.effective_chat.id, "📝 Обрабатываю твой запрос. Пожалуйста, подождите...") + message = await context.bot.send_message(update.effective_chat.id, "⏳ Обрабатываю твой запрос. Пожалуйста, подождите...") if not update.message.document: await context.bot.send_message(chat_id=update.effective_chat.id, text="Не удалось прочитать информацию из файла! Попробуйте другой формат.") diff --git a/vacancies/main/management/commands/collect_vacancies_from_telegram_messages.py b/vacancies/main/management/commands/collect_vacancies_from_telegram_messages.py index 9276fbb..7d7967e 100644 --- a/vacancies/main/management/commands/collect_vacancies_from_telegram_messages.py +++ b/vacancies/main/management/commands/collect_vacancies_from_telegram_messages.py @@ -1,14 +1,15 @@ -from itertools import batched from datetime import timedelta -from django.utils import timezone -from pydantic import BaseModel +from itertools import batched from typing import Literal -from vacancies.main.models import Vacancy, JobTitle -from langchain_openai import ChatOpenAI import clickhouse_connect -from django.core.management import BaseCommand from django.conf import settings +from django.core.management import BaseCommand +from django.utils import timezone +from langchain_openai import ChatOpenAI +from pydantic import BaseModel + +from vacancies.main.models import JobTitle, Vacancy query = """ SELECT DISTINCT ON (message) id, chat_username, telegram_id, message, timestamp @@ -42,6 +43,8 @@ class Command(BaseCommand): job_title: Literal[tuple(job_titles)] min_salary_rub: int | None max_salary_rub: int | None + company_name: str + requirements: str openai_client = ChatOpenAI(model_name="gpt-5-mini", temperature=0, seed=42, top_p=1) structured_llm = openai_client.with_structured_output(Structure) @@ -76,6 +79,8 @@ class Command(BaseCommand): job_title_id=job_title_map[response.job_title], min_salary_rub=response.min_salary_rub, max_salary_rub=response.max_salary_rub, + company_name=response.company_name, + requirements=response.requirements, content=message, timestamp=timezone.make_aware(timestamp), link=f"https://t.me/{chat_username}/{telegram_id}", diff --git a/vacancies/main/management/commands/generate_recommended_vacancies.py b/vacancies/main/management/commands/generate_recommended_vacancies.py index 52c351a..cbc117f 100644 --- a/vacancies/main/management/commands/generate_recommended_vacancies.py +++ b/vacancies/main/management/commands/generate_recommended_vacancies.py @@ -1,11 +1,12 @@ import asyncio from django.core.management import BaseCommand -from vacancies.main.models import CustomerCV -from vacancies.main.bot import application -from vacancies.main.recommendations import get_next_vacancy from telegram import InlineKeyboardButton, InlineKeyboardMarkup +from vacancies.main.bot import application +from vacancies.main.models import CustomerCV +from vacancies.main.recommendations import get_next_vacancy + class Command(BaseCommand): help = "Generates new recommended vacancies" @@ -18,7 +19,8 @@ class Command(BaseCommand): if vacancy := get_next_vacancy(customer_cv): await application.bot.send_message( chat_id=customer_cv.customer.chat_id, - text=vacancy.content, + text=vacancy.get_formatted_response(), + parse_mode="Markdown", reply_markup=InlineKeyboardMarkup([[ InlineKeyboardButton("Откликнуться", url=vacancy.link), ]]), diff --git a/vacancies/main/migrations/0012_vacancy_company_name_vacancy_requirements.py b/vacancies/main/migrations/0012_vacancy_company_name_vacancy_requirements.py new file mode 100644 index 0000000..0f1ad90 --- /dev/null +++ b/vacancies/main/migrations/0012_vacancy_company_name_vacancy_requirements.py @@ -0,0 +1,25 @@ +# Generated by Django 5.2.7 on 2025-11-09 19:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0011_remove_customercv_job_title_customercv_job_titles_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='vacancy', + name='company_name', + field=models.CharField(default='test', max_length=255), + preserve_default=False, + ), + migrations.AddField( + model_name='vacancy', + name='requirements', + field=models.TextField(default='test'), + preserve_default=False, + ), + ] diff --git a/vacancies/main/models.py b/vacancies/main/models.py index a2eaa28..c0b1428 100644 --- a/vacancies/main/models.py +++ b/vacancies/main/models.py @@ -45,6 +45,8 @@ class Vacancy(models.Model): external_id = models.CharField(max_length=255, unique=True) min_salary_rub = models.PositiveIntegerField(null=True, blank=True, default=None) max_salary_rub = models.PositiveIntegerField(null=True, blank=True, default=None) + company_name = models.CharField(max_length=255) + requirements = models.TextField() content = models.TextField() timestamp = models.DateTimeField() link = models.URLField() @@ -52,6 +54,25 @@ class Vacancy(models.Model): def __str__(self): return self.job_title.title + def get_formatted_response(self): + response = f""" + 💼 **Вакансия**: {self.job_title} + \n🏢 **Компания**: {self.company_name} + \n📝 **Требования**: {self.requirements} + """ + if self.min_salary_rub: + if self.max_salary_rub: + response += f"\n💸 **ЗП**: от {self.min_salary_rub} т.р." + else: + response += f"\n💸 **ЗП**: {self.min_salary_rub} т.р. - {self.max_salary_rub} т.р." + elif self.max_salary_rub: + response += f"\n💸 **ЗП**: до {self.max_salary_rub} т.р." + + return response + + class Meta: + verbose_name_plural = 'Vacancies' + class RecommendedVacancy(models.Model): customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="recommended_vacancies")