Spaces:
Sleeping
Sleeping
from pyvis.network import Network | |
import pandas as pd | |
import pymorphy3 | |
import re | |
import gradio as gr | |
# Инициализация pymorphy3 (лемматизатор) | |
morph = pymorphy3.MorphAnalyzer() | |
# Функция токенизации и лемматизации | |
def tokenize_and_lemmatize(text): | |
text = re.sub(r'[^\w\s]', '', text.lower()) # Удаляем пунктуацию, приводим к нижнему регистру | |
words = text.split() | |
return [morph.parse(word)[0].normal_form for word in words] | |
# Словарь ключевых слов для каждого сегмента (каждое слово в начальной форме) | |
personalization_keywords = { | |
"Поколение X": ["комиссия", "визит", "снижение", "ставка", "бесплатно", "экономия"], | |
"Поколение Y": ["онлайн", "цифровой", "бонус", "лимит", "qr", "sberpay"], | |
"Поколение Z": ["быстрота", "мгновенно", "минута", "оперативно", "решение"], | |
"Пол Женский": ["комфорт", "удобство", "забота", "легкость", "терминал"], | |
"ОПФ ИП": ["эффективность", "комиссия", "снижение", "ставка", "онлайн", "быстрота", "оптимизация"], | |
"ОПФ ООО": ["удобство", "комиссия", "открытие", "онлайн", "легкость", "автоматизация"], | |
"Психотип Конструктор": ["оптимизация", "снижение", "ставка", "комиссия", "льготный", "период", "выгодно", "настройка"], | |
"Пол Мужской": ["динамичность", "быстрота", "лимит", "решение", "активность", "оптимизация"], | |
"Стадия бизнеса Новичок": ["доступность", "простота", "низкий", "порог", "комиссия", "легкость"], | |
"Стадия бизнеса Профи": ["профессионализм", "комиссия", "снижение", "ставка", "оптимизация", "эффективность", "быстрота"], | |
"Психотип Рефлектор": ["премиальность", "эксклюзив", "бизнес", "зал", "акция", "привилегия", "статус"], | |
"Психотип Центрист": ["универсальность", "стандарт", "комиссия", "бесплатно", "надежность"], | |
"Стадия бизнеса Эксперт": ["максимизация", "высокий", "лимит", "снижение", "ставка", "комиссия", "выгода", "оптимизация"] | |
} | |
# Функция для классификации одного текста преимущества | |
def classify_advantage(text, keywords_dict): | |
""" | |
Возвращает список кортежей вида: | |
[ | |
(category, { 'count': int, 'matched_lemmas': set([...]) }), | |
... | |
] | |
отсортированных по убыванию count. | |
""" | |
lemmas = tokenize_and_lemmatize(text) | |
category_matches = {} | |
# Проходим по всем категориям и считаем число совпадений лемм | |
for category, keywords in keywords_dict.items(): | |
matches = set(lemmas) & set(keywords) # Пересечение множеств | |
if matches: | |
category_matches[category] = { | |
'count': len(matches), | |
'matched_lemmas': matches | |
} | |
# Сортируем категории по количеству совпадений (по убыванию) | |
sorted_matches = sorted( | |
category_matches.items(), | |
key=lambda x: x[1]['count'], | |
reverse=True | |
) | |
return sorted_matches | |
# Глобальная переменная для хранения DataFrame | |
df = None | |
def load_excel(file): | |
""" | |
Функция для загрузки Excel-файла. | |
Возвращает список уникальных продуктов и сообщение о статусе загрузки. | |
""" | |
global df | |
if file is None: | |
return [], "Файл не загружен. Загрузите Excel-файл." | |
try: | |
# Читаем Excel в DataFrame | |
df = pd.read_excel(file.name, usecols=["Продукт", "Преимущество"]) | |
unique_products = df["Продукт"].unique().tolist() | |
return unique_products, "Файл успешно загружен!" | |
except Exception as e: | |
return [], f"Ошибка при чтении файла: {str(e)}" | |
def analyze(product): | |
""" | |
Функция, вызываемая при выборе продукта в выпадающем списке. | |
Анализирует все преимущества, соответствующие данному продукту, | |
и возвращает подробный отчёт и визуализацию графа. | |
""" | |
global df | |
if df is None: | |
return "Сначала загрузите файл.", None | |
if not product: | |
return "Пожалуйста, выберите продукт.", None | |
# Фильтруем DataFrame по выбранному продукту | |
product_advantages = df[df["Продукт"] == product]["Преимущество"] | |
# Создаём граф | |
graph_html = create_category_graph(product, product_advantages, personalization_keywords) | |
# Собираем результаты | |
results = [] | |
for advantage in product_advantages: | |
matches = classify_advantage(advantage, personalization_keywords) | |
# Формируем текстовый отчёт по каждому преимуществу | |
advantage_text = f"**Преимущество**: {advantage}\n\n" | |
advantage_text += f"**Леммы**: {tokenize_and_lemmatize(advantage)}\n\n" | |
advantage_text += "**Совпадающие категории:**\n" | |
if matches: | |
for category, data in matches: | |
# Выводим и количество совпадений, и сами совпавшие леммы | |
matched_lemmas_str = ", ".join(sorted(data['matched_lemmas'])) | |
advantage_text += f"- {category}: {data['count']} совпадений (леммы: {matched_lemmas_str})\n" | |
else: | |
advantage_text += "- Нет совпадений.\n" | |
advantage_text += "\n---\n" | |
results.append(advantage_text) | |
if not results: | |
return "Для выбранного продукта не найдено преимуществ.", None | |
return "\n".join(results), graph_html | |
def create_category_graph(product, advantages, personalization_keywords): | |
""" | |
Создаёт граф связей между продуктом, его преимуществами и категориями персонализации. | |
Возвращает HTML-код для отображения графа в iframe. | |
""" | |
net = Network(notebook=False, height="500px", width="100%", directed=True, cdn_resources='in_line') # Используем встроенные ресурсы | |
# Добавляем узел для продукта | |
net.add_node(product, label=product, color="lightblue", size=30) | |
# Проходим по всем преимуществам продукта | |
for advantage in advantages: | |
# Добавляем узел для преимущества | |
net.add_node(advantage, label=advantage, color="orange", size=20) | |
net.add_edge(product, advantage) # Связь продукта с преимуществом | |
# Анализируем преимущество и добавляем связи с категориями | |
matches = classify_advantage(advantage, personalization_keywords) | |
for category, data in matches: | |
net.add_node(category, label=category, color="green", size=15) | |
net.add_edge(advantage, category) # Связь преимущества с категорией | |
# Генерируем HTML-код для графа | |
html = net.generate_html(notebook=False) | |
# Заменяем одинарные кавычки на двойные | |
html = html.replace("'", '"') | |
# Возвращаем iframe с HTML-кодом графа | |
return f""" | |
<iframe | |
width="100%" | |
height="600" | |
frameborder="0" | |
srcdoc='{html}'> | |
</iframe> | |
""" | |
with gr.Blocks() as demo: | |
gr.Markdown("## Классификация преимуществ по признакам персонализации") | |
gr.Markdown("**Шаг 1:** Загрузите Excel-файл с двумя столбцами: 'Продукт' и 'Преимущество'.") | |
file_input = gr.File(label="Загрузите Excel-файл", file_types=[".xlsx"]) | |
load_button = gr.Button("Загрузить файл") | |
load_status = gr.Markdown("") | |
gr.Markdown("**Шаг 2:** Выберите продукт из списка (по умолчанию ничего не выбрано).") | |
product_dropdown = gr.Dropdown(choices=[], label="Продукты", value=None) | |
analyze_button = gr.Button("Анализировать") | |
output_text = gr.Markdown("") | |
output_graph = gr.HTML(label="Визуализация графа") | |
# Логика при нажатии "Загрузить файл" | |
def on_file_upload(file): | |
unique_products, status_message = load_excel(file) | |
return gr.update(choices=unique_products), status_message | |
load_button.click( | |
fn=on_file_upload, | |
inputs=file_input, | |
outputs=[product_dropdown, load_status] | |
) | |
# Логика при нажатии "Анализировать" | |
analyze_button.click( | |
fn=analyze, | |
inputs=product_dropdown, | |
outputs=[output_text, output_graph] | |
) | |
# Запускаем демо | |
if __name__ == "__main__": | |
demo.launch(debug=True) |