| import streamlit as st
|
| import pandas as pd
|
| import numpy as np
|
| import os
|
| import re
|
| import string
|
| import joblib
|
| import fitz
|
| import random
|
|
|
|
|
| st.set_page_config(
|
| page_title="Классификатор резюме",
|
| page_icon="📄",
|
| layout="wide"
|
| )
|
|
|
|
|
| def clean_text(text):
|
| if text is None:
|
| return ""
|
| text = re.sub(r'[^\w\s]', ' ', text)
|
| text = re.sub(r'\d+', ' ', text)
|
| text = re.sub(r'\n', ' ', text)
|
| text = re.sub(r'[a-zA-Z]', ' ', text)
|
| text = re.sub(r'\s+', ' ', text)
|
| text = text.lower()
|
| text = text.translate(str.maketrans('', '', string.punctuation))
|
| return text
|
|
|
|
|
| def extract_text_from_pdf(pdf_file):
|
| text = ""
|
| try:
|
| with fitz.open(stream=pdf_file.read(), filetype="pdf") as doc:
|
| for page in doc:
|
| text += page.get_text()
|
| except Exception as e:
|
| st.error(f"Ошибка при чтении PDF: {e}")
|
| return text
|
|
|
|
|
| @st.cache_resource
|
| def load_model():
|
| try:
|
|
|
| pipeline = joblib.load('resume_classifier.joblib')
|
| st.success("Модель успешно загружена из файла")
|
| return pipeline
|
| except Exception as e:
|
| st.error(f"Ошибка при загрузке модели: {e}")
|
| return None
|
|
|
|
|
| def get_class_specific_comment(class_id):
|
|
|
| if class_id == 1:
|
| comments = [
|
| "Кандидат рекомендован. Профиль соответствует требованиям позиции.",
|
| "Резюме демонстрирует необходимые навыки и опыт для данной должности.",
|
| "Положительное решение. Квалификация кандидата соответствует ожиданиям."
|
| ]
|
| else:
|
| comments = [
|
| "Кандидат не рекомендован. Недостаточное соответствие требованиям.",
|
| "Резюме не содержит необходимого опыта для данной позиции.",
|
| "Профиль кандидата не соответствует критериям отбора."
|
| ]
|
|
|
| return random.choice(comments)
|
|
|
|
|
| def extract_key_words(text):
|
|
|
|
|
|
|
|
|
| words = text.split()
|
|
|
| key_words = set([word for word in words if len(word) > 4])
|
|
|
| return list(key_words)[:20]
|
|
|
|
|
| def enhance_comment_with_text(base_comment, text, predicted_class):
|
|
|
| if not base_comment:
|
| base_comment = get_class_specific_comment(predicted_class)
|
|
|
|
|
| text_sample = text[:1000].lower() if text else ""
|
|
|
|
|
| enhancements = []
|
|
|
|
|
| experience_indicators = ["опыт работы", "лет опыта", "год опыта", "опыт в", "работал в", "занимал должность"]
|
| for indicator in experience_indicators:
|
| if indicator in text_sample:
|
| enhancements.append("Имеет релевантный опыт работы.")
|
| break
|
|
|
|
|
| education_indicators = ["высшее образование", "университет", "вуз", "бакалавр", "магистр", "специальность"]
|
| for indicator in education_indicators:
|
| if indicator in text_sample:
|
| enhancements.append("Обладает подходящим образованием.")
|
| break
|
|
|
|
|
| skill_indicators = ["навыки", "владение", "знание", "умение", "компетенции", "технологии"]
|
| for indicator in skill_indicators:
|
| if indicator in text_sample:
|
| enhancements.append("Демонстрирует необходимые технические навыки.")
|
| break
|
|
|
|
|
| sales_indicators = ["продаж", "клиент", "менеджер", "сделк", "переговор", "презентаци"]
|
| for indicator in sales_indicators:
|
| if indicator in text_sample:
|
| enhancements.append("Имеет опыт в сфере продаж.")
|
| break
|
|
|
|
|
| if enhancements:
|
| random.shuffle(enhancements)
|
| enhancements = enhancements[:2]
|
| enhanced_comment = base_comment + " " + " ".join(enhancements)
|
| return enhanced_comment
|
|
|
| return base_comment
|
|
|
|
|
| def generate_comment(text, model, prediction):
|
| try:
|
|
|
| base_comment = get_class_specific_comment(prediction)
|
|
|
|
|
| enhanced_comment = enhance_comment_with_text(base_comment, text, prediction)
|
|
|
| return enhanced_comment
|
| except Exception as e:
|
| st.warning(f"Ошибка при генерации комментария: {str(e)}")
|
|
|
| if prediction == 1:
|
| return "Кандидат рекомендован к рассмотрению."
|
| else:
|
| return "Кандидат не рекомендован к рассмотрению."
|
|
|
|
|
| def main():
|
| st.title("Классификация резюме с улучшенными комментариями")
|
|
|
|
|
| model = load_model()
|
|
|
| if model is None:
|
| st.error("Модель не загружена. Пожалуйста, убедитесь, что файл resume_classifier.joblib существует в директории.")
|
| st.stop()
|
|
|
|
|
| st.header("Загрузите PDF файлы для классификации")
|
| uploaded_files = st.file_uploader("Выберите PDF файлы", type="pdf", accept_multiple_files=True)
|
|
|
| if uploaded_files:
|
| with st.spinner("Обрабатываем документы..."):
|
| results = []
|
|
|
| for file in uploaded_files:
|
|
|
| text = extract_text_from_pdf(file)
|
| clean_text_content = clean_text(text)
|
|
|
| if not clean_text_content:
|
| st.warning(f"Не удалось извлечь текст из {file.name}")
|
| continue
|
|
|
| try:
|
|
|
| prediction = model.predict([clean_text_content])[0]
|
|
|
| proba = model.predict_proba([clean_text_content])[0]
|
|
|
|
|
| if len(proba) <= prediction:
|
| st.warning(f"Ошибка: индекс {prediction} выходит за пределы размерности {len(proba)}")
|
| relevance_prob = 0.5
|
| else:
|
| relevance_prob = float(proba[prediction])
|
|
|
|
|
| comment = generate_comment(text, model, prediction)
|
|
|
|
|
| results.append({
|
| "file": file.name,
|
| "predicted_class": int(prediction),
|
| "relevance_prob": relevance_prob,
|
| "comment": comment
|
| })
|
| except Exception as e:
|
| st.error(f"Ошибка при обработке файла {file.name}: {str(e)}")
|
|
|
|
|
| if results:
|
| st.success(f"Обработано {len(results)} файлов")
|
| df = pd.DataFrame(results)
|
| st.dataframe(df)
|
|
|
|
|
| csv = df.to_csv(index=False)
|
| st.download_button(
|
| label="Скачать результаты в CSV",
|
| data=csv,
|
| file_name="classification_results.csv",
|
| mime="text/csv"
|
| )
|
|
|
| if __name__ == "__main__":
|
| main() |