File size: 7,509 Bytes
bcb314a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations
import re
import pandas as pd
from typing import List


def enhanced_q4_features(df: pd.DataFrame) -> pd.DataFrame:
    """
    Улучшенные фичи для вопроса 4 - ВСЯ ЛОГИКА В ОДНОЙ ФУНКЦИИ
    """
    out = df.copy()

    if "question_number" not in out.columns:
        raise ValueError("В датафрейме нет колонки 'question_number'")

    for col in ["question_text", "answer_text"]:
        if col not in out.columns:
            out[col] = ""

    mask = out["question_number"] == 4
    q = out.loc[mask, "question_text"].fillna("").astype(str)
    a = out.loc[mask, "answer_text"].fillna("").astype(str)

    # --- БАЗОВЫЕ ФИЧИ ---
    PLACE_WORDS = r"(?:кухн|парк|сквер|берег|река|дом|улиц|квартир|комнат|набережн)"
    SEASON_WORDS = r"(?:лето|зим|весн|осен|снег|жарко|холодно|листопад|сосулк)"
    PEOPLE_WORDS = r"(?:мама|папа|дедушк|бабушк|женщин|мужчин|ребен|дет|сем|дочка|сын|парень|девушк)"
    ACTION_WORDS = r"(?:игра|моет|готов|накрыва|бежит|катает|кормит|сидит|спит|несет|перепрыг|гуляет)"
    DETAIL_WORDS = r"(?:одет|рост|волос|глаз|характер|возраст|пальто|рубашк|кроссовк|плать|кофт|ботинк)"
    PIC_INTRO = r"(?:на картинке|на рисунке|я вижу|изображен)"

    CHILDREN_Q = r"(?:сколько детей|детям|о них|как.*играете.*дет(?:ями|ьми))"
    FREE_TIME_Q = r"(?:свободн(?:ое|ым)\s+врем|как.*проводите.*время|выходн(?:ой|ые))"

    # Базовые детекции
    has_place_time = a.str.contains(PLACE_WORDS, case=False, regex=True) | a.str.contains(SEASON_WORDS, case=False,
                                                                                          regex=True)
    has_people = a.str.contains(PEOPLE_WORDS, case=False, regex=True)
    has_actions = a.str.contains(ACTION_WORDS, case=False, regex=True)
    has_detail = a.str.contains(DETAIL_WORDS, case=False, regex=True)

    expects_children = q.str.contains(CHILDREN_Q, case=False, regex=True)
    expects_free = q.str.contains(FREE_TIME_Q, case=False, regex=True)

    answered_children = a.str.contains(r"(?:у меня.*дет(?:и|ей)|в моей семье.*дет|сын|дочк|мы.*играем)", case=False,
                                       regex=True)
    answered_free = a.str.contains(
        r"(?:свободн.*врем|обычно.*(?:в выходн|по выходн)|люблю|занимаюсь|хожу|смотрю|рисую|спорт|танц)", case=False,
        regex=True)

    non_cyr_ratio = a.apply(lambda t: (len(re.findall(r"[A-Za-z]", t)) / max(1, len(t))))
    non_cyr_ratio = pd.to_numeric(non_cyr_ratio, errors="coerce").fillna(0.0).astype(float)

    picture_first = a.str.contains(PIC_INTRO, case=False, regex=True)
    dont_know = a.str.contains(r"(?:не знаю|не понимаю|трудно сказать|не могу описать)", case=False, regex=True)

    # --- УЛУЧШЕННЫЕ ФИЧИ ---

    # 1. Детекция всех подвопросов
    subquestion_patterns = {
        'place_time': r'(место|время|сезон|лето|зима|весна|осень|кухня|парк|улица)',
        'people': r'(люди|человек|мужчина|женщина|ребенок|дети|семья|бабушка|дедушка)',
        'actions': r'(делает|стоит|сидит|играет|готовит|моет|читает|смотрит)',
        'person_detail': r'(одет|носит|платье|рубашка|брюки|волосы|глаза)',
        'family_children': r'(дет[еи]|семь[яеи]|сын|дочь|брат|сестра)',
        'playing': r'(игра[ею]|играем|гуля[ею]|занимаюсь)'
    }

    for name, pattern in subquestion_patterns.items():
        out.loc[mask, f'q4_has_{name}'] = a.str.contains(pattern, case=False, regex=True).astype(int)

    # 2. Структура ответа
    def analyze_structure(text: str) -> dict:
        text_lower = text.lower()

        has_intro = any(marker in text_lower for marker in [
            'на картинке', 'на рисунке', 'изображен', 'вижу', 'показан'
        ])

        has_personal = any(marker in text_lower for marker in [
            'у меня', 'в моей', 'мои', 'я ', 'мы ', 'наш'
        ])

        sentences = re.split(r'[.!?]+', text)
        num_sentences = len([s for s in sentences if len(s.strip()) > 10])

        return {
            'has_intro': has_intro,
            'has_personal': has_personal,
            'num_sentences': num_sentences
        }

    structure_features = a.apply(analyze_structure).apply(pd.Series)
    out.loc[mask, 'q4_has_intro'] = structure_features['has_intro'].astype(int)
    out.loc[mask, 'q4_has_personal'] = structure_features['has_personal'].astype(int)
    out.loc[mask, 'q4_num_sentences'] = structure_features['num_sentences']

    # 3. Полнота ответа
    subq_columns = [f'q4_has_{name}' for name in subquestion_patterns.keys()]
    out.loc[mask, 'q4_coverage_ratio'] = out.loc[mask, subq_columns].sum(axis=1) / len(subq_columns)

    # 4. Базовые слоты (оригинальные фичи)
    slots = (has_place_time.astype(int) + has_people.astype(int) + has_actions.astype(int) + has_detail.astype(int))
    slots_covered = (slots / 4.0).clip(0, 1)

    personal_ok = (
            (expects_children & answered_children) |
            (expects_free & answered_free) |
            (~expects_children & ~expects_free)
    )

    # Заполняем базовые фичи
    float_cols = ["q4_slots_covered", "q4_non_cyr_ratio"]
    int_cols = [
        "q4_has_place_time", "q4_has_people", "q4_has_actions", "q4_has_detail",
        "q4_expects_children", "q4_expects_free", "q4_answered_personal",
        "q4_picture_first", "q4_dont_know"
    ]

    for name in float_cols:
        out[name] = 0.0
    for name in int_cols:
        out[name] = 0

    out.loc[mask, "q4_slots_covered"] = slots_covered.astype(float).to_numpy()
    out.loc[mask, "q4_has_place_time"] = has_place_time.astype(int).to_numpy()
    out.loc[mask, "q4_has_people"] = has_people.astype(int).to_numpy()
    out.loc[mask, "q4_has_actions"] = has_actions.astype(int).to_numpy()
    out.loc[mask, "q4_has_detail"] = has_detail.astype(int).to_numpy()
    out.loc[mask, "q4_expects_children"] = expects_children.astype(int).to_numpy()
    out.loc[mask, "q4_expects_free"] = expects_free.astype(int).to_numpy()
    out.loc[mask, "q4_answered_personal"] = personal_ok.astype(int).to_numpy()
    out.loc[mask, "q4_non_cyr_ratio"] = non_cyr_ratio.astype(float).to_numpy()
    out.loc[mask, "q4_picture_first"] = picture_first.astype(int).to_numpy()
    out.loc[mask, "q4_dont_know"] = dont_know.astype(int).to_numpy()

    return out


# Совместимость с существующим кодом
def add_q4_features(df: pd.DataFrame) -> pd.DataFrame:
    """Совместимое имя функции для predict.py"""
    return enhanced_q4_features(df)