✅ 今回のゴール
- ユーザー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.Label
やtk.Entry
の使い方もすぐ教えてくれる - エラー解決も「的確に」「説明付き」で返してくれる
🔚 まとめ:初心者でもここまでできた!
TkinterとSQLiteを組み合わせれば、自分専用のスコア記録ツールを作ることができます。ChatGPTの助けがあれば、条件分岐やエラーハンドリング、GUI操作も一歩ずつ理解して進められました。
💡今後は「CSV出力」「グラフ表示」などにも挑戦したいです。
※私自身、プログラミングは勉強中の身です。この記事の内容は一例であり、動作や効果を保証するものではありません。ご了承ください。
