classのついでの話
前々回から2回にわたりclassについて投稿しました。何気なく銀行口座を例に書きましたが、仕事に役立てられそうだなぁと思い立ったので書いてみます。
管理薬台帳のシステム化計画
プロフィールにも書きましたが、薬剤師をやっています。医薬品の中には数量を厳密に管理しなければいけないものもあり、どこの部署に何をどれだけ払い出したかなど台帳を作成し管理しています。毎回手書きで書くのが手間なんですよね。銀行口座みたいに、各医薬品でインスタンスを作成し在庫量を入庫、出庫処理で増減させれば何となくシステム化できそうです。
ただし、プログラムを実行している間はデータが保持されますが、終了するとデータは消えてしまいます。なので、インスタンスの在庫量に増減処理を加えるたびにデータベースに記録していけば、プログラムを終了してもデータは残るし、払い出しのログを残すこともできます。
さらに、払い出しをするたびにコードを打ち込むのはさすがに面倒なので、GUI(Graphical User Interface)を作成しマウス操作で完結できるといいですね。
GUIは、Tkinterなどのパッケージを使用すれば作成できますが、自分が使い慣れているstreamlitを使用することにしました。と、いってもデプロイしてwebアプリ化するわけではなく、ローカルホストで動かしてあくまでGUIとして使用します。
ちなみに、DBも含めwebアプリとしてデプロイしても、github上のDBは上書きされないので、webアプリをリロードすると初期値に戻るので注意してください。
作成したコード
import sqlite3
import datetime
import pandas as pd
import streamlit as st
today = datetime.datetime.today().strftime("%Y-%m-%d %H:%M")
db = sqlite3.connect('InventoryManagement.db')
cur = db.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT,
drug_name TEXT,
section TEXT,
receiving INTEGER,
dispensing INTEGER,
stock INTEGER
)
""")
db.commit()
cur.execute("""
CREATE TABLE IF NOT EXISTS stocks (
drug_name TEXT,
stock INTEGER
)
""")
db.commit()
db.close()
class InventoryManagement:
# 初期化メソッド
def __init__(self, drug_name, stock):
self.drug_name = drug_name # 医薬品名
self.stock = stock # 初期在庫数
db = sqlite3.connect('InventoryManagement.db')
cur = db.cursor()
cur.execute("INSERT INTO logs (date, drug_name, stock) VALUES (?, ?, ?)",
(today, self.drug_name, self.stock))
db.commit()
db.close()
# 納品・返品メソッド
def nyuko(self, section, amount):
if amount > 0:
self.stock += amount
db = sqlite3.connect('InventoryManagement.db')
cur = db.cursor()
cur.execute("INSERT INTO logs (date, drug_name, section, receiving, stock) VALUES (?, ?, ?, ?, ?)",
(today, self.drug_name, section, amount, self.stock))
db.commit()
cur.execute(f"UPDATE stocks SET stock = {self.stock} WHERE drug_name = '{self.drug_name}'")
db.commit()
db.close()
txt = f"{section}から{self.drug_name}を{amount}コ受け取り入庫処理しました。 \n{self.drug_name}の在庫数は{self.stock}です。※実数を必ず確認※ \n"
else:
txt = "個数は正の数である必要があります。 \n"
return txt
# 払出しメソッド
def shukko(self, section, amount):
if 0 < amount <= self.stock:
self.stock -= amount
db = sqlite3.connect('InventoryManagement.db')
cur = db.cursor()
cur.execute("INSERT INTO logs (date, drug_name, section, dispensing, stock) VALUES (?, ?, ?, ?, ?)",
(today, self.drug_name, section, amount, self.stock))
db.commit()
cur.execute(f"UPDATE stocks SET stock = {self.stock} WHERE drug_name = '{self.drug_name}'")
db.commit()
db.close()
txt = f"{section}へ{amount}コ払出しました。 \n{self.drug_name}の在庫数は{self.stock}です。※実数を必ず確認※ \n"
else:
txt = "在庫不足または無効な払出し数です。 \n"
return txt
# 在庫数確認メソッド
def check_stock(self):
txt = f"{self.drug_name}の在庫数: {self.stock}コ \n"
return txt
# 部署リスト作成(手動)
sections = ['A病棟', 'B病棟', 'C病棟', '卸']
def read_db():
db = sqlite3.connect('InventoryManagement.db')
cur = db.cursor()
cur.execute("SELECT * FROM stocks")
res = cur.fetchall()
db.close()
ins = []
drugs = []
for r in res:
ins.append(InventoryManagement(r[0], r[1]))
drugs.append(r[0])
return ins, drugs
st.title('医薬品管理')
ins, drugs = read_db()
d = st.selectbox('医薬品を選択', drugs)
col1, col2 = st.columns([6, 1])
with col2:
btn1 = st.button('在庫確認')
if btn1:
txt = ins[drugs.index(d)].check_stock()
st.write(txt)
st.write('---')
cb1 = st.checkbox('出入庫')
if cb1:
section = st.selectbox('部署を入力', sections)
num = st.number_input('数量を入力', step=1)
col3, col4 = st.columns([6, 1])
with col4:
btn2 = st.button('出庫処理')
btn3 = st.button('入庫処理')
if btn2:
txt = ins[drugs.index(d)].shukko(section, num)
st.write(txt)
if btn3:
txt = ins[drugs.index(d)].nyuko(section, num)
st.write(txt)
st.write('---')
cb2 = st.checkbox('全医薬品の在庫数を表示')
if cb2:
for d in drugs:
st.write(ins[drugs.index(d)].check_stock())
最初はDBが空なので、適当なデータを入力しておきます。
import sqlite3
drug_name = input('医薬品名')
amount = int(input('数量'))
db = sqlite3.connect('InventoryManagement.db')
cur = db.cursor()
cur.execute("INSERT INTO stocks (drug_name, stock) VALUES (?, ?)",
[drug_name, amount])
db.commit()
db.close()
インスタンスをinsというリストに格納しています。DBから読み込んで順番にインスタンスを作成し、リスト化しています。これに対応したdrugsというリストも作成しておき、streamlit上のselectboxで医薬品リストとして使用しています。
drugs.index(d) ←dはselectboxで選択された医薬品名(str)
でdrugs(医薬品リスト)のインデックス番号が取得できます。
ins[drugs.index(d)]とすることで、selectboxで選択された医薬品に対応するインスタンスを指定し、classで定義したメソッドを使用しています。
DBで管理しているので、dfに変換してcvsとして出力させることも簡単です。今回はGUIとしてstreamlitを使用しselectboxから選択する方法をとりましたが、実務的にはバーコードを読んで呼び出せると良いかもしれません。
エラー処理なども書いていませんが、何か使えそうな気がします。
ただし、…管理薬台帳は印鑑がやっぱり必要ですかね。こういう所でシステム化が進まない医療業界は残念です。