Spaces:
Sleeping
Sleeping
File size: 6,394 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 |
# src/data_cleaning.py
import re
import pandas as pd
from bs4 import BeautifulSoup
# ⚠️ новый импорт: извлекаем речь тестируемого
from src.text_roles import extract_tester_reply
# ---------- утилиты очистки ----------
def clean_html(text: str) -> str:
"""Удаляем HTML/разметку из текста вопроса/ответа."""
if pd.isna(text):
return ""
return BeautifulSoup(str(text), "lxml").get_text(separator=" ", strip=True)
# эвристический парсер на случай, если в транскрипте есть роли
# (оставляем куски после "Тестируемый:/Кандидат:/Студент:" до следующего "Экзаменатор:")
_SPEAKER_PAT = re.compile(
r"(?:Тестируемый|Кандидат|Студент)\s*:\s*(.+?)(?=(?:Экзаменатор|Преподаватель|Собеседник)\s*:|$)",
re.IGNORECASE | re.DOTALL,
)
def extract_answer(transcript: str) -> str:
"""Базовое извлечение ответа из общей транскрипции (если есть метки ролей)."""
if not isinstance(transcript, str) or not transcript.strip():
return ""
t = transcript.replace("\r", "\n")
chunks = _SPEAKER_PAT.findall(t)
joined = " ".join(x.strip() for x in chunks) if chunks else t
return re.sub(r"\s+", " ", joined).strip()
# ---------- поиски колонок ----------
_CANDIDATES = {
"question_number": [
"номер вопроса", "порядковый номер", "порядковый номер вопроса",
"№ вопроса", "вопрос №", "номер", "question_number"
],
"question_text": [
"текст вопроса", "вопрос", "формулировка вопроса",
"question_text", "question"
],
"transcript": [
"транскрипция ответа", "транскрибация ответа", "транскрипт",
"диалог", "ответ (текст)", "аудио транскрипт", "текст ответа",
"transcript", "answer_text"
],
"score": [
"оценка", "оценка экзаменатора", "балл", "баллы",
"score", "target"
],
}
def _find_column(df: pd.DataFrame, keys: list[str]) -> str:
"""Ищем колонку по списку рус/англ вариантов (точно или по подстроке)."""
# уже стандартизированный файл? — возвращаем ключ, если он есть
for k in keys:
if k in df.columns:
return k
norm = {str(c).lower().strip(): c for c in df.columns}
for key in keys:
k = key.lower().strip()
if k in norm:
return norm[k]
for nk, orig in norm.items():
if k in nk: # частичное совпадение
return orig
raise KeyError(f"Не удалось найти колонку из набора: {keys} в {list(df.columns)}")
# ---------- основная функция ----------
def prepare_dataframe(df: pd.DataFrame) -> pd.DataFrame:
"""
Приводим датафрейм к стандартному виду:
columns = [question_number, question_text, answer_text, score]
Умеет работать и с «сырым» CSV из задания, и с уже обработанным,
где колонки могли быть: question_number, question_text, answer_text, score.
"""
cols = set(df.columns)
# кейс: файл уже в стандарте — просто мягко нормализуем
if {"question_number", "question_text", "answer_text", "score"}.issubset(cols):
out = df[["question_number", "question_text", "answer_text", "score"]].copy()
# подчистим HTML и лишние пробелы
out["question_text"] = out["question_text"].apply(clean_html).str.replace(r"\s+", " ", regex=True).str.strip()
out["answer_text"] = (
out["answer_text"]
.fillna("").astype(str)
.apply(clean_html)
.apply(extract_tester_reply) # ⚠️ извлекаем реплики тестируемого
.str.replace(r"\s+", " ", regex=True).str.strip()
)
# приведение типа номера вопроса (если возможно)
with pd.option_context("mode.chained_assignment", None):
try:
out["question_number"] = pd.to_numeric(out["question_number"], errors="coerce").astype("Int64")
except Exception:
pass
return out
# кейс: «сырой» файл из задания — ищем русские колонки
qnum_col = _find_column(df, _CANDIDATES["question_number"])
qtxt_col = _find_column(df, _CANDIDATES["question_text"])
tran_col = _find_column(df, _CANDIDATES["transcript"])
score_col = _find_column(df, _CANDIDATES["score"])
out = pd.DataFrame()
out["question_number"] = df[qnum_col]
out["question_text"] = df[qtxt_col].apply(clean_html)
# 1) базовое извлечение по ролям (если есть метки)
# 2) затем более мягкая эвристика extract_tester_reply (из src.text_roles)
out["answer_text"] = (
df[tran_col].apply(extract_answer)
.fillna("").astype(str)
.apply(clean_html)
.apply(extract_tester_reply)
)
out["score"] = df[score_col]
# финальная нормализация пробелов
out["question_text"] = out["question_text"].str.replace(r"\s+", " ", regex=True).str.strip()
out["answer_text"] = out["answer_text"].str.replace(r"\s+", " ", regex=True).str.strip()
# аккуратно приводим номер вопроса к целочисленному типу, если возможно
with pd.option_context("mode.chained_assignment", None):
try:
out["question_number"] = pd.to_numeric(out["question_number"], errors="coerce").astype("Int64")
except Exception:
pass
return out
|