feat: add subscribtion scheduler with ai, pagination

This commit is contained in:
Faynot
2026-03-29 11:25:31 +03:00
parent 1db524f757
commit 164acd6307
16 changed files with 688 additions and 358 deletions

112
ai.py Normal file
View File

@@ -0,0 +1,112 @@
import aiohttp
import json
import re
from config import OPENROUTER
SYSTEM_PROMPT_TEMPLATE = """
Ты — экспертный технический рекрутер. Твоя задача: отфильтровать IT-вакансии.
Предпочтения пользователя:
{preferences}
Верни ТОЛЬКО валидный JSON массив объектов, которые подходят под стек и грейд.
Если ничего не подходит, верни [].
Никаких пояснений и markdown-разметки.
"""
def get_forced_vacancies(vacancies: list, user_prefs: str) -> list:
stack_match = re.search(r'(?i)стек:(.*?)(?=доп:|$)', user_prefs, re.DOTALL)
if not stack_match:
return []
stack_content = stack_match.group(1).lower()
all_keywords = re.findall(r'[a-zA-Z0-9.+#]{2,}', stack_content)
common_words = {'html', 'css', 'git', 'sql', 'api', 'rest', 'remote', 'work'}
strong_keys = list(set(word for word in all_keywords if word not in common_words))
if not strong_keys:
return []
print(f"🔍 Ключи для авто-добавления: {strong_keys}")
forced = []
for vac in vacancies:
v_id = str(vac.get('id', ''))
vac_str = json.dumps(vac, ensure_ascii=False).lower()
vac_clean = re.sub(r'https?://\S+', '', vac_str)
if any(key in vac_clean for key in strong_keys):
forced.append(vac)
return forced
async def filter_vacancies_with_ai(vacancies: list, user_prefs: str) -> list:
if not vacancies:
return []
forced_vacancies = get_forced_vacancies(vacancies, user_prefs)
forced_ids = {str(v.get('id')) for v in forced_vacancies if v.get('id')}
remaining_vacancies = [v for v in vacancies if str(v.get('id')) not in forced_ids]
if not remaining_vacancies:
print(f"Все {len(forced_vacancies)} вакансий одобрены автоматом.")
return forced_vacancies
print(f"✅ Авто-одобрено: {len(forced_vacancies)}")
print(f"🤖 Отправка на AI: {len(remaining_vacancies)} (было {len(vacancies)})")
api_url = "https://openrouter.ai/api/v1/chat/completions"
system_prompt = SYSTEM_PROMPT_TEMPLATE.format(preferences=user_prefs)
async with aiohttp.ClientSession() as session:
try:
clean_token = str(OPENROUTER).strip()
async with session.post(
url=api_url,
headers={
"Authorization": f"Bearer {clean_token}",
"Content-Type": "application/json",
},
json={
"model": "openrouter/free",
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": json.dumps(remaining_vacancies, ensure_ascii=False)}
]
},
timeout=60
) as response:
if response.status != 200:
raw_err = await response.text()
print(f"❌ Ошибка API ({response.status}): {raw_err[:100]}")
return forced_vacancies
result_raw = await response.json()
if 'choices' not in result_raw:
return forced_vacancies
content = result_raw['choices'][0]['message']['content'].strip()
match = re.search(r'\[\s*\{.*\}\s*\]', content, re.DOTALL)
clean_json = match.group(0) if match else content.replace('```json', '').replace('```', '').strip()
try:
ai_vacs = json.loads(clean_json)
final = forced_vacancies.copy()
for v in ai_vacs:
if str(v.get('id')) not in forced_ids:
final.append(v)
print(f"🏁 Итог: {len(final)} релевантных вакансий")
return final
except:
return forced_vacancies
except Exception as e:
print(f"❌ Ошибка в блоке запроса: {e}")
return forced_vacancies