Laos-Natural-Science-Chatbot / data /manual_answers.py
Heng2004's picture
Update data/manual_answers.py
bab6d73 verified
raw
history blame
6.19 kB
# data/manual_answers.py – manual teacher answers
import os
import json
from typing import Dict, List, Optional
from .text_utils import normalize_question
MANUAL_QA_PATH = "data/manual_qa.jsonl"
# Map: normalized question -> answer (used by chatbot)
MANUAL_ANSWERS: Dict[str, str] = {}
# List used for UI (table)
# Each item: {"norm_q": ..., "q": ..., "a": ...}
MANUAL_LIST: List[Dict[str, str]] = []
# Fallback manual Q&A (built-in defaults)
_FALLBACK_MANUAL_QA: List[Dict[str, str]] = [
{
"q": "ປະຫວັດສາດມີຄວາມສໍາຄັນຈັ່ງໃດ໋?",
"a": (
"ປະຫວັດສາດມີຄວາມສໍາຄັນເພາະຊ່ວຍໃຫ້ເຮົາຮູ້ປະຫວັດຄວາມເປັນມາຂອງຊາດ "
"ຂອງບັນພະບຸລຸດ ແລະຂອງສັງຄົມມະນຸດ. ມັນເຮັດໃຫ້ເຮົ້າມີຄວາມພາກພູມໃຈ "
"ຮູ້ບຸນຄຸນປູ່ຍ່າຕາຍາຍ ແລະຮູ້ຫນ້າທີ່ຂອງຕົນເອງໃນການຮັກສາແລະພັດທະນາປະເທດຊາດ."
),
},
{
"q": "ປະຫວັດສາດແມ່ນຫຍັງ?",
"a": (
"ປະຫວັດສາດແມ່ນວິທະຍາສາດທີ່ສຶກສາເຫດການຕ່າງໆໃນອະດີດຂອງສັງຄົມມະນຸດ "
"ຕັ້ງແຕ່ການເກີດຂຶ້ນ ການເຕີບໂຕ ແລະການປ່ຽນແປງຕາມເວລາ."
),
},
{
"q": "ຫຼັກຖານປະຫວັດສາດມີຫຍັງແດ່?",
"a": (
"ຫຼັກຖານປະຫວັດສາດມີ 3 ປະເພດຫຼັກ: ຫຼັກຖານຜ່ານການບອກເລົ່າ, "
"ຫຼັກຖານປະເພດວັດຖຸ ແລະເອກະສານປະເພດການບັນທຶກ."
),
},
]
# ---------- Internal helpers ----------
def _add_pair(q: str, a: str) -> str:
"""
Add/overwrite a (q,a) pair into MANUAL_ANSWERS + MANUAL_LIST.
Returns normalized key.
"""
q_clean = q.strip()
a_clean = a.strip()
if not q_clean or not a_clean:
return ""
norm_q = normalize_question(q_clean)
# Update dict
MANUAL_ANSWERS[norm_q] = a_clean
# Update list (remove old entry with same key, then append new)
global MANUAL_LIST
MANUAL_LIST = [row for row in MANUAL_LIST if row["norm_q"] != norm_q]
MANUAL_LIST.append({"norm_q": norm_q, "q": q_clean, "a": a_clean})
return norm_q
def _load_fallback():
for pair in _FALLBACK_MANUAL_QA:
q = pair.get("q", "")
a = pair.get("a", "")
if q and a:
_add_pair(q, a)
def _load_from_file():
if not os.path.exists(MANUAL_QA_PATH):
return
with open(MANUAL_QA_PATH, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
obj = json.loads(line)
except json.JSONDecodeError:
continue
q = obj.get("q", "")
a = obj.get("a", "")
if q and a:
_add_pair(q, a)
def _load_all():
MANUAL_ANSWERS.clear()
MANUAL_LIST.clear()
_load_fallback()
_load_from_file()
def _save_to_file():
"""
Rewrite manual_qa.jsonl from MANUAL_LIST.
(Includes fallback entries once we start saving – that’s OK.)
"""
os.makedirs(os.path.dirname(MANUAL_QA_PATH), exist_ok=True)
with open(MANUAL_QA_PATH, "w", encoding="utf-8") as f:
for row in MANUAL_LIST:
json.dump({"q": row["q"], "a": row["a"]}, f, ensure_ascii=False)
f.write("\n")
# ---------- Public API (used by chatbot + UI) ----------
def add_manual_qa(question: str, answer: str) -> str:
"""
Add new manual Q&A (or overwrite existing same question).
Updates in-memory index and rewrites file.
Returns normalized key.
"""
if not question.strip() or not answer.strip():
raise ValueError("Question and answer must not be empty.")
norm_q = _add_pair(question, answer)
_save_to_file()
return norm_q
def list_manual_qa() -> List[Dict[str, str]]:
"""
Return a copy of all manual Q&A rows for displaying in UI.
Each row: { 'norm_q', 'q', 'a' }.
"""
return list(MANUAL_LIST)
def get_manual_qa(norm_q: str) -> Optional[Dict[str, str]]:
"""
Look up one Q&A by normalized key (for loading into edit boxes).
"""
for row in MANUAL_LIST:
if row["norm_q"] == norm_q:
return row
return None
def update_manual_qa(old_norm_q: str, new_q: str, new_a: str) -> str:
"""
Update an existing entry identified by old_norm_q.
Allows changing question text (so key may change).
Returns the new normalized key.
"""
if not old_norm_q:
raise ValueError("No entry selected for update.")
if not new_q.strip() or not new_a.strip():
raise ValueError("Question and answer must not be empty.")
# Remove old entry (if exists)
global MANUAL_LIST
MANUAL_LIST = [row for row in MANUAL_LIST if row["norm_q"] != old_norm_q]
if old_norm_q in MANUAL_ANSWERS:
MANUAL_ANSWERS.pop(old_norm_q, None)
# Add as new pair
norm_q = _add_pair(new_q, new_a)
_save_to_file()
return norm_q
def delete_manual_qa(norm_q: str) -> None:
"""
Delete an entry by normalized key.
"""
if not norm_q:
raise ValueError("No entry selected for deletion.")
global MANUAL_LIST
before = len(MANUAL_LIST)
MANUAL_LIST = [row for row in MANUAL_LIST if row["norm_q"] != norm_q]
if len(MANUAL_LIST) == before:
raise ValueError("Entry not found.")
if norm_q in MANUAL_ANSWERS:
MANUAL_ANSWERS.pop(norm_q, None)
_save_to_file()
# Load everything at import time
_load_all()