Spaces:
Sleeping
Sleeping
Download app.py from OlenaBondaruk/lessonObserver: direct link, hf CLI and curl.
- Browser
- Download file 44.6 kB
-
https://huggingface.co/spaces/OlenaBondaruk/lessonObserver/resolve/main/app.py
- Command line
-
hf download hf://spaces/OlenaBondaruk/lessonObserver/app.py
-
curl -L -o app.py https://huggingface.co/spaces/OlenaBondaruk/lessonObserver/resolve/main/app.py
44.6 kB
| import gradio as gr | |
| import html | |
| import time | |
| import re | |
| # ── Словники для аналізу ────────────────────────────────────────── | |
| PARTS_KEYWORDS = { | |
| "організаційний момент": ["організаційний", "привітання", "перевірка присутніх", "готовність"], | |
| "актуалізація знань": ["актуалізація", "пригадаємо", "повторення попередньої", "що ви знаєте"], | |
| "мотивація": ["мотивація", "навіщо", "проблемне питання", "загадка", "цікавий факт", "мотив"], | |
| "вивчення нового матеріалу": ["новий матеріал", "тема уроку", "ознайомимося", "вивчення", "нова тема"], | |
| "закріплення": ["закріплення", "виконайте вправу", "завдання", "практик", "вправ"], | |
| "підсумок": ["підсумок", "що нового дізналися", "рефлексія", "висновок", "підведемо"], | |
| "домашнє завдання": ["домашнє завдання", "д/з", "вдома виконайте", "додому"], | |
| } | |
| METHODS_KEYWORDS = { | |
| "пояснення вчителя": ["пояснення", "розповідає", "розповідь", "лекція", "вчитель пояснює"], | |
| "бесіда": ["бесіда", "запитання", "запитує", "відповідають", "обговорення", "діалог"], | |
| "демонстрація": ["демонстра", "показує", "презентація", "таблиця", "схема", "малюнок", "ілюстрац"], | |
| "практична робота": ["практична", "виконують вправу", "розв'язують", "пишуть", "розв'яжіть"], | |
| "самостійна робота": ["самостійна", "індивідуально", "кожен учень", "самостійно"], | |
| "гра": ["гра", "ігровий", "вікторина", "змагання", "грають"], | |
| "групова робота": ["група", "групова", "команда", "разом", "парна робота", "в парах"], | |
| "проєктна робота": ["проєкт", "дослідження", "міні-проєкт"], | |
| "дискусія": ["дискусія", "думки", "різні точки зору", "полемік", "дебат"], | |
| } | |
| LESSON_TYPE_RULES = [ | |
| ("урок вивчення нового матеріалу", ["новий матеріал", "нова тема", "вперше", "ознайомимося"]), | |
| ("урок закріплення знань", ["закріплення", "вправи", "тренування", "відпрацювання"]), | |
| ("урок повторення та узагальнення", ["повторення", "узагальнення", "систематизація", "пригадаємо"]), | |
| ("урок контролю знань", ["контроль", "тестування", "самостійна робота", "перевірка"]), | |
| ("комбінований урок", []), | |
| ] | |
| SUBJECT_KEYWORDS = { | |
| "математика": ["число", "задач", "приклад", "рівнян", "геометр", "дріб", "додаван", "множен"], | |
| "українська мова": ["слово", "речення", "текст", "орфограф", "частини мови", "дієслово", "іменник"], | |
| "природознавство": ["природа", "рослин", "тварин", "екосистем", "географ", "клімат", "ґрунт"], | |
| "читання": ["читання", "твір", "автор", "казка", "оповідання", "вірш", "герой"], | |
| "історія": ["історія", "подія", "епоха", "держава", "культура", "суспільство"], | |
| "біологія": ["клітина", "організм", "біологія", "еволюція", "фотосинтез"], | |
| } | |
| # ── Аналіз ──────────────────────────────────────────────────────── | |
| def detect_parts(text: str) -> list[str]: | |
| t = text.lower() | |
| return [p for p, kws in PARTS_KEYWORDS.items() if any(k in t for k in kws)] or ["структуру не визначено"] | |
| def detect_methods(text: str) -> list[str]: | |
| t = text.lower() | |
| return [m for m, kws in METHODS_KEYWORDS.items() if any(k in t for k in kws)] or ["загальні методи"] | |
| def detect_lesson_type(text: str) -> str: | |
| t = text.lower() | |
| for lesson_type, kws in LESSON_TYPE_RULES[:-1]: | |
| if any(k in t for k in kws): | |
| return lesson_type | |
| has_new = any(k in t for k in ["новий матеріал", "нова тема", "вивчення"]) | |
| has_fix = any(k in t for k in ["закріплення", "вправи", "практич"]) | |
| if has_new and has_fix: | |
| return "комбінований урок" | |
| if has_new: | |
| return "урок вивчення нового матеріалу" | |
| return "комбінований урок" | |
| def detect_subject(text: str) -> str: | |
| t = text.lower() | |
| scores = {subj: sum(1 for k in kws if k in t) for subj, kws in SUBJECT_KEYWORDS.items()} | |
| best = max(scores, key=scores.get) | |
| return best if scores[best] > 0 else "не визначено" | |
| def extract_topic(text: str) -> str: | |
| """Витягує тему уроку з конспекту""" | |
| patterns = [ | |
| r"тема[:\s]+(.+?)[\n\r]", | |
| r"тема уроку[:\s]+(.+?)[\n\r]", | |
| r"урок[:\s]+(.+?)[\n\r]", | |
| ] | |
| for pattern in patterns: | |
| m = re.search(pattern, text, re.IGNORECASE) | |
| if m: | |
| return m.group(1).strip() | |
| first_line = text.strip().split("\n")[0] | |
| return first_line[:80] if first_line else "не визначено" | |
| def extract_goal(text: str) -> str: | |
| """Витягує мету уроку""" | |
| patterns = [ | |
| r"мета[:\s]+(.+?)[\n\r]", | |
| r"мета уроку[:\s]+(.+?)[\n\r]", | |
| r"завдання уроку[:\s]+(.+?)[\n\r]", | |
| ] | |
| for pattern in patterns: | |
| m = re.search(pattern, text, re.IGNORECASE) | |
| if m: | |
| return m.group(1).strip() | |
| return "не визначено" | |
| def count_activity_words(text: str) -> dict: | |
| """Підраховує показники активності учнів""" | |
| t = text.lower() | |
| return { | |
| "активність учнів": sum(t.count(k) for k in ["учні", "діти", "школярі", "вони виконують"]), | |
| "робота вчителя": sum(t.count(k) for k in ["вчитель", "педагог", "викладач"]), | |
| "взаємодія": sum(t.count(k) for k in ["разом", "обговорення", "відповідають", "запитує"]), | |
| } | |
| FORMS_KEYWORDS = { | |
| "фронтальна робота": ["фронтальна", "весь клас", "усі учні", "клас відповідає"], | |
| "індивідуальна робота": ["індивідуально", "кожен учень", "самостійно", "індивідуальна"], | |
| "парна робота": ["в парах", "парна", "сусід по парті", "разом із сусідом"], | |
| "групова робота": ["група", "групова", "команда", "по групах", "в групах"], | |
| "колективна робота": ["колективна", "спільно", "разом", "увесь клас"], | |
| } | |
| TOOLS_KEYWORDS = { | |
| "підручник": ["підручник", "с.", "сторінка", "впр.", "вправа"], | |
| "зошит": ["зошит", "записують", "пишуть у зошиті"], | |
| "дошка": ["дошка", "на дошці", "записує на дошці"], | |
| "презентація": ["презентація", "слайд", "проєктор", "екран"], | |
| "картки": ["картка", "роздатковий матеріал", "картки"], | |
| "таблиця / схема": ["таблиця", "схема", "малюнок", "ілюстрація", "діаграма"], | |
| "натуральні об'єкти":["гербарій", "модель", "макет", "реальний об'єкт", "муляж"], | |
| "відео / аудіо": ["відео", "аудіо", "запис", "прослуховування", "перегляд"], | |
| } | |
| TASKS_KEYWORDS = { | |
| "читання тексту": ["читають", "прочитайте", "читання"], | |
| "письмові вправи": ["пишуть", "запишіть", "написати", "вправа у зошиті"], | |
| "усні відповіді": ["відповідають", "розкажіть", "усна відповідь", "говорять"], | |
| "розв'язання задач": ["розв'язують", "задача", "розв'яжіть", "приклад"], | |
| "робота з підручником": ["підручник", "відкрийте", "знайдіть у підручнику"], | |
| "заповнення таблиці": ["заповніть таблицю", "заповнюють", "таблицю"], | |
| "малювання / схеми": ["намалюйте", "схема", "зарисуйте", "позначте"], | |
| "тестові завдання": ["тест", "тестові", "обери правильну відповідь"], | |
| "творчі завдання": ["придумайте", "складіть", "творче завдання", "власний приклад"], | |
| "спостереження": ["спостерігають", "розгляньте", "опишіть що бачите"], | |
| } | |
| def detect_forms(text: str) -> list[str]: | |
| t = text.lower() | |
| return [f for f, kws in FORMS_KEYWORDS.items() if any(k in t for k in kws)] or ["не визначено"] | |
| def detect_tools(text: str) -> list[str]: | |
| t = text.lower() | |
| return [tool for tool, kws in TOOLS_KEYWORDS.items() if any(k in t for k in kws)] or ["не визначено"] | |
| def detect_tasks(text: str) -> list[str]: | |
| t = text.lower() | |
| return [task for task, kws in TASKS_KEYWORDS.items() if any(k in t for k in kws)] or ["не визначено"] | |
| def build_lesson_narrative( | |
| topic: str, | |
| goal: str, | |
| lesson_type: str, | |
| subject: str, | |
| parts: list[str], | |
| methods: list[str], | |
| forms: list[str], | |
| tools: list[str], | |
| tasks: list[str], | |
| teacher: str, | |
| text: str, | |
| ) -> str: | |
| """Генерує детальний текст спостереження уроку у стилі педагогічного протоколу""" | |
| teacher_str = html.escape(teacher) if teacher.strip() else "Вчитель" | |
| topic_str = html.escape(topic) if topic != "не визначено" else "—" | |
| goal_str = html.escape(goal) if goal != "не визначено" else "—" | |
| subj_str = html.escape(subject) if subject != "не визначено" else "" | |
| t = text.lower() | |
| paragraphs = [] | |
| # ── 1. Вступна характеристика ───────────────────────────────── | |
| subj_part = f" з {subj_str}" if subj_str else "" | |
| paragraphs.append( | |
| f"{teacher_str} провів(ла) <b>{html.escape(lesson_type)}</b>{subj_part}. " | |
| f"Тема уроку — «{topic_str}». " | |
| f"Мета: {goal_str}." | |
| ) | |
| # ── 2. Структура уроку ──────────────────────────────────────── | |
| valid_parts = [p for p in parts if p != "структуру не визначено"] | |
| if valid_parts: | |
| parts_list = "; ".join(f"<i>{html.escape(p)}</i>" for p in valid_parts) | |
| paragraphs.append( | |
| f"Урок включав такі <b>структурні етапи</b>: {parts_list}. " | |
| + ("Усі етапи логічно пов'язані між собою та відповідають обраному типу уроку." | |
| if len(valid_parts) >= 5 | |
| else "Окремі структурні етапи у конспекті чітко не виділені — рекомендується деталізувати план.") | |
| ) | |
| else: | |
| paragraphs.append( | |
| "Структурні етапи уроку у конспекті окремо не позначені." | |
| ) | |
| # ── 3. Організаційний момент / мотивація ───────────────────── | |
| org_phrases = [] | |
| if any(k in t for k in ["організаційний", "привітання", "готовність"]): | |
| org_phrases.append( | |
| f"На початку уроку {teacher_str} організував(ла) клас: " | |
| "привітав(ла) учнів, перевірив(ла) готовність робочих місць." | |
| ) | |
| if any(k in t for k in ["мотивація", "загадка", "цікавий факт", "проблемне питання", "мотив"]): | |
| org_phrases.append( | |
| "Для мотивації навчальної діяльності було використано проблемно-ігровий прийом або " | |
| "цікаве запитання, що зацікавило учнів та налаштувало їх на роботу." | |
| ) | |
| if any(k in t for k in ["актуалізація", "пригадаємо", "повторення"]): | |
| org_phrases.append( | |
| "Актуалізація опорних знань відбувалась через усне опитування або повторення " | |
| "раніше вивченого матеріалу." | |
| ) | |
| if org_phrases: | |
| paragraphs.append(" ".join(org_phrases)) | |
| # ── 4. Методи навчання ──────────────────────────────────────── | |
| valid_methods = [m for m in methods if m != "загальні методи"] | |
| if valid_methods: | |
| m_str = ", ".join(f"<b>{html.escape(m)}</b>" for m in valid_methods) | |
| paragraphs.append( | |
| f"У ході уроку {teacher_str} застосовував(ла) такі методи навчання: {m_str}. " | |
| ) | |
| method_details = [] | |
| if any(k in t for k in ["пояснення", "розповідає", "розповідь"]): | |
| method_details.append( | |
| "Словесний метод — <b>пояснення</b> — використовувався для ознайомлення учнів " | |
| "з новим матеріалом. Вчитель чітко й доступно викладав навчальний зміст, " | |
| "ілюструючи пояснення прикладами." | |
| ) | |
| if any(k in t for k in ["бесіда", "запитання", "відповідають", "обговорення"]): | |
| method_details.append( | |
| "<b>Бесіда</b> застосовувалась як метод діалогічної взаємодії: " | |
| "вчитель ставив запитання, учні формулювали відповіді, " | |
| "що сприяло розвитку мовлення та критичного мислення." | |
| ) | |
| if any(k in t for k in ["демонстра", "таблиця", "схема", "презентація", "малюнок"]): | |
| method_details.append( | |
| "<b>Наочно-демонстраційний метод</b> реалізовувався через показ таблиць, " | |
| "схем або ілюстрацій, що забезпечувало наочність сприймання нового матеріалу." | |
| ) | |
| if any(k in t for k in ["практична", "вправу", "розв'язують", "пишуть"]): | |
| method_details.append( | |
| "<b>Практичний метод</b> — виконання вправ і завдань — забезпечував " | |
| "відпрацювання навичок та закріплення вивченого." | |
| ) | |
| if any(k in t for k in ["самостійна", "індивідуально", "кожен учень"]): | |
| method_details.append( | |
| "<b>Самостійна робота</b> сприяла формуванню вміння учнів працювати " | |
| "без безпосередньої допомоги вчителя та розвитку навчальної самостійності." | |
| ) | |
| if any(k in t for k in ["гра", "ігровий", "вікторина"]): | |
| method_details.append( | |
| "<b>Ігровий метод</b> підвищував мотивацію та активність учнів, " | |
| "забезпечував емоційне залучення до навчального процесу." | |
| ) | |
| if method_details: | |
| paragraphs.extend(method_details) | |
| # ── 5. Форми організації ────────────────────────────────────── | |
| valid_forms = [f for f in forms if f != "не визначено"] | |
| if valid_forms: | |
| f_str = ", ".join(f"<b>{html.escape(f)}</b>" for f in valid_forms) | |
| form_comment = "" | |
| if "індивідуальна робота" in valid_forms and "фронтальна робота" in valid_forms: | |
| form_comment = ( | |
| " Поєднання фронтальної та індивідуальної форм роботи " | |
| "забезпечувало як колективне засвоєння, так і особистісну перевірку знань кожного учня." | |
| ) | |
| elif "групова робота" in valid_forms: | |
| form_comment = ( | |
| " Групова форма роботи розвивала комунікативні навички " | |
| "та вміння співпрацювати в команді." | |
| ) | |
| paragraphs.append( | |
| f"<b>Форми організації навчальної діяльності:</b> {f_str}.{form_comment}" | |
| ) | |
| # ── 6. Засоби навчання ──────────────────────────────────────── | |
| valid_tools = [t_ for t_ in tools if t_ != "не визначено"] | |
| if valid_tools: | |
| tools_str = ", ".join(f"<b>{html.escape(t_)}</b>" for t_ in valid_tools) | |
| paragraphs.append( | |
| f"<b>Засоби навчання</b>, використані на уроці: {tools_str}. " | |
| "Добір засобів відповідав змісту уроку та віковим особливостям учнів." | |
| ) | |
| # ── 7. Завдання учнів (деталізовано) ───────────────────────── | |
| task_details = [] | |
| if any(k in t for k in ["читають", "прочитайте", "читання"]): | |
| task_details.append( | |
| "Учні <b>читали текст</b> (або умову задачі), що розвивало навички " | |
| "свідомого читання та розуміння навчального матеріалу." | |
| ) | |
| if any(k in t for k in ["пишуть", "запишіть", "написати", "у зошиті", "зошит"]): | |
| task_details.append( | |
| "Виконувались <b>письмові вправи у зошитах</b>: учні записували " | |
| "відповіді, вирази або слова відповідно до завдань вчителя." | |
| ) | |
| if any(k in t for k in ["відповідають", "розкажіть", "усна відповідь"]): | |
| task_details.append( | |
| "Учні давали <b>усні відповіді</b> на запитання вчителя, " | |
| "формулювали власні думки та висновки." | |
| ) | |
| if any(k in t for k in ["задача", "розв'яжіть", "розв'язують", "приклад"]): | |
| task_details.append( | |
| "Виконувалось <b>розв'язання задач і прикладів</b>: " | |
| "учні аналізували умову, обирали дію, записували розв'язок і відповідь." | |
| ) | |
| if any(k in t for k in ["підручник", "відкрийте", "с.", "сторінка", "впр."]): | |
| task_details.append( | |
| "Проводилась <b>робота з підручником</b>: виконання вправ " | |
| "за вказаними сторінками та завданнями." | |
| ) | |
| if any(k in t for k in ["тест", "тестові", "обери правильну"]): | |
| task_details.append( | |
| "Учні виконували <b>тестові завдання</b> для перевірки рівня " | |
| "засвоєння вивченого матеріалу." | |
| ) | |
| if any(k in t for k in ["придумайте", "складіть", "творче"]): | |
| task_details.append( | |
| "Пропонувались <b>творчі завдання</b> — скласти власний приклад, " | |
| "придумати продовження або варіант завдання." | |
| ) | |
| if any(k in t for k in ["таблицю", "заповніть", "схема", "намалюйте"]): | |
| task_details.append( | |
| "Учні <b>заповнювали таблиці або схеми</b>, що сприяло " | |
| "систематизації та структуруванню знань." | |
| ) | |
| if task_details: | |
| paragraphs.append( | |
| f"<b>Завдання, які виконували учні на уроці:</b>" | |
| ) | |
| paragraphs.extend(task_details) | |
| # ── 8. Рефлексія ───────────────────────────────────────────── | |
| if any(k in t for k in ["рефлексія", "підсумок", "що дізналися", "висновок"]): | |
| paragraphs.append( | |
| f"Завершальний етап уроку містив <b>підсумок та рефлексію</b>. " | |
| f"{teacher_str} організував(ла) обговорення результатів роботи: " | |
| "учні відповіли на запитання про те, що нового дізналися, " | |
| "що сподобалося, що було складним. " | |
| "Це сприяло усвідомленню власних навчальних досягнень." | |
| ) | |
| if any(k in t for k in ["домашнє завдання", "д/з", "вдома"]): | |
| paragraphs.append( | |
| f"{teacher_str} повідомив(ла) <b>домашнє завдання</b>, " | |
| "надавши необхідні інструкції щодо його виконання." | |
| ) | |
| # ── 9. Загальний висновок ───────────────────────────────────── | |
| score = len(valid_parts) + len(valid_methods) + len(valid_forms) + len(valid_tools) | |
| if score >= 10: | |
| conclusion = ( | |
| "Урок відзначається <b>чіткою структурою, різноманітністю методів та форм роботи</b>. " | |
| "Конспект свідчить про ретельну підготовку вчителя та методичну грамотність." | |
| ) | |
| elif score >= 5: | |
| conclusion = ( | |
| "Урок має <b>достатньо опрацьований конспект</b> з визначеними методами та формами роботи. " | |
| "Окремі аспекти можна деталізувати додатково." | |
| ) | |
| else: | |
| conclusion = ( | |
| "Конспект потребує <b>детальнішого опрацювання</b>: варто чіткіше прописати " | |
| "методи, форми роботи, засоби навчання та завдання для учнів." | |
| ) | |
| paragraphs.append(conclusion) | |
| return "".join( | |
| f'<p style="margin:0 0 10px 0;line-height:1.75;">{p}</p>' | |
| for p in paragraphs | |
| ) | |
| # ── HTML-компоненти ─────────────────────────────────────────────── | |
| def pill(text: str, bg: str, color: str) -> str: | |
| return (f'<span style="background:{bg};color:{color};padding:4px 12px;' | |
| f'border-radius:14px;font-size:13px;margin:3px;display:inline-block;">' | |
| f'{html.escape(text)}</span>') | |
| def pills(items: list[str], bg: str, color: str) -> str: | |
| return "".join(pill(i, bg, color) for i in items) | |
| def card(icon: str, title: str, body: str, border: str = "#2196F3", bg: str = "#e8f4f8") -> str: | |
| return (f'<div style="background:{bg};border-radius:10px;padding:14px 18px;' | |
| f'margin-bottom:10px;border-left:4px solid {border};">' | |
| f'<div style="font-weight:600;font-size:13px;color:{border};margin-bottom:6px;">' | |
| f'{icon} {html.escape(title)}</div>' | |
| f'<div style="font-size:14px;color:#212121;line-height:1.6;">{body}</div></div>') | |
| def activity_bar(label: str, value: int, max_val: int, color: str) -> str: | |
| pct = min(100, int(value / max(max_val, 1) * 100)) | |
| return (f'<div style="margin-bottom:8px;">' | |
| f'<div style="font-size:12px;color:#555;margin-bottom:3px;">' | |
| f'{html.escape(label)}: {value}</div>' | |
| f'<div style="background:#eee;border-radius:6px;height:10px;">' | |
| f'<div style="background:{color};width:{pct}%;height:10px;' | |
| f'border-radius:6px;transition:width 0.3s;"></div></div></div>') | |
| # ── Валідація ───────────────────────────────────────────────────── | |
| def validate(text: str) -> tuple[bool, str]: | |
| text = text or "" | |
| if not text.strip(): | |
| return False, "⚠️ Будь ласка, введіть конспект уроку." | |
| if len(text.strip()) < 80: | |
| return False, "⚠️ Конспект задто короткий (мінімум 80 символів)." | |
| if len(text) > 20_000: | |
| return False, "⚠️ Конспект задто довгий (максимум 20 000 символів)." | |
| return True, "" | |
| # ── Головна функція ─────────────────────────────────────────────── | |
| def analyze( | |
| lesson_text: str, | |
| teacher: str, | |
| school: str, | |
| date: str, | |
| observer: str, | |
| show_recs: bool, | |
| ) -> tuple[str, str]: | |
| lesson_text = lesson_text or "" | |
| teacher = teacher or "" | |
| school = school or "" | |
| date = date or "" | |
| observer = observer or "" | |
| ok, err = validate(lesson_text) | |
| if not ok: | |
| return f'<div style="color:#c62828;padding:16px;">{err}</div>', "" | |
| try: | |
| t0 = time.time() | |
| # Аналіз | |
| topic = extract_topic(lesson_text) | |
| goal = extract_goal(lesson_text) | |
| lesson_type = detect_lesson_type(lesson_text) | |
| subject = detect_subject(lesson_text) | |
| parts = detect_parts(lesson_text) | |
| methods = detect_methods(lesson_text) | |
| forms = detect_forms(lesson_text) | |
| tools = detect_tools(lesson_text) | |
| tasks = detect_tasks(lesson_text) | |
| activity = count_activity_words(lesson_text) | |
| elapsed = round(time.time() - t0, 3) | |
| # Narrative | |
| narrative = build_lesson_narrative( | |
| topic, goal, lesson_type, subject, | |
| parts, methods, forms, tools, tasks, teacher, lesson_text | |
| ) | |
| # Метадані | |
| meta_items = [v for v in [ | |
| f"Вчитель: {html.escape(teacher)}" if teacher else "", | |
| f"Школа: {html.escape(school)}" if school else "", | |
| f"Дата: {html.escape(date)}" if date else "", | |
| f"Спостерігач: {html.escape(observer)}" if observer else "", | |
| ] if v] | |
| meta_html = ( | |
| f'<div style="background:#f5f5f5;border-radius:8px;padding:10px 16px;' | |
| f'margin-bottom:14px;font-size:13px;color:#555;">' | |
| + " | ".join(meta_items) + "</div>" | |
| ) if meta_items else "" | |
| # Повнота структури | |
| found_count = len([p for p in parts if p != "структуру не визначено"]) | |
| total_parts = len(PARTS_KEYWORDS) | |
| completeness = round(found_count / total_parts * 100) | |
| comp_color = "#4CAF50" if completeness >= 70 else "#FF9800" if completeness >= 40 else "#f44336" | |
| comp_bar = ( | |
| f'<div style="margin:8px 0;">' | |
| f'<div style="background:#eee;border-radius:6px;height:10px;">' | |
| f'<div style="background:{comp_color};width:{completeness}%;height:10px;border-radius:6px;"></div>' | |
| f'</div>' | |
| f'<div style="font-size:12px;color:#555;margin-top:4px;">' | |
| f'Виявлено {found_count} з {total_parts} частин ({completeness}%)</div></div>' | |
| ) | |
| # Активність | |
| max_act = max(activity.values()) if any(activity.values()) else 1 | |
| act_html = "".join( | |
| activity_bar(k, v, max_act, c) | |
| for (k, v), c in zip(activity.items(), ["#2196F3", "#FF9800", "#4CAF50"]) | |
| ) | |
| # Рекомендації | |
| recs_html = "" | |
| if show_recs: | |
| recs = [] | |
| if "організаційний момент" not in parts: | |
| recs.append("Додайте організаційний момент на початку конспекту.") | |
| if "мотивація" not in parts: | |
| recs.append("Включіть мотиваційний етап для активізації пізнавального інтересу.") | |
| if "підсумок" not in parts: | |
| recs.append("Завершіть урок підсумком або рефлексією.") | |
| if "домашнє завдання" not in parts: | |
| recs.append("Вкажіть домашнє завдання у конспекті.") | |
| if len(methods) < 2: | |
| recs.append("Урізноманітніть методи — використайте 2–3 різні прийоми.") | |
| if forms and forms[0] == "не визначено": | |
| recs.append("Вкажіть форми організації роботи учнів (фронтальна, групова, індивідуальна).") | |
| if tools and tools[0] == "не визначено": | |
| recs.append("Опишіть засоби навчання, які використовуватимуться на уроці.") | |
| if activity["активність учнів"] < activity["робота вчителя"]: | |
| recs.append("Збільшіть частку активної роботи учнів відносно монологу вчителя.") | |
| if not recs: | |
| recs.append("Конспект відповідає основним дидактичним вимогам. Гарна робота!") | |
| li = "".join(f"<li style='margin-bottom:5px'>{html.escape(r)}</li>" for r in recs) | |
| recs_html = card( | |
| "💡", "Методичні рекомендації", | |
| f"<ul style='margin:0;padding-left:18px'>{li}</ul>", | |
| "#FFA000", "#fff8e1" | |
| ) | |
| result = f""" | |
| <div style="font-family:sans-serif;max-width:780px;"> | |
| <h3 style="color:#1a237e;margin-bottom:12px;">📋 Спостереження уроку</h3> | |
| {meta_html} | |
| <div style="background:#e8eaf6;border-radius:10px;padding:16px 20px; | |
| margin-bottom:10px;border-left:4px solid #3949AB; | |
| font-size:14px;color:#212121;line-height:1.8;"> | |
| <div style="font-weight:600;font-size:13px;color:#3949AB;margin-bottom:10px;"> | |
| 📝 Загальна характеристика уроку | |
| </div> | |
| {narrative} | |
| </div> | |
| <div style="background:#e3f2fd;border-radius:10px;padding:14px 18px; | |
| margin-bottom:10px;border-left:4px solid #1976D2;"> | |
| <div style="font-weight:600;font-size:13px;color:#1565C0;margin-bottom:8px;"> | |
| 🏗️ Структурні частини уроку | |
| </div> | |
| {pills(parts, "#bbdefb", "#0d47a1")} | |
| {comp_bar} | |
| </div> | |
| <div style="background:#e8f5e9;border-radius:10px;padding:14px 18px; | |
| margin-bottom:10px;border-left:4px solid #388E3C;"> | |
| <div style="font-weight:600;font-size:13px;color:#2e7d32;margin-bottom:8px;"> | |
| 🎓 Методи та прийоми навчання | |
| </div> | |
| {pills(methods, "#c8e6c9", "#1b5e20")} | |
| </div> | |
| <div style="background:#f3e5f5;border-radius:10px;padding:14px 18px; | |
| margin-bottom:10px;border-left:4px solid #8E24AA;"> | |
| <div style="font-weight:600;font-size:13px;color:#6A1B9A;margin-bottom:8px;"> | |
| 👥 Форми організації роботи | |
| </div> | |
| {pills(forms, "#e1bee7", "#4a148c")} | |
| </div> | |
| <div style="background:#fff3e0;border-radius:10px;padding:14px 18px; | |
| margin-bottom:10px;border-left:4px solid #F57C00;"> | |
| <div style="font-weight:600;font-size:13px;color:#E65100;margin-bottom:8px;"> | |
| 🛠️ Засоби навчання | |
| </div> | |
| {pills(tools, "#ffe0b2", "#bf360c")} | |
| </div> | |
| <div style="background:#e0f7fa;border-radius:10px;padding:14px 18px; | |
| margin-bottom:10px;border-left:4px solid #0097A7;"> | |
| <div style="font-weight:600;font-size:13px;color:#006064;margin-bottom:8px;"> | |
| ✏️ Завдання учнів | |
| </div> | |
| {pills(tasks, "#b2ebf2", "#004d40")} | |
| </div> | |
| <div style="background:#fafafa;border-radius:10px;padding:14px 18px; | |
| margin-bottom:10px;border-left:4px solid #9E9E9E;"> | |
| <div style="font-weight:600;font-size:13px;color:#555;margin-bottom:8px;"> | |
| 📊 Активність у конспекті | |
| </div> | |
| {act_html} | |
| </div> | |
| {recs_html} | |
| <div style="font-size:12px;color:#aaa;margin-top:10px;"> | |
| ⏱ Аналіз виконано за {elapsed} сек. | Символів: {len(lesson_text)} | |
| </div> | |
| </div>""" | |
| return result, "✅ Аналіз завершено" | |
| except Exception as e: | |
| return ( | |
| f'<div style="color:#c62828;background:#ffebee;padding:16px;border-radius:8px;">' | |
| f'❌ Помилка: {html.escape(str(e))}</div>', | |
| "❌ Помилка", | |
| ) | |
| # ── Сесія ───────────────────────────────────────────────────────── | |
| def save_to_history(result_html: str, history: list) -> tuple[list, str]: | |
| history = history or [] | |
| if result_html and "Спостереження уроку" in result_html: | |
| history = history + [{"n": len(history) + 1, "html": result_html}] | |
| return history, f"Збережено: {len(history)} аналіз(ів)" | |
| def render_history(history: list) -> str: | |
| history = history or [] | |
| if not history: | |
| return "<p style='color:#888'>Немає збережених аналізів.</p>" | |
| return "".join( | |
| f'<details style="margin-bottom:10px;border:1px solid #ddd;' | |
| f'border-radius:8px;padding:8px 14px;">' | |
| f'<summary style="cursor:pointer;font-weight:600;color:#1565C0;">' | |
| f'Аналіз #{i["n"]}</summary>' | |
| f'<div style="margin-top:10px;">{i["html"]}</div></details>' | |
| for i in history | |
| ) | |
| # ── Інтерфейс ───────────────────────────────────────────────────── | |
| EXAMPLES = [ | |
| [ | |
| "Тема: Додавання чисел у межах 10\n" | |
| "Мета: навчити учнів додавати числа; розвивати логічне мислення.\n" | |
| "Організаційний момент: привітання, перевірка готовності.\n" | |
| "Актуалізація: пригадаємо вивчені числа від 1 до 9.\n" | |
| "Мотивація: загадка про числа.\n" | |
| "Вивчення нового матеріалу: вчитель пояснює на прикладах із предметами.\n" | |
| "Закріплення: учні виконують вправи в зошиті самостійно.\n" | |
| "Підсумок: що нового дізналися сьогодні?\n" | |
| "Домашнє завдання: с. 45, впр. 3.", | |
| "Іваненко О.М.", "ЗОШ №3, 2-А клас", "03.06.2025", "Петренко В.С.", True, | |
| ], | |
| [ | |
| "Тема: Рослини нашого краю\n" | |
| "Мета: ознайомити з місцевими рослинами; виховувати любов до природи.\n" | |
| "Організаційний момент. Мотивація: демонстрація гербарію.\n" | |
| "Бесіда: які рослини знаєте? Де вони ростуть? Учні відповідають.\n" | |
| "Вивчення нового матеріалу: розповідь з таблицею та схемою.\n" | |
| "Самостійна робота: учні підписують малюнки рослин індивідуально.\n" | |
| "Підсумок уроку та рефлексія. Домашнє завдання: вдома виконайте впр. 5.", | |
| "Коваль Н.П.", "НВК №7, 3-Б клас", "03.06.2025", "Студент-практикант", True, | |
| ], | |
| ] | |
| with gr.Blocks(theme=gr.themes.Soft(), title="LessonObserver") as demo: | |
| session = gr.State([]) | |
| gr.Markdown( | |
| "# 📚 LessonObserver\n" | |
| "**Автоматичний аналіз конспекту уроку для студентів педколеджу**" | |
| ) | |
| with gr.Tabs(): | |
| with gr.Tab("🔍 Аналіз уроку"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| lesson_in = gr.Textbox( | |
| label="📄 Конспект уроку", | |
| placeholder="Вставте сюди текст конспекту уроку...", | |
| lines=14, | |
| ) | |
| with gr.Column(scale=1): | |
| with gr.Accordion("ℹ️ Інформація про урок (необов'язково)", open=False): | |
| teacher_in = gr.Textbox(label="Вчитель", placeholder="Прізвище та ім'я") | |
| school_in = gr.Textbox(label="Школа / клас", placeholder="ЗОШ №5, 2-А клас") | |
| date_in = gr.Textbox(label="Дата", placeholder="03.06.2025") | |
| observer_in = gr.Textbox(label="Спостерігач", placeholder="Ваше ім'я") | |
| recs_check = gr.Checkbox(label="Показати рекомендації", value=True) | |
| btn_analyze = gr.Button("🔎 Аналізувати", variant="primary", size="lg") | |
| status_out = gr.Textbox(label="Статус", interactive=False, max_lines=1) | |
| result_out = gr.HTML() | |
| gr.Examples( | |
| examples=EXAMPLES, | |
| inputs=[lesson_in, teacher_in, school_in, date_in, observer_in, recs_check], | |
| label="📌 Приклади конспектів", | |
| ) | |
| btn_save = gr.Button("💾 Зберегти в сесію", size="sm") | |
| save_status = gr.Textbox(label="", interactive=False, max_lines=1) | |
| with gr.Tab("📂 Збережені аналізи"): | |
| btn_refresh = gr.Button("🔄 Оновити список") | |
| history_out = gr.HTML() | |
| with gr.Tab("❓ Довідка"): | |
| gr.Markdown(""" | |
| ## Як користуватися | |
| 1. Вставте текст конспекту уроку (мінімум 80 символів). | |
| 2. За бажанням заповніть поля вчителя, школи, дати, спостерігача. | |
| 3. Натисніть **«Аналізувати»**. | |
| 4. Отримайте структуроване спостереження. | |
| ## Що аналізує застосунок | |
| | Компонент | Метод | | |
| |-----------|-------| | |
| | Тема та мета уроку | Пошук за шаблоном у тексті | | |
| | Тип уроку | Аналіз ключових слів | | |
| | Навчальний предмет | Тематичні словники | | |
| | Структурні частини | Ключові слова кожного етапу | | |
| | Методи навчання | Дидактичний словник | | |
| | Активність учнів | Підрахунок згадувань | | |
| | Рекомендації | Правила дидактики | | |
| ## Обмеження | |
| - Аналіз базується на ключових словах — що детальніший конспект, то точніший результат | |
| - Не перевіряє фактичну правильність навчального матеріалу | |
| - Оптимальна довжина конспекту: 200–3000 символів | |
| """) | |
| btn_analyze.click( | |
| fn=analyze, | |
| inputs=[lesson_in, teacher_in, school_in, date_in, observer_in, recs_check], | |
| outputs=[result_out, status_out], | |
| ) | |
| btn_save.click( | |
| fn=save_to_history, | |
| inputs=[result_out, session], | |
| outputs=[session, save_status], | |
| ) | |
| btn_refresh.click( | |
| fn=render_history, | |
| inputs=[session], | |
| outputs=[history_out], | |
| ) | |
| demo.launch() |