| |
| """DASS心理模型(Q12).ipynb |
| |
| Automatically generated by Colab. |
| |
| Original file is located at |
| https://colab.research.google.com/drive/19ATyW5Lb692QV2Gk2I0rlsbeSQNwEDnQ |
| |
| 建立環境 |
| """ |
|
|
| import pandas as pd |
| import matplotlib.pyplot as plt |
| import numpy as np |
| import gradio as gr |
| import sklearn |
| import pickle |
| import joblib |
| import time |
| import os |
| import json |
| import gspread |
|
|
|
|
| from sklearn.model_selection import train_test_split, RandomizedSearchCV |
| from sklearn.compose import ColumnTransformer |
| from sklearn.preprocessing import OneHotEncoder, StandardScaler |
| from sklearn.pipeline import Pipeline |
| from sklearn.linear_model import LogisticRegression |
| from sklearn.metrics import classification_report, confusion_matrix, f1_score, accuracy_score, precision_score, balanced_accuracy_score |
| from sklearn.ensemble import RandomForestClassifier |
| from lightgbm import LGBMClassifier |
| from AutoPreprocess import AutoPreprocess |
| from google.oauth2.service_account import Credentials |
| from datetime import datetime, timezone, timedelta |
|
|
| """Gradio 使用者介面""" |
|
|
| |
| import pickle |
| model_path = os.path.abspath("DASS_model.bin") |
|
|
| with open(model_path, "rb") as f: |
| model = pickle.load(f) |
| model |
|
|
| """定義歷史紀錄功能""" |
|
|
| def update_history(current_result_1, current_result_2, history_list): |
| """ |
| current_result_1 & 2: 來自 predict_risk 的兩個回傳值 (HTML 字串) |
| history_list: 來自 gr.State 的現有紀錄列表 |
| """ |
|
|
| |
| now = datetime.now(tw_timezone).strftime("%Y-%m-%d %H:%M:%S") |
|
|
| |
| new_entry = f""" |
| <div style="border-bottom: 2px solid #eee; padding-bottom: 20px; margin-bottom: 20px;"> |
| <div style="font-size: 18px; color: #666; margin-bottom: 10px; font-weight: bold;"> |
| 🕒 測驗時間:{now} |
| </div> |
| |
| <div style=" |
| display: flex; |
| flex-direction: row; |
| justify-content: space-between; |
| align-items: flex-start; |
| gap: 20px; |
| border-bottom: 1px dashed #ccc; |
| padding-bottom: 15px; |
| margin-bottom: 15px; |
| width: 100%;"> |
| |
| <div style="flex: 1;">{current_result_1}</div> |
| <div style="flex: 1;">{current_result_2}</div> |
| |
| </div> |
| </div> |
| """ |
|
|
| |
| history_list.insert(0, new_entry) |
|
|
| |
| |
| combined_html = f""" |
| <div style="zoom: 0.8; -moz-transform: scale(0.8); -moz-transform-origin: 0 0;"> |
| {"".join(history_list)} |
| </div> |
| """ |
|
|
| return combined_html, history_list |
|
|
| """定義儲存測試資料的功能""" |
|
|
| import os |
| import json |
| from datetime import datetime, timezone, timedelta |
| import gspread |
| from google.oauth2.service_account import Credentials |
|
|
| |
| tw_timezone = timezone(timedelta(hours=8)) |
|
|
| def save_to_google_sheets(inputs, a_score, d_score, s_score, t_score, score): |
|
|
| |
| scope = ['https://www.googleapis.com/auth/spreadsheets', |
| 'https://www.googleapis.com/auth/drive'] |
|
|
| |
| google_json = os.environ.get("DASS_JSON") |
| info = json.loads(google_json) |
| creds = Credentials.from_service_account_info(info, scopes=scope) |
| client = gspread.authorize(creds) |
|
|
| |
| sheet = client.open("DASS使用者測試資料").sheet1 |
|
|
|
|
| |
| user_info = inputs[:3] |
| q_answers = inputs[3:] |
| now = datetime.now(tw_timezone).strftime("%Y-%m-%d %H:%M:%S") |
|
|
| |
| row_to_add = [ |
| now, |
| user_info[0], |
| user_info[1], |
| user_info[2], |
| a_score, |
| d_score, |
| s_score, |
| t_score, |
| score |
| ] |
|
|
| row_to_add.extend(q_answers) |
|
|
| |
| sheet.append_row(row_to_add) |
|
|
| """定義重新測驗功能""" |
|
|
| |
| def clear_all(): |
| |
| return [None] * 15 + ["", ""] |
|
|
| """定義主要測試功能""" |
|
|
| def predict_risk(gen, age, family, q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12): |
| inputs = [gen, age, family, q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12] |
|
|
| |
| if any(v is None or v == "" for v in inputs): |
| |
| return "", "", "<div style=\"color: red; font-weight: bold;\">⚠️測驗載入有誤:請確保每一題都已填答或查看填答格式是否正確。</div>" |
|
|
| |
| error_message = "" |
|
|
| |
| progress = gr.Progress() |
| progress(0, desc="模型計算中...") |
|
|
| |
| |
| cols = ["gender", "age", "familysize", "Q2A", "Q4A", "Q19A", "Q20A", "Q28A", "Q21A", "Q26A", "Q37A", "Q42A", "Q11A", "Q12A", "Q27A"] |
| input_df = pd.DataFrame([[gen, age, family, q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12]], columns=cols) |
|
|
| progress(0.5, desc="正在分析數據...") |
| time.sleep(0.5) |
|
|
| |
| score = model.predict(input_df)[0] |
| progress(1.0, desc="計算完成!") |
|
|
|
|
| |
| if score == 0: |
| label = "低度風險" |
| color = "#91cd92" |
| elif score == 1: |
| label = "中度風險" |
| color = "#f59e0b" |
| elif score == 2: |
| label = "高度風險" |
| color = "#ef4444" |
| else: |
| label = "計算結果有誤,請重新測試。" |
|
|
| |
| a_score = (q1 + q2 + q3 + q4 + q5) |
| d_score = (q6 + q7 + q8 + q9) |
| s_score = (q10 + q11 + q12) |
| t_score = a_score + d_score + s_score |
| max_val = 36 |
|
|
| def make_bar(label, score, max_val, color): |
| percent = (score / max_val) * 100 |
| return f""" |
| <div style="margin-bottom: 10px;"> |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> |
| <span style="font-weight: bold;">{label}</span> |
| </div> |
| <div style="background-color: #e0e0e0; border-radius: 10px; height: 12px; width: 100%;"> |
| <div style="background-color: {color}; width: {percent}%; height: 100%; border-radius: 10px;"></div> |
| </div> |
| </div> |
| """ |
|
|
| |
| |
| result_score = f""" |
| <div style="text-align: center; font-family: sans-serif;"> |
| <h2 style="color: #313230;">您的預測結果為</h2> |
| <h1 style="font-size: 60px; color: {color}; margin: 0;"> |
| {label} |
| </h1> |
| <h1 style="font-size: 20px; color: #bbbbc2; margin: 0;"> |
| {t_score}/36 |
| </h1> |
| </div> |
| """ |
|
|
| |
| label_html = f""" |
| <div style="padding: 20px; background: white; border-radius: 10px; border: 1px solid #ddd;"> |
| <h2 style="color: #313230;margin-top: 0; margin-bottom: 15px;">各面向之比重</h2> |
| {make_bar("焦慮 (Anxiety)", a_score, max_val, "#fccb42")} |
| {make_bar("憂鬱 (Depression)", d_score, max_val, "#6dc8fe")} |
| {make_bar("壓力 (Stress)", s_score, max_val, "#fb6d6d")} |
| </div> |
| """ |
|
|
| |
| save_to_google_sheets(inputs, a_score, d_score, s_score, t_score, score) |
|
|
|
|
| progress(1.0, desc="完成") |
|
|
| return result_score, label_html, error_message |
|
|
| |
|
|
| theme = gr.themes.Default( |
| primary_hue="amber", |
| secondary_hue="amber", |
| ).set( |
| body_background_fill="#fffbeb" |
| ) |
|
|
| |
| |
|
|
| |
|
|
| |
| custom_css = """ |
| #my_green_btn { |
| background-color: #91cd92 !important; |
| color: white !important; |
| border: none; |
| } |
| #my_green_btn:hover { |
| background-color: #72a473 !important; /* 滑鼠懸停時變深 */ |
| } |
| |
| #my_white_btn { |
| background-color: #ffffff !important; |
| color: black !important; |
| border: 1px solid #e4e4e7; |
| } |
| |
| #my_white_btn:hover { |
| background-color: #e4e4e7 !important; |
| color: black !important; /* 滑鼠懸停時變深 */ |
| } |
| |
| |
| .my-custom-panel { |
| background-color: #fffef8 !important; |
| border: 2px solid #e4e4e7 !important; |
| padding: 20px; |
| border-radius: 15px; |
| } |
| |
| #history_panel .label-wrap span { |
| font-weight: bold !important; |
| } |
| """ |
|
|
| with gr.Blocks(theme=theme, css=custom_css) as demo: |
|
|
| |
| history_state = gr.State([]) |
|
|
| |
| gr.Markdown("") |
| gr.HTML(f""" |
| <div style="text-align: center; font-family: sans-serif;"> |
| <h2 style="font-size: 32px; color: #313230; margin: 0;">🌿心理健康風險程度測試📝</h2> |
| </div> |
| """) |
| gr.HTML(f""" |
| <div style="text-align: center; font-family: sans-serif;"> |
| <h2 style="font-size: 18px; color: #313230; margin: 0;">歡迎來到心理健康風險程度測試環境!<br> |
| 本測驗將透過12題問答,替您在5分鐘內簡單計算出潛在的心理健康風險程度。<br> |
| 請輕鬆填答,無須思慮過度,測驗愉快!</h2> |
| </div> |
| """) |
|
|
| with gr.Column(variant="panel", elem_classes="my-custom-panel"): |
|
|
| |
| gr.Markdown("## Step 1. 請輸入基本資訊") |
| with gr.Row(): |
| with gr.Column(): |
| name = gr.Textbox(label="暱稱") |
| gen = gr.Dropdown(choices=["男", "女", "其他"], |
| label="性別", |
| value=[]) |
| with gr.Column(): |
| age = gr.Number(label="年齡 (僅限填寫數字)", value ="") |
| family = gr.Number(label="家庭人數 (僅限填寫數字)", value ="") |
|
|
|
|
| |
| gr.Markdown("") |
| gr.Markdown("## Step 2. 請依自身狀態選擇符合的答案") |
| q1 = gr.Radio([("從不", 0), ("偶爾", 1), ("經常", 2), ("總是", 3)], |
| label="Q1.我感覺到口乾舌燥。") |
| q2 = gr.Radio([("從不", 0), ("偶爾", 1), ("經常", 2), ("總是", 3)], |
| label="Q2.我感到呼吸困難(例如:在沒有體力勞動的情況下,呼吸過度急促或喘不過氣)。") |
| q3 = gr.Radio([("從不", 0), ("偶爾", 1), ("經常", 2), ("總是", 3)], |
| label="Q3.在氣溫不高或沒有體力勞動的情況下,我明顯地流汗(例如:手汗)。") |
| q4 = gr.Radio([("從不", 0), ("偶爾", 1), ("經常", 2), ("總是", 3)], |
| label="Q4.我無緣無故地感到害怕。") |
| q5 = gr.Radio([("從不", 0), ("偶爾", 1), ("經常", 2), ("總是", 3)], |
| label="Q5.我覺得自己接近恐慌發作的邊緣。") |
| q6 = gr.Radio([("從不", 0), ("偶爾", 1), ("經常", 2), ("總是", 3)], |
| label="Q6.我覺得生命沒什麼意義/價值。") |
| q7 = gr.Radio([("從不", 0), ("偶爾", 1), ("經常", 2), ("總是", 3)], |
| label="Q7.我感到垂頭喪氣、情緒低落。") |
| q8 = gr.Radio([("從不", 0), ("偶爾", 1), ("經常", 2), ("總是", 3)], |
| label="Q8.我覺得未來毫無希望。") |
| q9 = gr.Radio([("從不", 0), ("偶爾", 1), ("經常", 2), ("總是", 3)], |
| label="Q9.我發現自己很難打起精神主動去做事。") |
| q10 = gr.Radio([("從不", 0), ("偶爾", 1), ("經常", 2), ("總是", 3)], |
| label="Q10.我發現自己很容易變得心煩意亂。") |
| q11 = gr.Radio([("從不", 0), ("偶爾", 1), ("經常", 2), ("總是", 3)], |
| label="Q11.我覺得自己消耗了大量的神經能量(處於高度緊繃狀態)。") |
| q12 = gr.Radio([("從不", 0), ("偶爾", 1), ("經常", 2), ("總是", 3)], |
| label="Q12.我發現自己非常易怒(容易焦躁)。") |
|
|
|
|
| |
| sub_button = gr.Button("確認送出", elem_id="my_green_btn") |
| |
| with gr.Row(): |
| btn_reset = gr.Button("重新測驗", elem_id="my_white_btn") |
|
|
| |
| with gr.Row(): |
| out_html = gr.HTML() |
| out_label = gr.HTML() |
|
|
|
|
| |
| with gr.Accordion("查看歷史紀錄", open=False, elem_id="history_panel"): |
| history_display = gr.HTML(value="目前尚無測驗紀錄") |
|
|
|
|
| |
| |
| sub_button.click(fn=predict_risk, |
| inputs= [gen, age, family, q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12], |
| outputs= [out_html, out_label] |
| ).then( |
| fn=update_history, |
| inputs=[out_html, out_label, history_state], |
| outputs=[history_display, history_state]) |
|
|
| |
| |
| btn_reset.click( |
| fn=lambda: [None]*15 + ["", ""], |
| inputs=None, |
| outputs=[gen, age, family, q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12, out_html , out_label] |
| ) |
|
|
| gr.Markdown("## 免責聲明") |
| gr.Markdown("""本測驗結果僅供參考,非屬正規醫療檢驗範疇。 |
| 若對於自身狀況有任何疑慮,敬請尋求正規專業醫療協助!♡第四組關心您♡""") |
|
|
| demo.launch(share=True) |
|
|
| |