/
artyuhovs
/
AIFeedBackTrainingBot
Обзор
Документация
Войти
/
artyuhovs
/
AIFeedBackTrainingBot
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
tests/test_text_utils.py
375 строк
15 KB
Codex
Завершить архитектурное оздоровление и исправить итоги
15 июл 2026, 11:05
15 июл 2026, 11:05
c65f9a6
Код
Авторство
О чём код?
from app.services.gigachat_client import ( format_feedback_framework, format_feedback_quality, is_feedback_detected, ) from app.utils.text_utils import remove_filler_words, split_telegram_text, text_stats def test_short_text_stays_single_message() -> None: assert split_telegram_text("короткий текст") == ["короткий текст"] def test_long_text_is_split_for_telegram() -> None: text = "слово " * 1200 chunks = split_telegram_text(text, limit=100) assert len(chunks) > 1 assert all(len(chunk) <= 100 for chunk in chunks) assert " ".join(chunks).replace(" ", " ").strip() == text.strip() def test_emoji_are_counted_as_two_telegram_utf16_units() -> None: text = "ab😀" chunks = split_telegram_text(text, limit=3) assert chunks == ["ab", "😀"] assert all(len(chunk.encode("utf-16-le")) // 2 <= 3 for chunk in chunks) def test_emoji_chunks_never_exceed_telegram_utf16_limit() -> None: text = "😀" * 5 chunks = split_telegram_text(text, limit=4) assert chunks == ["😀😀", "😀😀", "😀"] assert all(len(chunk.encode("utf-16-le")) // 2 <= 4 for chunk in chunks) def test_text_stats_counts_words_and_characters() -> None: text = " Привет, мир! 42 слова. " stats = text_stats(text) assert stats.words == 4 assert stats.characters == len(text.strip()) def test_remove_filler_words_removes_common_fillers() -> None: text = "Эээ, ну, я как бы сделал, типа, задачу, в общем то, вот." assert remove_filler_words(text) == "Я сделал задачу." def test_remove_filler_words_keeps_words_with_same_letters() -> None: text = "Внутри слова не трогаем, ну-ка проверим, но ну удаляем." assert remove_filler_words(text) == "Внутри слова не трогаем, ну-ка проверим, но удаляем." def test_remove_filler_words_keeps_clean_text_unchanged() -> None: text = "Я сделал задачу и рассказал результат." assert remove_filler_words(text) == text def test_format_feedback_quality_turns_json_into_readable_text() -> None: text = """ { "prompt_version": "feedback_general_quality_v4", "course_score_10": 8, "self_assessment_poll_answer": 8, "critical_general_status": "mixed", "verdict": "ОС полезная, но можно снизить давление.", "course_numeric_criteria": [ { "id": "permission_and_contract", "weight": 1, "status": "met", "score": 1, "points": 1, "evidence": "Можно дам обратную связь?", "comment": "Разрешение есть." }, { "id": "neutral_respectful_tone", "weight": 1, "status": "met", "score": 1, "points": 1, "evidence": "Говорящий спокойно описывает ситуацию.", "comment": "Тон уважительный." }, { "id": "no_pressure_or_devaluation", "weight": 1, "status": "partially_met", "score": 0.5, "points": 0.5, "evidence": "Нужно было сделать нормально.", "comment": "Есть оттенок давления." }, { "id": "reason_for_feedback", "weight": 1, "status": "met", "score": 1, "points": 1, "evidence": "Хочу обсудить вчерашнюю встречу.", "comment": "Причина понятна." }, { "id": "focused_on_one_topic", "weight": 1, "status": "met", "score": 1, "points": 1, "evidence": "Речь только о встрече.", "comment": "Фокус удержан." }, { "id": "no_overgeneralizations", "weight": 2, "status": "met", "score": 1, "points": 2, "evidence": "Нет слов всегда или никогда.", "comment": "Обобщений нет." }, { "id": "facts_not_judgments", "weight": 2, "status": "partially_met", "score": 0.5, "points": 1, "evidence": "Ты перебил меня два раза.", "comment": "Есть факт, но рядом оценка." }, { "id": "useful_for_recipient", "weight": 1, "status": "partially_met", "score": 0.5, "points": 0.5, "evidence": "Так команде будет проще договориться.", "comment": "Польза обозначена частично." } ], "strengths": ["Есть факт"], "what_is_missing_for_10": ["Снизить давление"], "recommendations": ["Сказать без оценки нормально/ненормально"], "better_version": "Можно дам обратную связь по встрече?", "repeat_practice_task": "Запиши еще раз с запросом разрешения." } """ formatted = format_feedback_quality(text) assert formatted.startswith("<b>Оценка качества ОС: 8/10</b>") assert "<b>Фреймворк:</b>" not in formatted assert "<blockquote expandable>Универсальная оценка качества ОС" in formatted assert "<b>Итог:</b>" not in formatted assert "Модельный итог в JSON" not in formatted assert "Ответ для самооценки" not in formatted assert "Следующая попытка" not in formatted assert "Комментарий" not in formatted expected_order = [ "Разрешение/контракт", "Нейтральный тон", "Без давления и обесценивания", "Причина обращения", "Фокус на одном эпизоде/теме", "Нет обобщений", "Факты вместо оценок", "Польза для адресата", ] cursor = -1 for label in expected_order: position = formatted.index(label) assert position > cursor cursor = position assert "<b>Разрешение/контракт: 1/1</b>" in formatted assert "<b>Без давления и обесценивания: 0.5/1</b>" in formatted assert "<b>Факты вместо оценок: 1/2</b>" in formatted assert "Цитата:" not in formatted assert "<b>КРИТЕРИИ И БАЛЛЫ</b>" in formatted assert "<blockquote expandable>Можно дам обратную связь?</blockquote>" in formatted assert "✅ <i>Разрешение есть:</i>\n<blockquote expandable>Можно дам обратную связь?</blockquote>" in formatted assert ( "⚠️ <i>Есть оттенок давления:</i>\n<blockquote expandable>Нужно было сделать нормально.</blockquote>" in formatted ) assert "- <b>" not in formatted assert "score 1" not in formatted assert "permission_and_contract" not in formatted assert "Оценка:" not in formatted assert "Итог: 8/10" not in formatted assert "<b>ИТОГО:</b>" not in formatted assert "<b>КАЧЕСТВЕННАЯ ОС</b>" in formatted assert "<b>👍 Что уже хорошо:</b>" in formatted assert "<b>🎯 Что не хватило до 10:</b>" in formatted assert "<b>🛠️ Что поправить в формулировках:</b>" in formatted assert "<b>💬 Как можно сказать сильнее:</b>" in formatted assert "Что не хватило до 5" not in formatted assert "Можно дам обратную связь" in formatted assert formatted.endswith("Можно дам обратную связь по встрече?") def test_feedback_detected_reads_general_json_flag() -> None: assert is_feedback_detected('{"detected_feedback": true}') is True assert is_feedback_detected('{"detected_feedback": false}') is False def test_format_feedback_quality_extracts_json_from_model_preface() -> None: text = """ Вот оценка: ```json { "prompt_version": "feedback_general_quality_v4", "course_score_10": 1, "course_numeric_criteria": [ { "id": "unknown_english_criterion", "weight": 1, "status": "met", "score": 1, "points": 1, "evidence": "Фрагмент есть.", "comment": "Критерий выполнен." } ] } ``` """ formatted = format_feedback_quality(text) assert formatted.startswith("<b>Оценка качества ОС: 1/10</b>") assert "<b>Критерий: 1/1</b>" in formatted assert "unknown_english_criterion" not in formatted assert "<b>ИТОГО:</b>" not in formatted def test_format_feedback_framework_turns_json_into_readable_text() -> None: rewritten = ( "На вчерашней встрече ты перебил меня два раза, из-за этого мы потеряли фокус. " "Давай в следующий раз договоримся дослушивать." ) text = """ { "prompt_version": "feedback_framework_quality_v6", "framework_name": "COIN", "status": "evaluated", "framework_fit": "does_not_fit", "verdict": "COIN собран частично.", "framework_score_10": 3.5, "framework_elements": [ { "id": "context", "weight": 2, "status": "met", "score": 1, "points": 2, "evidence": "Есть контекст.", "comment": "Контекст обозначен." }, { "id": "observation_factual", "weight": 3, "status": "partially_met", "score": 0.5, "points": 1.5, "evidence": "Есть наблюдение.", "comment": "Наблюдение частично фактическое." } ], "hygiene_elements": [ { "id": "permission_and_contract", "weight": 0.25, "status": "met", "score": 1, "points": 0.25, "evidence": "Спасибо, что уделил время.", "comment": "Есть уважительное открытие разговора." } ], "main_framework_gaps": ["Нет влияния и следующего шага."], "what_is_missing_for_10": ["Добавить impact и next steps."], "recommendations": ["Разделить наблюдение и влияние."], "rewritten_by_framework": "__REWRITTEN__" } """.replace("__REWRITTEN__", rewritten) formatted = format_feedback_framework(text) assert formatted.startswith("<b>Оценка по COIN: 3.5/10</b>") assert "<b>Фреймворк:</b>" not in formatted assert "<b>Статус:</b> оценено" not in formatted assert "<blockquote expandable>COIN: Context, Observation, Impact, Next steps." in formatted assert "<b>КРИТЕРИИ И БАЛЛЫ</b>" in formatted assert "<b>Контекст: 2/2</b>" in formatted assert "<b>Наблюдение/факт: 1.5/3</b>" in formatted assert "Цитата:" not in formatted assert "<blockquote expandable>Есть контекст.</blockquote>" in formatted assert "✅ <i>Контекст обозначен:</i>\n<blockquote expandable>Есть контекст.</blockquote>" in formatted assert ( "⚠️ <i>Наблюдение частично фактическое:</i>\n<blockquote expandable>Есть наблюдение.</blockquote>" in formatted ) assert "- <b>" not in formatted assert "Гигиена ОС" not in formatted assert "Разрешение/контракт" not in formatted assert "Context:" not in formatted assert "Observation:" not in formatted assert "Спасибо, что уделил время." not in formatted assert "Оценка:" not in formatted assert "<b>КАЧЕСТВЕННАЯ ОС</b>" in formatted assert "<b>📌 Главные расхождения:</b>" in formatted assert "<b>🎯 Что не хватило до 10:</b>" in formatted assert "<b>🛠️ Что поправить в формулировках:</b>" in formatted assert "<b>💬 Как можно сказать сильнее:</b>" in formatted assert "Как сказать по этому фреймворку" not in formatted assert "Что не хватило до 5" not in formatted assert "<b>ИТОГО:</b>" not in formatted assert formatted.endswith("Давай в следующий раз договоримся дослушивать.") def test_format_feedback_framework_uses_same_card_sections_for_coin_and_boff() -> None: coin = """ { "prompt_version": "feedback_framework_quality_v6", "framework_name": "COIN", "framework_score_10": 7, "framework_elements": [ {"id": "context", "weight": 2, "status": "met", "points": 2, "evidence": "Вчера.", "comment": "Контекст есть."} ], "main_framework_gaps": ["Не хватает наблюдения."], "what_is_missing_for_10": ["Добавить observation."], "recommendations": ["Сказать фактами."], "rewritten_by_framework": "Вчера на встрече..." } """ boff_element = ( '{"id": "behavior_factual", "weight": 2.5, "status": "met", "points": 2.5, ' '"evidence": "Ты перебил.", "comment": "Поведение есть."}' ) boff = """ { "prompt_version": "feedback_framework_quality_v6", "framework_name": "BOFF", "framework_score_10": 7, "framework_elements": [ __BOFF_ELEMENT__ ], "main_framework_gaps": ["Не хватает чувства."], "what_is_missing_for_10": ["Добавить feelings."], "recommendations": ["Назвать свою реакцию."], "rewritten_by_framework": "Когда ты перебил..." } """.replace("__BOFF_ELEMENT__", boff_element) coin_formatted = format_feedback_framework(coin) boff_formatted = format_feedback_framework(boff) shared_sections = [ "<b>КРИТЕРИИ И БАЛЛЫ</b>", "<b>КАЧЕСТВЕННАЯ ОС</b>", "<b>📌 Главные расхождения:</b>", "<b>🎯 Что не хватило до 10:</b>", "<b>🛠️ Что поправить в формулировках:</b>", "<b>💬 Как можно сказать сильнее:</b>", ] for section in shared_sections: assert section in coin_formatted assert section in boff_formatted assert coin_formatted.startswith("<b>Оценка по COIN: 2/10</b>") assert boff_formatted.startswith("<b>Оценка по BOFF: 2.5/10</b>") assert "<blockquote expandable>COIN:" in coin_formatted assert "<blockquote expandable>BOFF:" in boff_formatted assert "<b>ИТОГО:</b>" not in coin_formatted assert "<b>ИТОГО:</b>" not in boff_formatted