save_data / app.py
hongkokokok's picture
Create app.py
7b6ebd6 verified
import gradio as gr
from collections import defaultdict
import pandas as pd
import os
from datetime import datetime
# =====================
# DISC Predictor
# =====================
class DISCPredictor:
def __init__(self, mapping=None):
self.mapping = mapping if mapping else {}
self.traits = ['D','I','S','C']
def predict_percentage(self, answers):
scores = defaultdict(float)
for q_idx, ans in enumerate(answers):
key = q_idx + 1
ans = str(ans).upper().strip()
qmap = self.mapping.get(key, {})
choice_map = qmap.get(ans, {})
for trait, w in choice_map.items():
scores[trait] += float(w)
total = sum(scores.values()) if sum(scores.values()) != 0 else 1.0
percentages = {t: round((scores.get(t,0)/total)*100) for t in self.traits}
max_score = max(scores.values()) if scores else 0
winners = [t for t, s in scores.items() if s == max_score]
main_trait = '/'.join(sorted(winners)) if winners else ""
return main_trait, percentages
# =====================
# Mapping
# =====================
mapping = {
1: {'A': {'D':2}, 'B': {'I':2}, 'C': {'S':2}, 'D': {'C':2}},
2: {'A': {'C':2}, 'B': {'I':2}, 'C': {'S':2}, 'D': {'D':2}},
3: {'A': {'S':2}, 'B': {'I':2}, 'C': {'C':2}, 'D': {'D':2}},
4: {'A': {'S':2}, 'B': {'C':2}, 'C': {'D':2}, 'D': {'I':2}},
5: {'A': {'D':2}, 'B': {'I':2}, 'C': {'S':2}, 'D': {'C':2}},
6: {'A': {'I':2}, 'B': {'S':2}, 'C': {'C':2}, 'D': {'D':2}},
7: {'A': {'S':2}, 'B': {'C':2}, 'C': {'I':2}, 'D': {'D':2}},
8: {'A': {'C':2}, 'B': {'S':2}, 'C': {'D':2}, 'D': {'I':2}},
9: {'A': {'D':2}, 'B': {'I':2}, 'C': {'S':2}, 'D': {'C':2}},
10:{'A': {'I':2}, 'B': {'C':2}, 'C': {'D':2}, 'D': {'S':2}},
}
# =====================
# คำถาม DISC
# =====================
questions_text = {
1: {"ฉันรู้สึกว่ามันยากที่จะผ่อนคลาย ใช้ชีวิตสบาย ๆ หรืออยู่นิ่ง ๆ ": "A",
"ฉันมีเพื่อนเยอะ ": "B",
"ฉันพร้อมที่จะช่วยเหลือผู้อื่นเสมอ ": "C",
"ฉันชอบความสมบูรณ์แบบ": "D"},
2: {"ฉันเป็นคนเรียบร้อย มีระเบียบ": "A",
"ฉันเป็นคนกระตือรือร้น ทั้ง work hard และ play hard": "B",
"ฉันเป็นคนใจเย็นมาก": "C",
"ฉันมีทางของตัวเอง": "D"},
3: {"ฉันค่อนข้างพอใจกับวิธีชีวิตปกติของฉัน": "A",
"ฉันมีความคิดสร้างสรรค์และไม่ชอบกฎเกณฑ์": "B",
"ฉันชอบความเงียบและความสงบสุข": "C",
"ฉันมองโลกในแง่ดี": "D"},
4: {"ฉันเป็นผู้ฟังที่ดี": "A",
"ฉันชอบสิ่งที่แน่นอน คาดเดาได้": "B",
"ฉันมั่นใจในตัวเองสูง": "C",
"ฉันชอบเรื่องตลกขบขัน": "D"},
5: {"ฉันชอบการแข่งขัน": "A",
"ฉันไม่ชอบการที่ต้องจริงจังกับชีวิตมากเกินไป": "B",
"ฉันคิดถึงจิตใจผู้อื่นเสมอ": "C",
"ฉันไม่ชอบเสี่ยง ไม่ชอบสิ่งที่ไม่แน่นอน": "D"},
6: {"ฉันโน้มน้าวใจคนอื่นเก่ง": "A",
"ฉันเป็นคนอ่อนโยน ขี้เกรงใจ": "B",
"ฉันทำอะไรด้วยความระมัดระวังเสมอ": "C",
"ฉันเป็นคนกล้าหาญและเด็ดเดี่ยว": "D"},
7: {"ฉันให้อภัยผู้อื่นเสมอ": "A",
"ฉันเป็นคนมีเหตุผลและเข้มแข็ง": "B",
"ฉันเข้าสังคมเก่ง ชอบติดต่อสื่อสาร": "C",
"ฉันมักตัดสินใจเร็ว กล้าหาญ": "D"},
8: {"ฉันปฏิบัติตามกฎระเบียบเสมอ": "A",
"ฉันใจเย็น มีน้ำใจ": "B",
"ฉันเป็นคนมุ่งมั่น ชอบวิเคราะห์": "C",
"ฉันร่าเริง เข้าสังคมเก่ง": "D"},
9: {"ฉันชอบที่จะเป็นที่ 1": "A",
"ฉันเป็นคนสุภาพ อัธยาศัยดี": "B",
"ฉันเคารพความคิดเห็นของผู้อื่น": "C",
"ฉันไม่ชอบเข้าสังคม": "D"},
10: {"ฉันคิดว่าฉันเป็นคนมีเสน่ห์": "A",
"ฉันวิเคราะห์สถานการณ์ได้ดี": "B",
"ฉันเป็นคนใจร้อน ตัดสินใจเร็ว": "C",
"เวลาจะทำอะไร ฉันจะคิดก่อนว่ามันจะกระทบผู้อื่นหรือไม่": "D"},
}
# =====================
# คำอธิบาย DISC
# =====================
disc_descriptions = {
"D": "กล้าตัดสินใจ มุ่งมั่น ชอบการแข่งขัน และเป็นผู้นำ",
"I": "เข้าสังคมเก่ง ร่าเริง ชอบสื่อสาร โน้มน้าวผู้อื่นได้ดี",
"S": "ใจเย็น อดทน สุภาพ มีน้ำใจ ชอบช่วยเหลือผู้อื่น",
"C": "รอบคอบ มีเหตุผล ชอบความถูกต้อง และทำงานเป็นระบบ",
}
# =====================
# ฟังก์ชันตรวจสอบปุ่ม Submit
# =====================
def check_all_selected(affiliation, age_group, *args):
try:
q1 = args[0]
q2 = args[1]
except Exception:
q1 = q2 = None
if affiliation and age_group and q1 and q2:
return gr.update(interactive=True)
else:
return gr.update(interactive=False)
# =====================
# ฟังก์ชันแสดงผล + บันทึก Excel ใน test/
# =====================
def disc_test(affiliation, age_group, *args):
answers_letters = []
for idx, text in enumerate(args):
key = idx + 1
ans = questions_text[key].get(text, None)
if ans is None:
ans = 'A'
answers_letters.append(ans)
predictor = DISCPredictor(mapping)
main_trait, percentages = predictor.predict_percentage(answers_letters)
# บันทึกไฟล์ Excel ลง test/
folder_path = "https://huggingface.co/spaces/hongkokokok/save_data"
os.makedirs(folder_path, exist_ok=True)
file_path = os.path.join(folder_path, "disc_results.xlsx")
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
row = {
"timestamp": ts,
"affiliation": affiliation,
"age_group": age_group,
"Main Trait": main_trait,
"D %": percentages.get("D", 0),
"I %": percentages.get("I", 0),
"S %": percentages.get("S", 0),
"C %": percentages.get("C", 0),
}
for i, a in enumerate(answers_letters, start=1):
row[f"Q{i}"] = a
try:
if os.path.exists(file_path):
existing = pd.read_excel(file_path)
df = pd.concat([existing, pd.DataFrame([row])], ignore_index=True)
else:
df = pd.DataFrame([row])
df.to_excel(file_path, index=False)
saved_msg = f"บันทึกผลเรียบร้อย: {file_path}"
except Exception as e:
saved_msg = f"ไม่สามารถบันทึกไฟล์ Excel ได้: {e}"
# สร้างผลลัพธ์ HTML
result_text = f"""
<h2 style="text-align:center; font-size:2rem; font-weight:700; color:#ff5722; margin-bottom:10px;">
🧠 ผลลัพธ์ DISC ของคุณ: <span style="color:#4caf50">{main_trait}</span>
</h2>
<div style="text-align:center; margin-bottom:12px; font-size:0.95rem;">
<b>สังกัด:</b> {affiliation} &nbsp; | &nbsp; <b>ช่วงอายุ:</b> {age_group} <br>
<small>บันทึกเมื่อ: {ts}</small>
</div>
"""
sorted_traits = sorted(percentages.items(), key=lambda x: x[1], reverse=True)
bg_color_map = {"D": "#ffebee", "I": "#fffde7", "S": "#e8f5e9", "C": "#e3f2fd"}
text_color_map = {"D": "red", "I": "#fbc02d", "S": "green", "C": "blue"}
icon_map = {"D":"🏆", "I":"🎉", "S":"🤝", "C":"📊"}
result_text += "<div style='display:flex; flex-direction:column; gap:12px;'>"
for t, pct in sorted_traits:
desc = disc_descriptions.get(t,"")
result_text += f"""
<div style="background-color:{bg_color_map.get(t,'#f5f5f5')};
padding:12px; border-radius:12px; box-shadow:1px 1px 6px rgba(0,0,0,0.06);
font-size:1.05rem;">
<b style="font-size:1.15rem; color:{text_color_map.get(t,'black')};">
{icon_map.get(t,'')} {t}: {pct}%
</b><br>
<span>{desc}</span>
</div>
"""
result_text += "</div>"
result_text += f"<div style='margin-top:12px; font-size:0.95rem; color:#555;'>{saved_msg}</div>"
return result_text
# =====================
# Gradio UI
# =====================
with gr.Blocks() as demo:
gr.HTML("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Kanit:wght@400;600&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Baloo+2:wght@700&display=swap');
body { font-family: 'Kanit', sans-serif; }
.big-radio .wrap label { font-size: 1.02rem !important; padding: 10px 14px !important; margin:6px 0 !important; display:block; border:1px solid #ddd; border-radius:10px; transition:0.18s; }
.big-radio .wrap input[type=radio] { transform: scale(1.15); margin-right:8px; }
.radio-question { margin-bottom:18px; padding:14px; border:2px solid #eee; border-radius:12px; background-color:white; box-shadow:0 2px 6px rgba(0,0,0,0.04); transition:0.2s; }
</style>
""")
gr.HTML("<h1 style='text-align:center; font-family:Baloo 2; background:linear-gradient(to right, #ff7e5f, #feb47b); -webkit-background-clip:text; -webkit-text-fill-color:transparent;'>แบบทดสอบ DISC</h1>")
with gr.Row():
with gr.Column(scale=2):
affiliation = gr.Dropdown(
choices=["งานการพยาบาลผู้ป่วยกุมารเวชศาสตร์","งานการพยาบาลผู้ป่วยทั่วไป จิตเวชและจักษุ โสต ศอ นาสิก","อื่นๆ"],
label="สังกัด (โปรดเลือก)",
)
with gr.Column(scale=1):
age_group = gr.Radio(
choices=["น้อยกว่า 20 ปี","21-28 ปี","29-36 ปี","37–44 ปี","45–52 ปี","53-60 ปี"],
label="ช่วงอายุ (โปรดเลือก)",
)
inputs = []
for i in range(1, 11):
with gr.Group(elem_classes=["radio-question"]):
gr.HTML(f'<div class="question-number">ข้อ {i}</div>')
radio = gr.Radio(
list(questions_text[i].keys()),
label="",
elem_classes=["big-radio"]
)
inputs.append(radio)
output = gr.HTML(label="ผลลัพธ์ DISC")
btn = gr.Button("Submit", elem_classes=["submit-btn"], interactive=False)
btn.click(disc_test, inputs=[affiliation, age_group] + inputs, outputs=[output])
for r in inputs:
r.change(check_all_selected, inputs=[affiliation, age_group] + inputs, outputs=[btn])
affiliation.change(check_all_selected, inputs=[affiliation, age_group] + inputs, outputs=[btn])
age_group.change(check_all_selected, inputs=[affiliation, age_group] + inputs, outputs=[btn])
if __name__ == "__main__":
demo.launch()