ChatGPTとPythonで始めたスコア記録アプリ!ID検索・スコア計算もできた!

ChatGPTとPythonで始めたスコア記録アプリ!ID検索・スコア計算もできた!
目次

✅ 今回のゴール

  • ユーザーIDを検索して登録情報を自動取得
  • 年齢に応じたスコアを自動計算
  • 検査データとして記録に残せるようにする
  • 子ども(〜17歳)にも対応したスコア式を使う
  • 既存データの「編集モード」も搭載

🛠 使用した技術

  • Python
  • Tkinter(GUI)
  • tkcalendar(カレンダー入力)
  • SQLite(ローカルDB)

💬 ChatGPTとのやりとり例

「子どもと大人でスコア式が違うのですが、1つの関数にまとめられますか?」
ChatGPT「条件分岐で年齢範囲を判定し、それぞれの式を実装すれば可能です。floatへの型変換も忘れずに。」

「データがある場合は自動表示、なければ入力できるようにしたいです」
ChatGPT「SQLiteでID検索し、データがあれば各エントリーフィールドに値を自動入力しましょう」


📌 コードの重要ポイント(初心者向け解説)

① 年齢計算関数(calculate_age

def calculate_age(birthdate, exam_date):
    return exam_date.year - birthdate.year - ((exam_date.month, exam_date.day) < (birthdate.month, birthdate.day))

👉 生年月日と記録日から自動で年齢を算出。月日が未到達なら-1する仕様がポイント。


② 柔軟な日付変換(parse_birth_date

def parse_birth_date(date_str):
    from re import findall
    nums = findall(r'\d+', date_str)
    if len(nums) == 3:
        y, m, d = map(int, nums)
        return datetime(y, m, d)

👉 ハイフンやスラッシュ混在OKな日付パーサー。ユーザー入力が多少乱れていても対応可能。


③ スコア計算関数(calculate_score

if age <= 1:
    エラー表示
elif 2 <= age <= 10:
    score = 0.3 * 身長 / Cr
elif 11〜17歳:
    5次関数モデル(男女別)
elif 18歳以上:
    eGFR式(男性・女性補正あり)

👉 年齢によって計算式を分ける構造に。特に11〜17歳の「5次関数」モデルが特徴的。実際の臨床モデルにもとづいた形式で設計。


④ 編集モードボタン

def enable_edit():
    entry_name.config(state="normal")
    # 他の入力も編集可能に

👉 誤登録・修正に対応できるよう「編集モード」ボタンを実装。


⑤ ID確認 → 情報表示

cur.execute("SELECT name, kana, birth_date, sex FROM user_info WHERE user_id=?", (user_id,))

👉 入力されたIDでデータを検索。あれば入力欄に自動反映。なければ新規登録用に全て開放。


⑥ スコア表示とDB登録

label_score_result.config(text=f"{score}(計算済み)")

👉 計算されたスコアを画面に表示しつつ、SQLiteのexam_dataテーブルにも保存。


実際のコード
import tkinter as tk
from tkinter import messagebox
from tkcalendar import DateEntry
import sqlite3
from datetime import datetime

# 年齢計算関数
def calculate_age(birthdate, exam_date):
    return exam_date.year - birthdate.year - ((exam_date.month, exam_date.day) < (birthdate.month, birthdate.day))

# 生年月日フォーマットの自動判別関数
def parse_birth_date(date_str):
    from re import findall
    try:
        nums = findall(r'\d+', date_str)
        if len(nums) == 3:
            y, m, d = map(int, nums)
            return datetime(y, m, d)
    except:
        pass
    return None

# スコア計算関数
def calculate_score(creatinine, age, sex, height_cm):
    try:
        height_m = float(height_cm) / 100  # cm→m
        cre = float(creatinine)
        age = int(age)

        if age <= 1:
            messagebox.showerror("年齢エラー", "1歳以下のスコア計算には対応していません。")
            return 0

        elif 2 <= age <= 10:
            score = (0.3 * height_m / cre) * 100

        elif 11 <= age <= 17:
            h = height_m
            if sex == "男性":
                score = ((-1.259 * h**5 + 7.815 * h**4 - 18.57 * h**3 + 21.39 * h**2 - 11.71 * h + 2.628) / cre) * 100
            else:
                score = ((-4.536 * h**5 + 27.16 * h**4 - 63.47 * h**3 + 72.43 * h**2 - 40.06 * h + 8.778) / cre) * 100

        elif age >= 18:
            base = 194 * (cre ** -1.094) * (age ** -0.287)
            score = base if sex == "男性" else base * 0.739

        else:
            score = 0

        return round(score, 2)
    except Exception as e:
        messagebox.showerror("計算エラー", f"スコア計算中にエラーが発生しました: {e}")
        return 0

# 編集有効化
def enable_edit():
    entry_name.config(state="normal")
    entry_kana.config(state="normal")
    entry_birth.config(state="normal")
    entry_birth.bind("<FocusOut>", update_age_from_birth)
    radio_male.config(state="normal")
    radio_female.config(state="normal")
    messagebox.showinfo("編集モード", "ユーザー情報の編集が可能になりました")

# 生年月日から年齢自動入力
def update_age_from_birth(event=None):
    try:
        birth_str = entry_birth.get().replace("-", "/")
        birth_date = parse_birth_date(birth_str)
        if birth_date:
            today = datetime.today()
            age = calculate_age(birth_date, today)
            entry_age.config(state="normal")
            entry_age.delete(0, tk.END)
            entry_age.insert(0, age)
    except:
        pass

# ID確認
def check_user():
    user_id = entry_id.get().strip()
    if not user_id:
        messagebox.showerror("エラー", "ユーザーIDを入力してください。")
        return

    conn = sqlite3.connect("user_data.db")
    cur = conn.cursor()
    cur.execute("SELECT name, kana, birth_date, sex FROM user_info WHERE user_id=?", (user_id,))
    result = cur.fetchone()
    conn.close()

    entry_id.config(state="readonly")

    if result:
        entry_name.config(state="normal")
        entry_kana.config(state="normal")
        entry_birth.config(state="normal")
        entry_age.config(state="normal")

        entry_name.delete(0, tk.END)
        entry_kana.delete(0, tk.END)
        entry_age.delete(0, tk.END)
        sex_var.set("")

        entry_name.insert(0, result[0])
        entry_kana.insert(0, result[1])
        entry_birth.delete(0, tk.END)
        entry_birth.insert(0, result[2])
        entry_birth.config(state="readonly")
        sex_var.set(result[3])

        try:
            birth_dt = parse_birth_date(result[2])
            if birth_dt:
                age = calculate_age(birth_dt, datetime.today())
                entry_age.insert(0, age)
                entry_age.config(state="readonly")
        except:
            pass

        entry_name.config(state="readonly")
        entry_kana.config(state="readonly")
        entry_birth.config(state="readonly")
        radio_male.config(state="disabled")
        radio_female.config(state="disabled")
    else:
        entry_name.config(state="normal")
        entry_kana.config(state="normal")
        entry_birth.config(state="normal")
        entry_age.config(state="normal")
        radio_male.config(state="normal")
        radio_female.config(state="normal")

        entry_birth.delete(0, tk.END)
        entry_birth.insert(0, "1900/01/01")  # ← 初期値に戻す


# 登録処理
def register_all():
    user_id = entry_id.get().strip()
    name = entry_name.get().strip()
    kana = entry_kana.get().strip()
    birth_date_input = entry_birth.get().strip().replace("-", "/")
    sex = sex_var.get().strip()
    height = entry_height.get().strip()
    weight = entry_weight.get().strip()

    if birth_date_input in ["", "1900/01/01"]:
        messagebox.showerror("入力エラー", "生年月日が初期値のままです。正しい日付を入力してください。")
        return

    birth_date_obj = parse_birth_date(birth_date_input)
    if not birth_date_obj:
        messagebox.showerror("入力エラー", "生年月日は yyyy/mm/dd 形式で入力してください。")
        return

    birth_date = birth_date_obj.strftime("%Y/%m/%d")
    exam_date_obj = cal_exam_date.get_date()
    exam_date = exam_date_obj.strftime("%Y/%m/%d")

    try:
        yyy = float(entry_creatinine.get())
    except ValueError:
        messagebox.showerror("入力エラー", "数値Y(yyy)は数値で入力してください。")
        return

    with sqlite3.connect("user_data.db") as conn:
        cur = conn.cursor()
        cur.execute("SELECT birth_date, sex FROM user_info WHERE user_id=?", (user_id,))
        user = cur.fetchone()

        if not user:
            cur.execute("""
                INSERT INTO user_info (user_id, name, kana, birth_date, sex)
                VALUES (?, ?, ?, ?, ?)
            """, (user_id, name, kana, birth_date, sex))
            birth_date_str = birth_date
        else:
            cur.execute("""
                UPDATE user_info
                SET name = ?, kana = ?, birth_date = ?, sex = ?
                WHERE user_id = ?
            """, (name, kana, birth_date, sex, user_id))
            birth_date_str = birth_date

        birth_dt = parse_birth_date(birth_date_str)
        if birth_dt:
            age = calculate_age(birth_dt, exam_date_obj)
        else:
            age = ""

        try:
            score = calculate_score(yyy, int(age) if age else 0, sex, height)
        except:
            score = 0

        label_score_result.config(text=f"{score}(計算済み)")

        cur.execute("""
            INSERT INTO exam_data (
                user_id, exam_date, age_at_exam,
                inout, area, method,
                yyy, height, weight, yyy_result
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            user_id, exam_date, age,
            "", "", "", yyy, height, weight, score
        ))

    messagebox.showinfo("完了", "登録が完了しました。")


# 次の検査用リセット処理
def reset_for_next_exam():
    entry_id.config(state="normal")
    entry_id.delete(0, tk.END)
    entry_name.config(state="normal")
    entry_kana.config(state="normal")
    entry_birth.config(state="normal")
    radio_male.config(state="normal")
    radio_female.config(state="normal")

    entry_name.delete(0, tk.END)
    entry_kana.delete(0, tk.END)
    entry_birth.delete(0, tk.END)
    entry_birth.insert(0, "1900/01/01")
    sex_var.set("")

    cal_exam_date.set_date(datetime.today())
    entry_height.delete(0, tk.END)
    entry_weight.delete(0, tk.END)
    entry_age.config(state="normal")
    entry_age.delete(0, tk.END)
    entry_creatinine.delete(0, tk.END)
    label_score_result.config(text="(計算後に表示)")

# GUIレイアウト
root = tk.Tk()
root.title("スコア記録アプリ")

tk.Label(root, text="ユーザーID(user_id)").grid(row=0, column=0, sticky="e")
entry_id = tk.Entry(root)
entry_id.grid(row=0, column=1)
tk.Button(root, text="ID確認", command=check_user).grid(row=0, column=2, padx=5)
tk.Button(root, text="編集", command=enable_edit).grid(row=0, column=3, padx=5)

tk.Label(root, text="名前").grid(row=1, column=0, sticky="e")
entry_name = tk.Entry(root)
entry_name.grid(row=1, column=1)

tk.Label(root, text="フリガナ").grid(row=2, column=0, sticky="e")
entry_kana = tk.Entry(root)
entry_kana.grid(row=2, column=1)

tk.Label(root, text="生年月日").grid(row=3, column=0, sticky="e")
entry_birth = tk.Entry(root, state="readonly")
entry_birth.grid(row=3, column=1)
entry_birth.bind("<FocusOut>", update_age_from_birth)

tk.Label(root, text="性別").grid(row=4, column=0, sticky="e")
sex_var = tk.StringVar()
radio_male = tk.Radiobutton(root, text="男性", variable=sex_var, value="男性")
radio_female = tk.Radiobutton(root, text="女性", variable=sex_var, value="女性")
radio_male.grid(row=4, column=1, sticky="w", padx=(0, 40))
radio_female.grid(row=4, column=1, sticky="e")

tk.Label(root, text="身長(cm)").grid(row=5, column=0, sticky="e")
entry_height = tk.Entry(root)
entry_height.grid(row=5, column=1)

tk.Label(root, text="体重(kg)").grid(row=6, column=0, sticky="e")
entry_weight = tk.Entry(root)
entry_weight.grid(row=6, column=1)

tk.Label(root, text="年齢").grid(row=7, column=0, sticky="e")
entry_age = tk.Entry(root)
entry_age.grid(row=7, column=1)

tk.Label(root, text="実施記録日").grid(row=8, column=0, sticky="e")
cal_exam_date = DateEntry(root, date_pattern="yyyy/mm/dd", width=17)
cal_exam_date.grid(row=8, column=1)

tk.Label(root, text="数値Y(yyy)").grid(row=9, column=0, sticky="e")
entry_creatinine = tk.Entry(root)
entry_creatinine.grid(row=9, column=1)

tk.Label(root, text="スコア(yyy_result)").grid(row=10, column=0, sticky="e")
label_score_result = tk.Label(root, text="(計算後に表示)")
label_score_result.grid(row=10, column=1)

tk.Button(root, text="登録する", command=register_all).grid(row=11, columnspan=3, pady=10)
tk.Button(root, text="次の検査", command=reset_for_next_exam).grid(row=12, columnspan=3, pady=(0, 10))

root.mainloop()

🧪 テストと活用場面

  • 自分自身の記録(年齢・身長・数値Yからスコア確認)
  • 子どもと大人を区別した評価
  • 何度も検査するような仮想ケースにも対応(複数登録OK)
  • 完全オフラインだから安全性も高い
スコア記録画面

🧠 ChatGPTを使ってよかったこと

  • 数式の変換や条件分岐の整理がとにかく早い
  • GUI設計に悩んだ時、tk.Labeltk.Entryの使い方もすぐ教えてくれる
  • エラー解決も「的確に」「説明付き」で返してくれる

🔚 まとめ:初心者でもここまでできた!

TkinterとSQLiteを組み合わせれば、自分専用のスコア記録ツールを作ることができます。ChatGPTの助けがあれば、条件分岐やエラーハンドリング、GUI操作も一歩ずつ理解して進められました。


💡今後は「CSV出力」「グラフ表示」などにも挑戦したいです。

※私自身、プログラミングは勉強中の身です。この記事の内容は一例であり、動作や効果を保証するものではありません。ご了承ください。

あわせて読みたい
ChatGPTと挑戦!Pythonでオフライン対応のスコア計算アプリを作る準備 「自分でもアプリって作れるのかな?」そんな思いから、Pythonでオフライン対応のアプリ開発にチャレンジしています。プログラミングは少しかじった程度の筆者が、ChatG...

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

このブログ「Re:AI Life」では、
ChatGPTを中心に、AIをちょっとだけ生活に取り入れて、毎日がちょっとラクになる方法をお届けしています。
「AIなんてわからないよ〜」という方こそ大歓迎!
同世代の仲間として、安心して読める・試せる・相談できる場所を目指しています。
どうぞよろしくお願いします😊

目次