Dash-math_Database / pages /firebase_progress.py
Mochi0622's picture
update
fc76b7f
Raw
History Blame Contribute Delete
6.64 kB
"""
firebase_progress.py
--------------------
學生學習進度的讀寫邏輯。
放在專案根目錄,由 dt_callbacks.py 和頁面呼叫。
Firestore 資料結構:
students/{student_id}/
profile: { name, student_id, last_seen }
progress: { quiz_correct, solutions_viewed, ai_hints_used, ... }
events: [ { type, unit, qid, ts, ... }, ... ] ← 子集合
"""
from __future__ import annotations
import datetime
from firebase_init import get_db
# ─── 時間戳 ────────────────────────────────────────────────────────────────
def _now() -> str:
return datetime.datetime.now(datetime.timezone.utc).isoformat()
# ─── 學生個人資料 ──────────────────────────────────────────────────────────
def upsert_student(student_id: str, name: str = "") -> None:
"""建立或更新學生個人資料。"""
db = get_db()
ref = db.collection("students").document(student_id)
ref.set({
"student_id": student_id,
"name": name or student_id,
"last_seen": _now(),
}, merge=True)
# ─── 記錄事件 ─────────────────────────────────────────────────────────────
def log_event(student_id: str, event_type: str, **kwargs) -> None:
"""
記錄一筆學習事件到 students/{student_id}/events 子集合。
event_type 範例:
"quiz_correct" - 填空/選擇題答對
"quiz_wrong" - 答錯
"solution_viewed" - 展開詳細解法
"hint_used" - 按下 AI 提示
"quiz_ai_done" - AI 批改完成
"""
db = get_db()
payload = {
"type": event_type,
"ts": _now(),
**kwargs, # unit, qid, answer, ...
}
db.collection("students").document(student_id)\
.collection("events").add(payload)
def log_quiz_result(
student_id: str,
unit: str,
qid: str,
correct: bool,
answer: str = "",
) -> None:
"""記錄一題答題結果,同時更新 progress 計數。"""
event_type = "quiz_correct" if correct else "quiz_wrong"
log_event(student_id, event_type, unit=unit, qid=qid, answer=answer)
# 更新統計計數
db = get_db()
ref = db.collection("students").document(student_id)
field = "quiz_correct_count" if correct else "quiz_wrong_count"
ref.set({field: _increment(1), "last_seen": _now()}, merge=True)
def log_solution_viewed(student_id: str, unit: str, solution_id: str) -> None:
"""記錄學生展開了某個解法區塊。"""
log_event(student_id, "solution_viewed", unit=unit, solution_id=solution_id)
db = get_db()
db.collection("students").document(student_id)\
.set({"solutions_viewed_count": _increment(1), "last_seen": _now()}, merge=True)
def log_choice_option(unit: str, qid: str, option_key: str) -> None:
"""記錄某題某選項被選擇的次數(全班統計,存在 question_stats 集合)。"""
db = get_db()
ref = (
db.collection("question_stats")
.document(f"{unit}__{qid}")
)
ref.set({
"unit": unit,
"qid": qid,
f"option_{option_key}": _increment(1),
}, merge=True)
def log_quiz_question_stat(unit: str, qid: str, correct: bool) -> None:
"""記錄某題全班答對/答錯次數(存在 question_stats 集合)。"""
db = get_db()
ref = db.collection("question_stats").document(f"{unit}__{qid}")
field = "correct_count" if correct else "wrong_count"
ref.set({"unit": unit, "qid": qid, field: _increment(1)}, merge=True)
def get_all_question_stats() -> list[dict]:
"""讀取所有題目的答題統計(老師後台用)。"""
db = get_db()
docs = db.collection("question_stats").stream()
return [d.to_dict() for d in docs]
def register_question(unit: str, qid: str, prompt_text: str, qtype: str, options: list | None = None) -> None:
"""
在第一次有學生作答時,記錄題目內容到 question_stats(若尚未存在)。
prompt_text: 題目文字(純文字,不含 LaTeX 標記也可)
options: [{"key": "a", "text": "..."}, ...] 或 None
"""
db = get_db()
ref = db.collection("question_stats").document(f"{unit}__{qid}")
doc = ref.get()
if doc.exists and doc.to_dict().get("prompt_text"):
return # 已有紀錄,不重複寫
update = {"unit": unit, "qid": qid, "qtype": qtype}
if prompt_text:
update["prompt_text"] = prompt_text[:300] # 截斷避免過長
if options:
update["options"] = [{"key": o.get("key", ""), "text": o.get("text", "")} for o in options]
ref.set(update, merge=True)
def log_hint_used(student_id: str, unit: str, qid: str) -> None:
"""記錄學生使用了 AI 提示。"""
log_event(student_id, "hint_used", unit=unit, qid=qid)
db = get_db()
db.collection("students").document(student_id)\
.set({"ai_hints_used_count": _increment(1), "last_seen": _now()}, merge=True)
# ─── 讀取 ────────────────────────────────────────────────────────────────
def get_student_progress(student_id: str) -> dict:
"""讀取單一學生的 progress 計數(老師後台用)。"""
db = get_db()
doc = db.collection("students").document(student_id).get()
if doc.exists:
return doc.to_dict()
return {}
def get_all_students() -> list[dict]:
"""讀取所有學生的 profile(老師後台列表用)。"""
db = get_db()
docs = db.collection("students").stream()
return [d.to_dict() for d in docs]
def get_student_events(student_id: str, limit: int = 100) -> list[dict]:
"""讀取某學生最近的事件記錄。"""
db = get_db()
docs = (
db.collection("students").document(student_id)
.collection("events")
.order_by("ts", direction="DESCENDING")
.limit(limit)
.stream()
)
return [d.to_dict() for d in docs]
# ─── Firestore 遞增輔助 ───────────────────────────────────────────────────
def _increment(n: int):
from google.cloud.firestore import Increment
return Increment(n)