File size: 10,373 Bytes
cd9ea1c
d59ffef
 
 
 
cd9ea1c
5829a54
d59ffef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd9ea1c
d59ffef
 
 
 
5829a54
d59ffef
 
 
 
 
 
 
 
5829a54
d59ffef
 
 
 
 
 
 
5829a54
d59ffef
 
cd9ea1c
d59ffef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5829a54
d59ffef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd9ea1c
d59ffef
 
 
 
 
 
 
 
 
 
 
24b46ce
d59ffef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
965b4c8
d59ffef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5829a54
d59ffef
 
 
 
 
5829a54
cd9ea1c
d59ffef
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
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)