lessonObserver / app.py
OlenaBondaruk's picture
Update app.py
aa97c60 verified
Raw History Blame Contribute Delete
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;">'
+ " &nbsp;|&nbsp; ".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()