6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

医療費控除の入力が面倒すぎたので、Pythonだけで「ACCESS風」ローカル入力ツールを作った

6
Posted at

先日は珍プレー大賞をいただきありがとうございました。
ずっと中古ノートパソコンで作業しているので、賞金でモニターをポチしました。
仕事に、あと今度の個展でのMVや開発アプリの表示で使う予定です。

さて、本題。
医療費控除の明細入力、毎年地味に面倒ではないでしょうか。
自分は「医療費控除の入力が面倒」「PCにAccessが入っていない」「でも入力UIは欲しい」という状況だったので、Python + Antigravityでローカル専用の入力ツールを作りました。

  • 個人利用(デプロイなし)
  • ローカルホストで動作
  • 同一Wi-Fiならスマホからも入力可能(任意)

できること(ざっくり仕様)

  • 医療費データをフォームで入力し、CSVに保存
  • 入力済みデータの確認・削除
  • 国税庁の「医療費控除の明細書」っぽく、人×病院(支払先)で名寄せしてExcel出力

(補足)国税庁の様式では「領収書1枚ごと」ではなく、一定条件で「医療を受けた方」「病院等」ごとにまとめて記載できます、という前提をベースにしています。


完成

Streamlit-01-10-2026_12_47_AM.png

国税庁の明細書(参考)

ref1-pdf-01-09-2026_04_29_PM.png


使ったもの

  • Python
  • Antigravity
  • pandas(CSV操作・集計)
  • openpyxl(Excel出力)

セットアップ手順

1) 必要パッケージをインストール

コマンドプロンプトで以下を実行します。

pip install streamlit pandas openpyxl

2) app.py を作成して保存

以下を app.py として保存します(そのまま貼り付けOK)。

import streamlit as st
import pandas as pd
import datetime
import os
from io import BytesIO

# --- 設定 ---
DATA_FILE = "medical_expenses.csv"
FAMILY_MEMBERS = ["世帯主", "配偶者", "子(長男)", "子(長女)"] # 必要に応じて変更してください

# --- データ読み込み・保存関数 ---
def load_data():
    if os.path.exists(DATA_FILE):
        return pd.read_csv(DATA_FILE)
    else:
        # 初回起動時は空のデータフレームを作成
        return pd.DataFrame(columns=[
            "日付", "氏名", "支払先", "区分_診療", "区分_医薬品",
            "区分_介護", "区分_その他", "支払金額", "補填金額"
        ])

def save_data(df):
    df.to_csv(DATA_FILE, index=False)

# --- メイン画面 ---
st.title("🏥 医療費控除 かんたん入力アプリ")

# データ読み込み
df = load_data()

# サイドバー:メニューと集計
st.sidebar.header("メニュー")
page = st.sidebar.radio("モード切替", ["データ入力", "データ確認・修正", "Excel出力"])

# 現在の合計額表示
total_paid = df["支払金額"].sum() if not df.empty else 0
st.sidebar.markdown("---")
st.sidebar.metric("今年の医療費合計", f"¥{total_paid:,}")

# --- 1. データ入力画面 ---
if page == "データ入力":
    st.header("領収書の入力")

    with st.form("input_form", clear_on_submit=True):
        col1, col2 = st.columns(2)

        # 日付 (デフォルトは今日)
        with col1:
            date_input = st.date_input("日付", datetime.date.today())

        # 氏名選択
        with col2:
            name_input = st.selectbox("医療を受けた人", FAMILY_MEMBERS)

        # 支払先 (学習機能付き)
        # 過去のデータからユニークな病院名リストを取得
        existing_hospitals = df["支払先"].unique().tolist() if not df.empty else []
        existing_hospitals.sort()
        # 先頭に新規追加オプションを入れる
        hospital_options = ["【新規入力】"] + existing_hospitals

        selected_hospital = st.selectbox("病院・薬局などの名称", hospital_options)

        # 新規入力が選ばれた場合、テキストボックスを表示
        if selected_hospital == "【新規入力】":
            hospital_input = st.text_input("新しい支払先を入力してください")
        else:
            hospital_input = selected_hospital
            # UI上は隠すが値を持たせるためのダミー
            st.text_input("新しい支払先", value=selected_hospital, disabled=True, label_visibility="collapsed")

        st.markdown("---")

        # 医療費の区分 (チェックボックス)
        st.write("医療費の区分 (該当するものにチェック)")
        c1, c2, c3, c4 = st.columns(4)
        kubun_1 = c1.checkbox("診療・治療")
        kubun_2 = c2.checkbox("医薬品購入")
        kubun_3 = c3.checkbox("介護保険")
        kubun_4 = c4.checkbox("その他")

        st.markdown("---")

        # 金額
        col3, col4 = st.columns(2)
        with col3:
            cost_input = st.number_input("支払った医療費 (円)", min_value=0, step=100)
        with col4:
            hoten_input = st.number_input("補填される金額 (円)", min_value=0, step=100, help="保険金などで戻ってきた金額")

        # 登録ボタン
        submitted = st.form_submit_button("登録する", use_container_width=True)

        if submitted:
            if not hospital_input:
                st.error("支払先を入力してください")
            elif cost_input == 0:
                st.warning("金額が0円です")
            else:
                # 新しいデータを追加
                new_data = {
                    "日付": date_input,
                    "氏名": name_input,
                    "支払先": hospital_input,
                    "区分_診療": kubun_1,
                    "区分_医薬品": kubun_2,
                    "区分_介護": kubun_3,
                    "区分_その他": kubun_4,
                    "支払金額": cost_input,
                    "補填金額": hoten_input
                }
                # pandas 2.0以降のconcat推奨
                df = pd.concat([df, pd.DataFrame([new_data])], ignore_index=True)
                save_data(df)
                st.success(f"{hospital_input} のデータを保存しました!")
                # ページをリロードしてリストを更新するために少し待機(Streamlitの仕様)
                st.rerun()

# --- 2. データ確認・修正画面 ---
elif page == "データ確認・修正":
    st.header("入力データの確認・削除")

    if df.empty:
        st.info("データがありません。「データ入力」から登録してください。")
    else:
        # データ表示
        st.dataframe(df, use_container_width=True)

        st.markdown("### データの削除")
        del_index = st.number_input("削除する行番号(index)を入力", min_value=0, max_value=len(df)-1, step=1)
        if st.button("指定した行を削除"):
            df = df.drop(df.index[del_index])
            df = df.reset_index(drop=True)
            save_data(df)
            st.success("削除しました")
            st.rerun()

# --- 3. Excel出力画面 ---
elif page == "Excel出力":
    st.header("申告用データの出力")
    st.write("国税庁の明細書に合わせて、**「人」と「病院」ごとにデータをまとめて**出力します。")

    if df.empty:
        st.warning("データがありません。")
    else:
        # 集計処理(名寄せ)
        grouped = df.groupby(["氏名", "支払先"]).agg({
            "支払金額": "sum",
            "補填金額": "sum",
            "区分_診療": "max", # 一回でもチェックがあればTrue
            "区分_医薬品": "max",
            "区分_介護": "max",
            "区分_その他": "max"
        }).reset_index()

        # 表示用データフレーム作成
        output_df = pd.DataFrame()
        output_df["(1)医療を受けた方の氏名"] = grouped["氏名"]
        output_df["(2)病院・薬局などの名称"] = grouped["支払先"]

        # 区分を✓に変換(Excelで見やすく)
        output_df["診療・治療"] = grouped["区分_診療"].apply(lambda x: "" if x else "")
        output_df["医薬品購入"] = grouped["区分_医薬品"].apply(lambda x: "" if x else "")
        output_df["介護保険"] = grouped["区分_介護"].apply(lambda x: "" if x else "")
        output_df["その他"] = grouped["区分_その他"].apply(lambda x: "" if x else "")

        output_df["(4)支払った医療費"] = grouped["支払金額"]
        output_df["(5)補填される金額"] = grouped["補填金額"]

        st.table(output_df)

        # Excelダウンロード
        output = BytesIO()
        with pd.ExcelWriter(output, engine='openpyxl') as writer:
            output_df.to_excel(writer, index=False, sheet_name='医療費控除明細')

        st.download_button(
            label="Excelファイルをダウンロード",
            data=output.getvalue(),
            file_name=f"医療費控除明細_{datetime.date.today()}.xlsx",
            mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        )

実行方法(ローカル)

3) Antigravityで起動

以下を実行しました。

streamlit run app.py

ブラウザでローカルホストが開き、入力画面が表示されます。
今後はここに領収書を都度入力していく運用にする予定です。


集計の考え方

Excel出力では、以下で集計しています。

  • キー:氏名 × 支払先
  • 金額:支払金額補填金額 は合計
  • 区分:同じ病院で1回でもチェックがあれば max でTrue化)

まとめ

医療費控除の入力が面倒だったので、PythonでAccess風(っぽい)入力ツールをローカルで作りました。

  • UIは最低限でよい(入力が早いこと重視)
  • CSVに保存して後から確認できる
  • 申告向けに「人×病院」で集計してExcel出力できる

同じように「Access入ってないけど入力UIが欲しい」人には、かなり手軽だと思います。毎回実行キーが必要ですが


6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?