はじめに
こんにちは。
前回、Pythonで特定保健指導対象者抽出ツールを作ってみた|Pandasとopenpyxlで健診データを自動判定という記事で、健診データから支援区分を自動判定するツールを紹介しました。
このツールのおかげで「誰が対象者か」は一目で分かるようになりましたが、
・対象者一人ひとりに合わせたコメントを考えるのに時間がかかる
・Excelを見ながら手作業で指導内容をまとめている
・同じような文章を毎回書き写していて非効率である
・紙やPDFで渡す資料の体裁を整えるのが手間に感じる
特定保健指導に携わったことがある方なら、一度はこうした「判定した後」の作業に時間を取られた経験があるのではないでしょうか。
そこで今回は、前回作成した判定結果をもとに、対象者ごとの「保健指導シート」をPDFで自動出力する仕組みを作ってみました。
その過程で、PDF内の日本語がすべて■(黒塗り四角)になるという、Python初心者ならではのトラブルにも遭遇したので、原因と解決方法もあわせて記録しておきます。
この記事でわかること
- Excelの健診データから、数値に応じた保健指導コメントを自動生成する方法
- 対象者ごとにPDF(氏名.pdf)を自動出力する方法
- reportlabで日本語PDFを作ると文字が■になってしまう原因
- TTFontを使って日本語フォントを登録し、文字化けを解決する方法
自己紹介
私は保健師7年目で、現在はフリーランスとして活動しています。
Python学習を始めて2か月半ほどですが、「保健指導の経験から感じた面倒くささをそのままツールにする」というスタンスで、前回の対象者抽出ツールに続き、今回のPDF自動生成ツールを作成しました。
現場での経験はあっても、エンジニアとしての経験はまだ浅いので、今回のようなフォント周りのトラブルによくぶつかります。同じところでつまずいている方の参考になれば嬉しいです。
目次
- 完成イメージ
- 使用したデータ
- 1.Excel読み込み処理
- 2.保健指導コメントの自動生成
- 3.PDF生成でつまずいた話:文字が■になる
- 4.PDF作成コード
- 5.出来上がったPDF
- このツールで得られたメリット
- まとめ
完成イメージ
- Excel(health_data.xlsx)から1人分ずつデータを読み込む
- BMIやHbA1cなどの数値に応じて保健指導コメントを自動生成する
- 対象者ごとにPDF(氏名.pdf)を出力する
前回のツールで判定した「支援区分」を土台にして、そこから先の指導コメント作成〜資料化までを自動化するイメージです。
使用したデータ
Excel(health_data.xlsx)を使用します。
health_data.xlsx には、以下のような健診データが24人分入っています。
| 氏名 | 性別 | 年齢 | 腹囲 | BMI | 収縮期血圧 | 拡張期血圧 | HbA1c | 中性脂肪 | LDLコレステロール | 支援区分 | リスク数 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 田中 | 男 | 45 | 90 | 27 | 145 | 94 | 5.6 | 180 | 40 | 積極的支援 | 3 |
| 鈴木 | 女 | 50 | 90 | 24 | 120 | 78 | 6.6 | 130 | 50 | 動機付け支援 | 1 |
| 佐藤 | 男 | 52 | 92 | 28 | 148 | 94 | 5.5 | 146 | 40 | 積極的支援 | 4 |
1. Excel読み込み処理
最初にpandas でExcelを読み込みました。
# excel_loader.py
import pandas as pd
def load_excel(path):
return pd.read_excel(path)
2. 保健指導コメントの自動生成
BMI・腹囲・血圧・脂質・HbA1c支援区分の値に応じて、コメントを組み立てます。
#comment_generator.py
def create_comment(row):
comments = []
# BMI
if row["BMI"] >= 25:
comments.append(
"体重を3〜5%減らすことを目標にしましょう。"
)
# 血圧
if row["収縮期血圧"] >= 140 or row["拡張期血圧"] >= 90:
comments.append(
"血圧が高めです。減塩を意識しましょう。"
)
elif row["収縮期血圧"] >= 130 or row["拡張期血圧"] >= 85:
comments.append(
"血圧が少し高めです。運動習慣をつけましょう。"
)
# 脂質
if row["中性脂肪"] >= 300 or row["HDLコレステロール"] <= 35:
comments.append(
"脂質が高めです。脂肪分の高い食事のとりすぎには気をつけましょう。"
)
elif row["中性脂肪"] >= 150 or row["HDLコレステロール"] < 40:
comments.append(
"脂質が少し高めです。食事バランスには気をつけましょう。"
)
# HbA1c
if row["HbA1c"] >= 6.5:
comments.append(
"HbA1cが高めです。糖質の摂り過ぎに注意しましょう。"
)
elif row["HbA1c"] >= 5.6:
comments.append(
"HbA1cが少し高めです。食生活を見直しましょう。"
)
# 支援区分
if row["支援区分"] == "積極的支援":
comments.append(
"積極的支援の対象です。生活改善に取り組みましょう。"
)
elif row["支援区分"] == "動機付け支援":
comments.append(
"動機付け支援の対象です。無理のない目標を立てましょう。"
)
else:
comments.append(
"現在の生活習慣を維持していきましょう。"
)
return "\n".join(comments)
前回のツールでは判定結果を出すところまでで終わっていましたが、こうして「支援区分」を再利用してコメントに反映させることで、判定〜コメント作成までをつなげられるのが個人的に気に入っているところです。
3. PDF生成でつまずいた話:文字が■になる
最初は reportlab の Paragraph を使い、ごく普通にPDFを作ろうとしました。
from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.styles import getSampleStyleSheet
def create_pdf(row):
filename = f"output/{row['氏名']}.pdf"
doc = SimpleDocTemplate(filename)
styles = getSampleStyleSheet()
story = []
story.append(Paragraph("保健指導シート", styles["Title"]))
story.append(Paragraph(f"氏名:{row['氏名']}", styles["BodyText"]))
# ...(以下略)
doc.build(story)
一見問題なく動きそうですが、生成されたPDFを開くと氏名やコメントの日本語部分がすべて「■■■■」という黒い四角に置き換わってしまいました。
原因
reportlab の標準スタイル(Title・BodyTextなど)は、デフォルトで Helvetica という欧文フォントを使用しています。このフォントには日本語のグリフ(字形データ)が含まれていないため、日本語を描画しようとすると「表示できない文字」を意味する■に置き換えられてしまうのです。
つまり文字コードやデータの問題ではなく、フォントが日本語に対応していないことが原因でした。
解決策:日本語フォントを登録してスタイルに適用する
reportlab.pdfbase.ttfonts.TTFont を使うと、任意のフォントファイルを登録してPDF内で使用できます。Windows環境であれば標準で日本語フォント(メイリオ・游ゴシック・MSゴシックなど)が入っているため、そのどれかを読み込んで使う方法が手軽です。
import os
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# Windows標準の日本語フォント候補(存在するものを自動選択)
FONT_CANDIDATES = [
(r"C:\Windows\Fonts\meiryo.ttc", "Meiryo"), # メイリオ(推奨・綺麗)
(r"C:\Windows\Fonts\YuGothM.ttc", "YuGothic"), # 游ゴシック
(r"C:\Windows\Fonts\msgothic.ttc", "MSGothic"), # MSゴシック
]
FONT_NAME = None
FONT_PATH = None
for path, name in FONT_CANDIDATES:
if os.path.exists(path):
FONT_PATH = path
FONT_NAME = name
break
if FONT_PATH is None:
raise FileNotFoundError("日本語フォントが見つかりませんでした。フォントパスを確認してください。")
# .ttc(TrueType Collection)の場合、subfontIndexの指定が必要
if FONT_NAME not in pdfmetrics.getRegisteredFontNames():
pdfmetrics.registerFont(TTFont(FONT_NAME, FONT_PATH, subfontIndex=0))
フォントを登録したら、あとは getSampleStyleSheet() で取得した各スタイルの fontName を、登録した日本語フォント名に差し替えるだけです。
from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.styles import getSampleStyleSheet
def create_pdf(row, output_dir="output"):
os.makedirs(output_dir, exist_ok=True)
filename = f"{output_dir}/{row['氏名']}.pdf"
doc = SimpleDocTemplate(filename)
styles = getSampleStyleSheet()
for style_name in ["Title", "BodyText", "Normal", "Heading1", "Heading2", "Heading3"]:
styles[style_name].fontName = FONT_NAME
story = []
story.append(Paragraph("保健指導シート", styles["Title"]))
story.append(Paragraph(f"氏名:{row['氏名']}", styles["BodyText"]))
story.append(Paragraph(f"性別:{row['性別']}", styles["BodyText"]))
story.append(Paragraph(f"年齢:{row['年齢']}", styles["BodyText"]))
story.append(Paragraph(f"BMI:{row['BMI']}", styles["BodyText"]))
story.append(Paragraph(f"腹囲:{row['腹囲']}", styles["BodyText"]))
story.append(Paragraph(f"収縮期血圧:{row['収縮期血圧']}", styles["BodyText"]))
story.append(Paragraph(f"拡張期血圧:{row['拡張期血圧']}", styles["BodyText"]))
story.append(Paragraph(f"中性脂肪:{row['中性脂肪']}", styles["BodyText"]))
story.append(Paragraph(f"HDLコレステロール:{row['HDLコレステロール']}", styles["BodyText"]))
story.append(Paragraph(f"HbA1c:{row['HbA1c']}", styles["BodyText"]))
story.append(Paragraph(f"支援区分:{row['支援区分']}", styles["BodyText"]))
story.append(Paragraph("保健指導コメント", styles["Heading2"]))
story.append(Paragraph(row["保健指導コメント"], styles["BodyText"]))
doc.build(story)
return filename
4. PDF作成コード
if __name__ == "__main__":
from excel_loader import load_excel
from comment_generator import create_comment
df = load_excel("health_data.xlsx")
df["保健指導コメント"] = df.apply(create_comment, axis=1)
for _, row in df.iterrows():
create_pdf(row)
print("PDFを作成しました。")
5. 出来上がったPDF
「田中」さんのデータで生成したPDFの中身は以下のようになりました。日本語が文字化けせずきちんと表示されています。
保健指導シート
氏名:田中
性別:男
年齢:45
BMI:27
腹囲:90
収縮期血圧:145
拡張期血圧:94
中性脂肪:180
HDLコレステロール:40
HbA1c:5.6
支援区分:積極的支援
保健指導コメント
体重を3〜5%減らすことを目標にしましょう。
血圧が高めです。減塩を意識しましょう。
脂質が少し高めです。食事バランスには気をつけましょう。
HbA1cが少し高めです。食生活を見直しましょう。
積極的支援の対象です。生活改善に取り組みましょう。
このツールで得られたメリット
- 対象者ごとのコメント作成が自動化され、指導内容を考える時間を減らせる
- Excelの数値をもとに一定基準でコメントが生成されるため、担当者による表現のばらつきを抑えられる
- PDFという形で一人ずつ資料化されるため、配布資料として使いやすい
- 前回の判定ツールと組み合わせることで、「抽出→判定→コメント作成→資料化」までの一連の業務がPythonでつながる
一つひとつは小さな自動化ですが、積み重ねることで「判定した後の面倒な作業」がまとめて減らせる、というのが今回の収穫でした。
まとめ
- reportlab で日本語PDFを作ると、標準フォントのままでは文字が■になる
- 原因は標準フォント(Helveticaなど)が日本語グリフを持っていないこと
- TTFontで日本語フォント(メイリオ・游ゴシック・MSゴシックなど)を登録し、スタイルのfontNameを差し替えれば解決する
- 実行環境ごとにフォントの場所が異なるため、存在チェックをして自動選択できるようにしておくと環境が変わっても動かしやすい
Excelデータ×Pythonで保健指導業務の一部を自動化できたので、今後は複数人分をまとめて出力したり、レイアウトをもう少し見やすく整えたりしていきたいと思います。
一方で、コメントを自動化することで業務効率化を狙えるが、対象者にはコメントが少し冷たく感じることもあるため、文章のレパートリーを増やすことや、自動生成したものに人間が手を加えていくことも並行して考えるとよいかと思います。
前回の対象者抽出ツールと合わせて、「判定からコメント作成、資料化まで」を一通り作ってみたことで、Python学習のモチベーションもさらに上がりました。同じように現場の実務課題を抱えている方にとって、この記事が「自分でもツールを作ってみよう」と思うきっかけの一つになれば嬉しいです。
最後まで読んでいただき、ありがとうございました!