namespace Gwang
{
class Program
{
static void Main(string[] args)
{
string characterName = "John";
int characterAge;
characterAge = 35;
Console.WriteLine("There once was a man named" + characterAge);
Console.WriteLine("He was " + characterAge + " years old");
Console.WriteLine("He really liked the name " + characterName);
Console.WriteLine(string.Format("But didn't like being {0}.", characterAge));
Console.ReadLine();
}
}
}
namespace Gwang
{
class Program
{
static void Main(string[] args)
{
string phrase = "Park's Solution";
char grade = 'e';
int age = 30;
int minus_num = -220;
double gpa = 3.3;
bool isMale = false;
bool hasAlive = true;
Console.WriteLine("Hello!\n");
Console.ReadLine();
}
}
}
namespace Gwang
{
class Program
{
static void Main(string[] args)
{
int num = 8;
Console.WriteLine(num);
Console.WriteLine(Math.Abs(-111));
Console.WriteLine(Math.Max(3,2));
Console.WriteLine(Math.Min(128, 3.5));
Console.WriteLine(Math.Sqrt(64));
Console.WriteLine(4 + 50 / 2);
}
}
}
namespace Gwang
{
class Program
{
static void Main(string[] args)
{
Console.Write("Enter your name: ");
string name = Console.ReadLine();
Console.Write("Enter your age: ");
string age = Console.ReadLine();
Console.WriteLine("Hello " + name + ", you are " + age + ".");
Console.ReadLine();
}
}
}
namespace Gwang
{
class Program
{
static void Main(string[] args)
{
int num = Convert.ToInt32("45");
Console.WriteLine(num + 6);
Console.Write("Enter a number: ");
double num1 = Convert.ToDouble(Console.ReadLine());
Console.Write("Enter a another number: ");
double num2 = Convert.ToDouble(Console.ReadLine());
Console.WriteLine(num1 + num2);
}
}
}
namespace Gwang
{
class Program
{
static void Main(string[] args)
{
string color, pluralNoun, celebrity;
Console.Write("Enter a color: ");
color = Console.ReadLine();
Console.Write("Enter a plural noun: ");
pluralNoun = Console.ReadLine();
Console.Write("Enter celebrity: ");
celebrity = Console.ReadLine();
// Madlib
Console.WriteLine("Roses are {0}.", color);
Console.WriteLine("{0} are blue.", pluralNoun);
Console.WriteLine("I love {0}. ",celebrity);
}
}
}
# test_server_launcher.py
from Back.db_connector import DBConnector
from Back.server import EMRServer
if __name__ == '__main__':
db_conn = DBConnector(test_option=True)
db_conn.create_tables()
from data.insert_data_to_tables import *
insert_KTAS_data(db_conn)
insert_dummy_employee_data(db_conn, 30)
insert_dummy_chat_room(db_conn, 30)
insert_dummy_employee_chat_room_data(db_conn, 20, (1, 30), (1, 30))
server = EMRServer(db_conn)
server.start()
프로젝트명
미니 의료 정보 시스템 만들기
개발 인원
박광현 (1명)
개발 기간
2023년 07월 17일 ~ 23일 19시 (6일간)
개발 장소
광주인력개발원 생산정보 시스템실
과제 내용
이전 프로젝트에서 사용했던 파이썬과 네트워크, SQL을 다시 공부하고 모든 내용을 포함시켜
파이썬과 네트워크, SQL 전체를 아우르는 복습이 가능한 프로그램을 만들어오기
개발 결과
1. 환자 접수 기능 - 미구현
2. 로그인 기능 구현
3. 실시간 입실 환자 연동 구현
4. 실시간 직원 목록 및 부서 리스트 제공 구현
5. KTAS 리스트 자료 저장 구현
6. DashBoard 미구현
기능 설명
[구조 개요]
(서버파트)
파이썬 socket 모듈을 사용하여 소켓 통신(TCP/IP)을 사용한다. 현재는 Loopback IP, Port 9999를 사용하고 있으나, Common 패키지 내의 클래스 변수를 변경하면 이를 참조하여 변경할 수 있다.
파이썬 내장 Select 모듈을 사용하여 socket list 를 관리하고 listening하고 있으며, 블로킹 구조를 사용 중, timeout을 0.1초로 설정하였다. 구동된 서버는 쓰레드로 "receive_message" 라는 함수를 통해 들어온 데이터를 헤더에 따라 처리한다.
헤더와 데이터는 ASCII code 2번(start of text)를 통해 구분하고 있으며, 들어온 데이터 스트림에 대해 split을 시행시켜, 명령과 데이터를 구분하여 전달 받았다.
들어온 데이터는 이어서, 직접 커스텀한 ObjEncoder / ObjDecoder 클래스를 사용하였고, 이를 통해 원하는 형태로 자유자재로 데이터를 보내고 받는게 가능해졌다. 해당 클래스는 Common에 있어, Server class 와 Client class 가 같이 사용하고 있고, 패키지를 따로 두었기 때문에, 따로 모듈화 가능한 장점이 있다. 또한, eval 함수를 사용하지 않았고 오류가 발생하면 해당 요청을 무시해버리도록 설계하여 보안과 내구성도 이전 프로젝트에 비해서 올라간 것이 특징이다.
서버를 구동시키면 Dependency Injection 된 DBConnector 인스턴스를 이용 DB 접근 가능하고, 객체 또는 해당 테이블 ID 를 통해서 객체를 INSERT하거나 SELECT 할 수 있다. ORM을 계획하고 작성하였기 때문에, 설계하는데 시간은 조금 걸렸지만, 관계와 속성이 확정되고 함수가 만들어지기 시작하니까 기능 추가하는 것이 아주 수월해졌다.
(DB 파트)
네트워크 연습을 위해 주로 사용했던 된 테이블은 Employee 와 ChatRoom이다. 이 둘은 서로 다대 다 관계이기 때문에 Employee_Chatroom 테이블로 일대 다, 다대 일 관계로 풀어내었고, SQL 로직과 파이썬 로직을 합해 이전과 대화를 했던 적이 있던 상대라면 이전의 대화방을 불러주고, 그게 아니라면 새로운 Chat room row 를 만들어 autoincrement되는 ID를 서버가 받고, 이를 다시 클라이언트가 받아 해당 방에 message sending을 할 수 있게 설계하였다.
수월한 테스트를 위해 따로 SQL 파일을 작성하여 DBConnector에서 executeScript를 실행하게끔 하였고, 더미 데이터를 입력하여 테스트 시간을 단축시키고 효과적으로 상황에 대응하기 위해 노력하였다. 다만 이 방법은 몇번째 줄에서 오류가 났는지 알려주지 않기 때문에 어느정도 숙련된 SQL 작성자에게 권한다. (그래도 잘 모르겠으면 직접 script 를 옮겨다가 browser에서 실행시키거나 cli 에서 작성하면 몇번째 줄인지는 알려준다.)
(클라이언트 파트)
SOLID SRP를 준수하기 위해 Connector 클래스와 Controller 클래스를 구분하였다.
Connector는 서버소켓과 연결, Controller 는 인스턴스 변수로 각 위젯들을 생성하고, 관리하고 있으며, PyQtSignal을 통해 Connector로 들어온 메시지에 대해 Controller를 핸들링하거나, 메모리에 필요한 정보를 미리 가지고 있다가 Controller에서 요청이 있을 때 참조한다.
Controller 에서는 조작 부분(창 띄우기, 닫기 또는 connector로의 함수 호출, connector로부터 emitted 된 신호들을 처리) 기능을 맡고 있고 Connector는 controller로부터 들어온 함수를 실질적으로 server로 보내거나, server로 부터 들어온 데이터 처리 및 저장, signal 호출 등의 역할을 구분하였다.
[클래스 설명]
DBConnector: server 에 sqlite 연결 기능 제공, 여러 connection pool을 사용하지 않도록 singleton 패턴으로 설계함
EMRServer: 서버 기능 클래스
Connector: 클라이언트(통신) 클래스
WidgetController: 클라이언트(컨트롤러) 클래스
Common: 공통적으로 사용하는 클래스(상수나 연속적으로 사용하는 str 을 저장하여 오타로 인한 오류를 억제하고자 사용하였음
ObjEncoder/ObjDecoder 클래스: object 또는 list(tuple), list(dict) 구조를 json 또는 formatted string으로 만든뒤 UTF-8 로 인코딩/디코딩하는 역할을 함
FakeDataMaker: 파이썬 Faker 모듈을 사용해 테스트용 더미 데이터를 생성하는데 사용함
[DAO]
BedOfWard
ChatRoom
Department
EmergencyNurseRecord
EmployeeChatRoom
KTAS
Message
일정표
표1) 참조
요구분석표
표2) 참조
ERD
그림 1) 참조
구동 영상 및 이미지
하단 첨부 이미지 및 영상 참조 (첨부파일 동봉)
소스 코드
하단 소스 코드 또는 첨부파일 참조
개발 후기
정말 순식간에 마감일이 되어버렸다. 완성도 있는 결과물보다는 판을 너무 크게 벌려놓은 탓에 수습을 못한 부분이 많은데, 매 순간 막히는 부분이 생겨도 어떻게든 문제점을 들춰내 하나씩 해결해나가다 보면 프로그램이 예상대로 구동되고, 하나씩 동작이 되면서 성취감을 느꼈다. 미완성되어 미구현한 부분들도 시간이 있었다면 다 만들어낼 수 있었을 것이다. 다양한 데이터 전송과 많은 타이핑을 경험했고, 아직 두렵긴 하지만 네트워크가 가능하면 할 수 있는 일이 무진장 많아지기 때문에 있는 시간을 최선을 다하고 최대한 활용했다.
또한, 많은 동료 수강생들이 비슷한 문제를 맞닥뜨리고 있을 때, 같이 고민해주려고 노력하였고, 엄연히 개인 프로젝트지만 스터디를 같이하는 팀플레이 요소도 있었기 때문에, 중간에 그룹스터디도 진행하고 팀원들의 진행사항을 계속 속속이 파악했다. 부득이하게 멀리 튕겨나간 사람도 있었다. 다시 한번 느끼는 일이지만, 살아남은 자가 강하고, 강한자가 살아 남는다. 가만히 있지도 말 것이고, 이렇게 버티고 있는 것도 잘하는 것이라 스스로 위로해본다.
이번 프로젝트에서 가장 아쉬운 점은 통일성이 부족한 DTO 객체들과 복잡한 DAO 관계 및 상태로 인해 빠른 시간안에 기대했던 모든 기능을 구현하지 못했다는 것이다. 만일 이 경험 상태로 다시 프로젝트 처음으로 돌아간다면, UI 제작 -> 테이블 제작 -> 송수신 데이터 설계 -> 테이블 수정 보완 -> 구현 순으로 진행할 것 같다. 이번 프로젝트는 UI 제작 -> 테이블 제작 -> UI 기능 구현 + 송수신 데이터 동시에 진행하다 보니, 매끄럽게 진행되지 못했다.
미숙한 부분이 많았지만, 이전 프로젝트에서 거의 실패라고 느껴졌던 실시간 네트워크 통신에 대해 많은 부분 보완할 수 있었고, 거진 한달 전에 진행했던 QtFlowSheet 개인 프로젝트에 비해서도 많은 성장이 이뤄졌다.
테이블은 총 14개가 나왔다. 처음엔 12개 정도 테이블이 나왔는데, 중간에 관계 요소가 누락되었거나, 데이터 저장을 위해 추가해야하는 테이블이 더 늘어나면서 부득이하게 수정이 필요했다. 이에 대해 대응하는데도 좋은 경험이 되었다. 이전에 비해 확실히 딜레이 없이 SQL이 자동으로 작성되고 있고, 테이블 관계가 작성된다. 프로젝트 첫날에 RDBMS 의 관계 구조에 대해 동료 수강생들과 스터디를 진행한 것이 큰 도움이 되었다. 하지만 아직 부족한 것이 많기 때문에 계속 연습해볼 예정이다.
표1) 일정표
개발 기간
표2) 요구분석표
그림 1) ERD (Entity Relationship Diagram)
소스코드
# db_connector.py
import datetime
import sqlite3
import numpy as np
import pandas as pd
from Domain.chat_room import ChatRoom
from Domain.emergency_nurse_record import EmergencyNurseRecord
from Domain.message import Message
from Domain.people._employee import Employee
from Domain.people.patient import Patient
class DBConnector:
# ===================== BASIC ============================ #
_instance = None
SQL_PATH = r"../Back/table_creation.sql"
def __new__(cls, test_option=False):
if not isinstance(cls._instance, cls):
cls._instance = object.__new__(cls)
return cls._instance
def __init__(self, test_option=False):
self.conn = None
self.test_option = test_option
def start_conn(self):
if self.test_option is False:
self.conn = sqlite3.connect('../test/main_storage.db')
else:
self.conn = sqlite3.connect('../test/test_storage.db')
return self.conn.cursor()
def end_conn(self):
if self.conn is not None:
self.conn.close()
self.conn = None
def commit_db(self):
self.conn.commit()
@staticmethod
def set_pandas_configure():
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)
pd.set_option('display.width', 1000)
@staticmethod
def make_prepared_statement(object_, table_name):
if hasattr(object_, 'to_dict_for_insert'):
key_words = object_.to_dict_for_insert().keys()
keys = ','.join(key_words)
values = object_.to_dict_for_insert()
else:
key_words = object_.__dict__.keys()
keys = ','.join(object_.__dict__.keys())
values = object_.__dict__
colon_keys = ','.join([f':{x}' for x in key_words])
# print('#key_words#',key_words)
# print('#keys#',keys)
# print('#values#',values)
# print('#colon_keys#',colon_keys)
pstmt = f'insert into {table_name} ({keys}) values ({colon_keys})'
return pstmt, values
def find_obj_by_id_and_table_name(self, obj_id, table_name, class_name):
assert isinstance(obj_id, int) and isinstance(table_name, str)
c = self.start_conn()
fetched_row = c.execute(f'select * from {table_name} where {class_name}_id = {obj_id}').fetchone()
self.end_conn()
return fetched_row
def find_all_by_table_name(self, table_name):
assert isinstance(table_name, str)
c = self.start_conn()
fetched_row_list = c.execute(f'select * from {table_name}').fetchall()
self.end_conn()
return fetched_row_list
# ================ CREATE TABLE ============================ #
def create_tables(self):
c = self.start_conn()
pstmt = None
with open(self.SQL_PATH, 'r', encoding='utf-8') as file:
pstmt = file.read()
if pstmt is None:
raise 'sql문 못불러옴'
c.executescript(pstmt)
self.commit_db()
self.end_conn()
print('created_tables')
# ===================== DOMAIN ============================ #
# ================ EMPLOYEE ============================ #
def insert_employee(self, employee: Employee):
assert isinstance(employee, Employee)
c = self.start_conn()
if employee.employee_id is None:
# insert
pstmt, values, = self.make_prepared_statement(employee, 'tb_employee')
c.execute(pstmt, values)
else:
# todo: update 로직 필요하면 작성
return
self.commit_db()
row = c.execute('select * from tb_employee order by employee_id desc limit 1').fetchone()
result = Employee(*row)
self.end_conn()
return result
def find_employee_by_id(self, employee_id):
fetched_row = self.find_obj_by_id_and_table_name(employee_id, "tb_employee", "employee")
return Employee(*fetched_row)
def find_all_employee(self):
fetched_rows = self.find_all_by_table_name("tb_employee")
result_list = list()
for row in fetched_rows:
result_list.append(Employee(*row))
return result_list
def find_all_employee_department(self):
all_employee_list = self.find_all_employee()
c = self.start_conn()
result_list = list()
for e in all_employee_list:
name = None
if e.type_job == 1:
name = c.execute(f"""
select name from tb_department where department_id =
(select assigned_department_id from tb_doctor where employee_id = {e.employee_id})
""").fetchone()
elif e.type_job ==2:
name = c.execute(f"""
select name from tb_department where department_id =
(select assigned_ward_id from tb_nurse where employee_id = {e.employee_id})
""").fetchone()
elif e.type_job ==3:
name = c.execute(f"""
select name from tb_department where department_id =
(select assigned_part_id from tb_administration where employee_id = {e.employee_id})
""").fetchone()
if name is None:
name = "-"
else:
name = name[0]
result_list.append(name)
return result_list
def assert_login_username_and_password(self, username, password):
c = self.start_conn()
fetched_row = c.execute(f'select * from tb_employee where login_username=? and login_password=?',
(f'{username}', f'{password}',)).fetchone()
if fetched_row is None:
return None
return Employee(*fetched_row)
# ================ DOCTOR / NURSE / ADMIN ============================ #
def insert_doctor_job(self, employee, assigned_department_id):
c = self.start_conn()
c.execute(f'''insert into tb_doctor(employee_id, assigned_department_id)
values (?, ?)''', (employee.employee_id, assigned_department_id,))
self.commit_db()
self.end_conn()
def insert_nurse_job(self, employee, assigned_ward_id):
c = self.start_conn()
c.execute(f'''insert into tb_nurse(employee_id, assigned_ward_id)
values (?, ?)''', (employee.employee_id, assigned_ward_id,))
self.commit_db()
self.end_conn()
def insert_admin_job(self, employee, assigned_part_id):
c = self.start_conn()
c.execute(f'''insert into tb_administration(employee_id, assigned_part_id)
values (?, ?)''', (employee.employee_id, assigned_part_id,))
self.commit_db()
self.end_conn()
def find_doctor_name_by_doctor_id(self, doctor_id):
c = self.start_conn()
fetched_row = c.execute(f"""
select name from tb_employee where employee_id =
(select employee_id from tb_doctor where doctor_id = ?)
""", (doctor_id, )).fetchone()
if fetched_row is not None:
result = fetched_row[0]
else:
result = ''
self.end_conn()
return result
def find_nurse_name_by_nurse_id(self, nurse_id):
c = self.start_conn()
fetched_row = c.execute(f"""
select name from tb_employee where employee_id =
(select employee_id from tb_nurse where nurse_id = ?)
""", (nurse_id, )).fetchone()
if fetched_row is not None:
result = fetched_row[0]
else:
result = ''
self.end_conn()
return result
# ================ CHAT ROOM ============================ #
def insert_chat_room(self, chat_room: ChatRoom):
assert isinstance(chat_room, ChatRoom)
c = self.start_conn()
if chat_room.chat_room_id is None:
# insert
pstmt, values, = self.make_prepared_statement(chat_room, 'tb_chat_room')
c.execute(pstmt, values)
else:
# todo: update 로직 필요하면 작성
pass
self.commit_db()
row = c.execute('select * from tb_chat_room order by chat_room_id desc limit 1').fetchone()
result = ChatRoom(*row)
self.end_conn()
return result
def create_chat_room(self, login_employee_id, request_employee_id):
c = self.start_conn()
now_time_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
c.execute(f"""
insert into tb_chat_room(created_time) values (?)
""", (f"{now_time_str}",))
self.commit_db()
last_index = c.execute(f"""
select chat_room_id from tb_chat_room order by chat_room_id desc limit 1
""").fetchone()
if last_index is None:
raise 'insert 실패'
last_index = last_index[0]
c.executescript(f"""
insert into tb_employee_chat_room(chat_room_id,employee_id)
values ({last_index}, {login_employee_id}),
({last_index}, {request_employee_id});
""")
self.commit_db()
self.end_conn()
def find_chat_room_by_two_employee_id(self, login_employee_id, request_employee_id):
assert isinstance(login_employee_id, int) and isinstance(request_employee_id, int)
c = self.start_conn()
login_employee_chat_room_list = set()
request_employee_chat_room_list = set()
fetched_row = c.execute(f"""
select chat_room_id from tb_employee_chat_room where employee_id = {login_employee_id}
""").fetchall()
for i in fetched_row:
login_employee_chat_room_list.update(i)
fetched_row = c.execute(f"""
select chat_room_id from tb_employee_chat_room where employee_id = {request_employee_id}
""").fetchall()
for i in fetched_row:
request_employee_chat_room_list.update(i)
intersection_chat_room_set = login_employee_chat_room_list.intersection(request_employee_chat_room_list)
if len(intersection_chat_room_set) == 0:
intersection_chat_room_id = self.create_chat_room(login_employee_id, request_employee_id)
else:
intersection_chat_room_id= intersection_chat_room_set.pop()
print("intersection_chat_room_id: ", intersection_chat_room_id)
return intersection_chat_room_id
def find_chat_room_by_id(self, chat_room_id):
assert isinstance(chat_room_id, int)
fetched_row = self.find_obj_by_id_and_table_name(chat_room_id, "tb_chat_room", "chat_room")
# 관련 톡방 사용자(2명) list 가져오기
employee_list = self.find_employees_by_chat_room_id(chat_room_id)
# message_list 가져오기
message_list = self.find_messages_by_chat_room_id(chat_room_id)
return ChatRoom(*fetched_row, employee_list=employee_list, message_list=message_list)
def find_employees_by_chat_room_id(self, chat_room_id):
c = self.start_conn()
fetched_rows = c.execute(f"""
select * from tb_employee where employee_id in
(select employee_id from tb_employee_chat_room where chat_room_id = {chat_room_id})
""").fetchall()
result_list = list()
for row in fetched_rows:
result_list.append(Employee(*row))
self.end_conn()
return result_list
# ================ EMPLOYEE CHAT ROOM ============================ #
def insert_employee_chat_room(self, employee_id, chat_room_id):
assert isinstance(employee_id, int) and isinstance(chat_room_id, int)
c = self.start_conn()
c.execute('insert into tb_employee_chat_room(employee_id, chat_room_id) values (?, ?)',
(employee_id, chat_room_id))
self.commit_db()
self.end_conn()
# ================ EmergencyNurseRecord ============================ #
def find_emergency_nurse_record_by_register_id(self, register_id):
c = self.start_conn()
fetched_row = c.execute("""
select * from tb_emergency_nurse_record where register_id = ?
""", (register_id,)).fetchone()
if fetched_row is not None:
return EmergencyNurseRecord(*fetched_row)
return None
# ================ patient ============================ #
def find_all_patient(self):
fetched_rows = self.find_all_by_table_name("tb_patient")
result_list = list()
for row in fetched_rows:
result_list.append(Patient(*row))
return result_list
def find_patient_by_bed_id(self, bed_id):
c = self.start_conn()
fetched_row = c.execute("""
select * from tb_patient where using_bed_id = ?
""", (bed_id, )).fetchone()
if fetched_row is not None:
return Patient(*fetched_row)
return None
def find_bed_id_and_name_list_by_employee_id(self, employee_id):
c = self.start_conn()
job_code = self.find_employee_by_id(employee_id).type_job
table_name = ''
if job_code == 1:
table_name = 'tb_doctor'
col_name = 'assigned_department_id'
elif job_code == 2:
table_name = 'tb_nurse'
col_name = 'assigned_ward_id'
else:
table_name = 'tb_admin'
col_name = 'assigned_part_id'
fetched_rows = c.execute(f"""
select bed_id, viewing_bed_name from tb_bed_of_ward
where assigned_ward_id =
(select {col_name} from {table_name} where employee_id =?)
""", (employee_id,)).fetchall()
bed_id_list = list()
name_list = list()
for row in fetched_rows:
bed_id_list.append(row[0])
name_list.append(row[1])
self.end_conn()
return bed_id_list, name_list
# ================ MESSAGE ============================ #
def insert_message(self, message: Message):
assert isinstance(message, Message)
c = self.start_conn()
if message.message_id is None:
# insert
pstmt, values, = self.make_prepared_statement(message, 'tb_message')
c.execute(pstmt, values)
else:
# todo: update 로직 필요하면 작성
pass
self.commit_db()
row = c.execute('select * from tb_message order by message_id desc limit 1').fetchone()
result = Message(*row)
self.end_conn()
return result
def find_messages_by_message_id(self, message_id):
c = self.start_conn()
fetched_row = c.execute(f"""
select * from tb_message where message_id = {message_id})
""").fetchone()
result = Message(*fetched_row)
self.end_conn()
return result
def find_messages_by_chat_room_id(self, chat_room_id):
c = self.start_conn()
fetched_rows = c.execute(f"""
select * from tb_message where chat_room_id = ?
""", (chat_room_id, )).fetchall()
result_list = list()
for row in fetched_rows:
result_list.append(Message(*row))
self.end_conn()
return result_list
def checked_message_by_chat_room_id(self, chat_room_id):
c = self.start_conn()
fetched_rows = c.execute(f"""
update tb_message set "is_confirmed"=1 where message_id in
(select employee_id from tb_employee_chat_room where chat_room_id = {chat_room_id})
""").fetchall()
result_list = list()
for row in fetched_rows:
result_list.append(Message(*row))
self.end_conn()
return result_list
# server.py
import time
import traceback
import socket
from threading import Thread
import select
from Back.db_connector import DBConnector
from Common.class_common import Common
from Common.class_json_converter import ObjDecoder, ObjEncoder
from Domain.dto.dto_class import DTOMaker
from Domain.message import Message
from Domain.people._employee import Employee
class EMRServer:
def __init__(self, db_connector: DBConnector):
self.db_conn = db_connector
self.common = Common()
self.server_socket = None
self.sockets_list = list()
self.clients = dict()
self.thread_for_run = None
self.run_signal = True
self.decoder = ObjDecoder()
self.encoder = ObjEncoder()
self.dto_maker = DTOMaker()
def start(self):
if self.thread_for_run is not None: # 실행중이면 종료 시키기
return
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # AF_INET(ipv4를 의미)
self.server_socket.bind((self.common.HOST, self.common.PORT)) # 바인딩
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.listen() # 리슨 시작
self.sockets_list.clear() # 소켓리스트 클리어
self.sockets_list.append(self.server_socket)
self.run_signal = True
self.thread_for_run = Thread(target=self.run)
self.thread_for_run.start()
print('SERVER STARTED')
def run(self):
while True:
if self.run_signal is False:
break
try:
read_sockets, _, exception_sockets = select.select(self.sockets_list, [], self.sockets_list, 0.1)
except Exception:
continue
for notified_socket in read_sockets:
if notified_socket == self.server_socket:
client_socket, client_address = self.server_socket.accept()
print(client_address, '연결됨')
user = self.receive_message(client_socket)
if user is False:
continue
self.sockets_list.append(client_socket)
self.clients[client_socket] = user
else:
message = self.receive_message(notified_socket)
if message is False:
self.sockets_list.remove(notified_socket)
print(notified_socket, '연결해제됨')
del self.clients[notified_socket]
for notified_socket in exception_sockets:
self.sockets_list.remove(notified_socket)
print(notified_socket, '오류로 연결해제됨')
del self.clients[notified_socket]
def send_message(self, client_socket: socket, header, data):
assert isinstance(header, str) or isinstance(header, bytes)
assert isinstance(data, str) or isinstance(data, bytes)
if isinstance(header, bytes):
header_to_sending = header.decode(self.common.FORMAT)
else:
header_to_sending = header
if isinstance(data, bytes):
data_to_sending = data.decode(self.common.FORMAT)
else:
data_to_sending = data
message_to_send = f"{self.common.START_OF_TEXT}".join([header_to_sending, data_to_sending])
if len(message_to_send) >= self.common.BUFFER:
raise "부족한 버퍼 용량"
message_to_send = f"{message_to_send:<{self.common.BUFFER}}"
print(f"SERVER SENDED: (HEADER: {header} | DATA: {data})")
client_socket.send(message_to_send.encode(self.common.FORMAT))
def header_data_distributor(self, recv_encoded_message):
decoded_message = recv_encoded_message.decode(self.common.FORMAT).strip()
header, data_decoded_str, = decoded_message.split(self.common.START_OF_TEXT)
data = self.decoder.binary_to_obj(data_decoded_str)
return header, data
def receive_message(self, client_socket: socket):
try:
recv_encoded_message = client_socket.recv(self.common.BUFFER)
if len(recv_encoded_message) < 3:
return True
request_header, request_data = self.header_data_distributor(recv_encoded_message)
print(f"SERVER RECEIVED: ({request_header},{request_data})")
except Exception:
traceback.print_exc()
return False
if request_header == self.common.LOGIN_ACCESS_REQ:
username = request_data['login_username']
password = request_data['password']
login_employee = self.db_conn.assert_login_username_and_password(username, password)
if login_employee is None:
self.send_message(client_socket, self.common.LOGIN_ACCESS_RES, self.common.FALSE)
else:
# 로그인 성공 로직
# 회원 정보 보내기
data_str = self.encoder.toJSON_an_object(login_employee)
self.send_message(client_socket, self.common.LOGIN_ACCESS_RES, data_str)
# patient_list 보내기
# sample_patient_list = f"""{[("IC-1", "김철수(M/55)", "김순재/조운", "접수중"),
# ("IC-2", "관우(M/22)", "김순재/박사라", "접수중")]}"""
stored_patient_list = list()
patient_table_list = list()
bed_id_list, name_list, = self.db_conn.find_bed_id_and_name_list_by_employee_id(
login_employee.employee_id)
patient_list = list()
doctor_list = list()
state_list = list()
first_patient_index = -1
for idx, i in enumerate(bed_id_list):
temp = self.db_conn.find_patient_by_bed_id(i)
stored_patient_list.append(temp)
if temp is None:
patient_list.append('')
doctor_list.append('')
state_list.append('')
else:
if first_patient_index == -1:
first_patient_index = idx
patient_list.append(temp.get_name_and_age())
doctor_name = self.db_conn.find_doctor_name_by_doctor_id(temp.assigned_doctor_id)
nurse_name = self.db_conn.find_nurse_name_by_nurse_id(temp.assigned_nurse_id)
doctor_list.append(f"{doctor_name}/{nurse_name}")
state_list.append('접수중')
for row in zip(name_list, patient_list, doctor_list, state_list):
patient_table_list.append(row)
self.send_message(client_socket, self.common.PATIENT_NAMELIST_RES, f"{patient_table_list}")
print('first_patient_index:', first_patient_index)
# patient 보내기
if first_patient_index != -1:
self.send_message(client_socket, self.common.PATIENT_RES,
f"{stored_patient_list[first_patient_index]}")
time.sleep(0.2)
# MEDICAL_ORDER_RES
emergency_nurse_record = self.db_conn.find_emergency_nurse_record_by_register_id(
stored_patient_list[first_patient_index].register_number)
self.send_message(client_socket, self.common.EMERGENCY_NURSE_RECORD_RES,
f"{emergency_nurse_record}")
# all_employee 보내기
employee_list = self.db_conn.find_all_employee()
self.send_message(client_socket, self.common.ALL_EMPLOYEE_LIST_RES,
f"{employee_list}")
# all_employee_department 보내기
all_employee_department = self.db_conn.find_all_employee_department()
self.send_message(client_socket, self.common.ALL_EMPLOYEE_DEPARTMENT_LIST_RES,
f"{all_employee_department}")
elif request_header == self.common.CHAT_ROOM_REQ:
assert isinstance(request_data, tuple)
login_employee_id, request_employee_id= request_data
chat_room_id = self.db_conn.find_chat_room_by_two_employee_id(login_employee_id, request_employee_id)
chat_room = self.db_conn.find_chat_room_by_id(chat_room_id)
self.send_message(client_socket, self.common.CHAT_ROOM_RES, self.encoder.toJSON_an_object(chat_room))
message_list = self.db_conn.find_messages_by_chat_room_id(chat_room_id)
self.send_message(client_socket, self.common.MESSAGE_LIST_RES, self.encoder.toJSON_an_object(message_list))
elif request_header == self.common.SEND_A_MESSAGE_REQ:
assert isinstance(request_data, Message)
arrived_message = request_data
processed_message = self.db_conn.insert_message(arrived_message)
data = self.encoder.toJSON_an_object(processed_message)
for s in self.sockets_list:
if s != self.server_socket:
self.send_message(s, self.common.SEND_A_MESSAGE_RES, data)
return True
# table_creation.SQL
DROP TABLE IF EXISTS tb_ktas;
CREATE TABLE "tb_ktas" (
"ktas_id" INTEGER,
"first_category_name" TEXT NOT NULL,
"first_category_code" TEXT NOT NULL,
"second_category_name" TEXT NOT NULL,
"second_category_code" TEXT NOT NULL,
"third_category_name" TEXT NOT NULL,
"third_category_code" TEXT NOT NULL,
"fourth_category_name" TEXT NOT NULL,
"fourth_category_code" TEXT NOT NULL,
"final_grade" TEXT NOT NULL,
"ktas_code" TEXT NOT NULL,
PRIMARY KEY("ktas_id" AUTOINCREMENT)
);
DROP TABLE IF EXISTS tb_employee;
CREATE TABLE "tb_employee" (
"employee_id" INTEGER,
"name" TEXT NOT NULL,
"type_job" INTEGER NOT NULL,
"login_username" TEXT NOT NULL UNIQUE,
"login_password" TEXT NOT NULL,
"mobile_phone_num_1" TEXT,
"mobile_phone_num_2" TEXT,
PRIMARY KEY("employee_id" AUTOINCREMENT)
);
DROP TABLE IF EXISTS tb_department;
CREATE TABLE "tb_department" (
"department_id" INTEGER,
"name" TEXT NOT NULL,
"job_category" INTEGER NOT NULL,
PRIMARY KEY("department_id" AUTOINCREMENT)
);
DROP TABLE IF EXISTS tb_doctor;
CREATE TABLE "tb_doctor" (
"doctor_id" INTEGER,
"employee_id" INTEGER NOT NULL,
"assigned_department_id" INTEGER NOT NULL,
PRIMARY KEY("doctor_id" AUTOINCREMENT)
FOREIGN KEY("assigned_department_id") REFERENCES "tb_department"("department_id")
);
DROP TABLE IF EXISTS tb_nurse;
CREATE TABLE "tb_nurse" (
"nurse_id" INTEGER,
"employee_id" INTEGER NOT NULL,
"assigned_ward_id" INTEGER NOT NULL,
PRIMARY KEY("nurse_id" AUTOINCREMENT),
FOREIGN KEY("employee_id") REFERENCES "tb_employee"("employee_id")
FOREIGN KEY("assigned_ward_id") REFERENCES "tb_department"("department_id")
);
DROP TABLE IF EXISTS tb_administration;
CREATE TABLE "tb_administration" (
"administration_id" INTEGER,
"employee_id" INTEGER NOT NULL,
"assigned_part_id" INTEGER NOT NULL,
PRIMARY KEY("administration_id" AUTOINCREMENT),
FOREIGN KEY("employee_id") REFERENCES "tb_employee"("employee_id"),
FOREIGN KEY("assigned_part_id") REFERENCES "tb_department"("department_id")
);
DROP TABLE IF EXISTS tb_chat_room;
CREATE TABLE "tb_chat_room" (
"chat_room_id" INTEGER,
"created_time" TEXT NOT NULL,
PRIMARY KEY("chat_room_id" AUTOINCREMENT)
);
DROP TABLE IF EXISTS tb_employee_chat_room;
CREATE TABLE "tb_employee_chat_room" (
"employee_chat_room_id" INTEGER,
"chat_room_id" INTEGER NOT NULL,
"employee_id" INTEGER NOT NULL,
FOREIGN KEY("chat_room_id") REFERENCES "tb_chat_room"("chat_room_id"),
FOREIGN KEY("employee_id") REFERENCES "tb_employee"("employee_id"),
PRIMARY KEY("employee_chat_room_id" AUTOINCREMENT)
);
DROP TABLE IF EXISTS tb_patient;
CREATE TABLE "tb_patient" (
"patient_id" INTEGER,
"birth_date" TEXT,
"name" TEXT NOT NULL,
"sex" TEXT NOT NULL,
"ssn" TEXT UNIQUE,
"address" TEXT,
"type_insurance" TEXT,
"using_bed_id" INTEGER,
"register_number" INTEGER UNIQUE,
"assigned_doctor_id" INTEGER,
"assigned_nurse_id" INTEGER,
FOREIGN KEY("using_bed_id") REFERENCES "tb_bed_of_ward"("bed_id"),
FOREIGN KEY("assigned_doctor_id") REFERENCES "tb_doctor"("doctor_id"),
FOREIGN KEY("assigned_nurse_id") REFERENCES "tb_nurse"("nurse_id"),
PRIMARY KEY("patient_id" AUTOINCREMENT)
);
DROP TABLE IF EXISTS tb_bed_of_ward;
CREATE TABLE "tb_bed_of_ward" (
"bed_id" INTEGER,
"assigned_ward_id" INTEGER NOT NULL,
"additional_info" TEXT,
"viewing_bed_name" TEXT NOT NULL,
PRIMARY KEY("bed_id" AUTOINCREMENT),
FOREIGN KEY("assigned_ward_id") REFERENCES "tb_department"
);
DROP TABLE IF EXISTS tb_emergency_nurse_record;
CREATE TABLE "tb_emergency_nurse_record" (
"enr_id" INTEGER,
"register_id" INTEGER,
"onset_time" TEXT,
"ktas_id" INTEGER,
"cheif_complain" TEXT,
"description" TEXT,
"recorder_nurse_id" INTEGER,
"saved_time" TEXT,
"responser" TEXT,
"memo" TEXT,
PRIMARY KEY("enr_id" AUTOINCREMENT),
FOREIGN KEY("recorder_nurse_id") REFERENCES "tb_nurse"("nurse_id")
FOREIGN KEY("register_id") REFERENCES "tb_patient"("register_number")
);
DROP TABLE IF EXISTS tb_message;
CREATE TABLE "tb_message" (
"message_id" INTEGER,
"sender_employee_id" INTEGER NOT NULL,
"chat_room_id" INTEGER NOT NULL,
"contents" TEXT NOT NULL,
"is_confirmed" INTEGER NOT NULL,
FOREIGN KEY("chat_room_id") REFERENCES "tb_chat_room"("chat_room_id"),
PRIMARY KEY("message_id" AUTOINCREMENT),
FOREIGN KEY("sender_employee_id") REFERENCES "tb_employee"("employee_id")
);
DROP TABLE IF EXISTS tb_medical_order;
CREATE TABLE "tb_medical_order" (
"medical_order_id" INTEGER,
"patient_register_id" INTEGER NOT NULL,
"saved_timestamp" TEXT NOT NULL,
"order_statement" TEXT NOT NULL,
"recoder_doctor_id" INTEGER NOT NULL,
FOREIGN KEY("recoder_doctor_id") REFERENCES "tb_doctor"("doctor_id"),
PRIMARY KEY("medical_order_id" AUTOINCREMENT)
);
INSERT INTO tb_employee("name", "type_job", "login_username", "login_password", "mobile_phone_num_1", "mobile_phone_num_2") values
("박광현", 2, "qqq", "1234", "010-1234-5678","-"),
("노우현", 1, "qq", "1234", "010-2222-5678","-"),
("주혜인", 2, "q123", "1234", "010-4422-5678","-");
INSERT INTO tb_chat_room("created_time") values ("2023-07-22 09:00:00"),("2023-07-21 21:00:00");
INSERT INTO tb_employee_chat_room("chat_room_id","employee_id") values (1, 1),
(1, 2),
(2, 1),
(2, 3);
INSERT INTO tb_message("sender_employee_id","chat_room_id","contents","is_confirmed") values
(1, 1, "ER 김철수님 NRS 7점 chest pain 호소합니다.", 0),
(1, 1, "처방주세요", 0),
(2, 1, "알겠습니다. 처방대로 주세요, 금방 가겠습니다.", 0),
(2, 2, "EDTA 보틀좀 빌려주세요", 0),
(1, 2, "우리도 없어요 죄송", 0);
INSERT INTO tb_nurse('employee_id', 'assigned_ward_id') values
(1, 9);
INSERT INTO tb_doctor('employee_id', 'assigned_department_id') values
(2, 3);
INSERT INTO tb_department("name", "job_category") VALUES
("응급의학과", 1),("신경외과", 1), ("흉부외과", 1), ("외과", 1), ("내과", 1), ("소아청소년과", 1),
("7A",2),("9B",2),("ER",2),("MICU",2),("101W",2),("EICU",2),
("원무과",3),("보험심사팀",3);
INSERT INTO tb_bed_of_ward("assigned_ward_id", "additional_info", "viewing_bed_name") VALUES
(9, "소생실", "CR1"),
(9, "소생실", "CR2"),
(9, "중증구역", "IC1"),
(9, "중증구역", "IC2"),
(9, "중증구역", "IC3"),
(9, "중증구역", "IC4"),
(9, "중증구역", "IC5"),
(9, "중증구역", "IC6"),
(9, "중증구역", "IC7"),
(9, "중증구역", "IC8"),
(9, "중증구역", "IC9"),
(9, "중증구역", "IC10"),
(9, "중증구역", "IC11"),
(9, "중증구역", "IC12"),
(9, "경증구역", "TR");
INSERT INTO tb_patient("birth_date", "name","sex", "ssn", "address", "type_insurance", "using_bed_id", "register_number", "assigned_doctor_id", "assigned_nurse_id") VALUES
('1958-06-08', '김철수', 'M', '580608-1354644', '경기도 수원시 언저리 어딘가', '건강보험', 1, 486124, 1, 1);
INSERT INTO tb_emergency_nurse_record(
"onset_time",
"register_id",
"ktas_id",
"cheif_complain",
"description",
"recorder_nurse_id",
"saved_time",
"responser",
"memo") values
('2022-07-18',486124, 'AICAB', '가슴이 답답해요',
"내원전 2시간 전, 속 울렁거림, 가슴 답답함 느껴졌으나 시간 지나니 괜찮아짐, 식사 후 집으로 귀가하던 중 가슴 부여잡은 채 쓰러진 것 행인이 목격,\n 119 신고로 이송됨 이송 중에 의식 깨어 현재는 alert하나 흉통(NRS>7) 계속 호소중임",
1, "2023-07-20 13:11:22", "환자 본인", "- 보호자 대기(-) / 연락(+)\n- Chest Pain NRS 6점 -> noti(+) /처방(-)\n- ECG: ST Elevation r/o ischemic HF");
INSERT INTO tb_medical_order (
"patient_register_id",
"saved_timestamp",
"order_statement",
"recoder_doctor_id"
) values
(486124, "2023-07-18 02:23:44", "1. check v/s q 1hr", 1),
(486124, "2023-07-18 02:23:44", "2. restrict ABR", 1),
(486124, "2023-07-18 02:23:44", "3. Total NPO", 1),
(486124, "2023-07-18 02:23:44", "4. f/u BST q 6hr", 1),
(486124, "2023-07-18 02:23:44", "", 1),
(486124, "2023-07-18 02:23:44", "====== 수액 ========", 1),
(486124, "2023-07-18 02:23:44", "1. NS 1L/bag 100ml/hr", 1),
(486124, "2023-07-18 02:23:44", "", 1),
(486124, "2023-07-18 02:23:44", "====== PO ========", 1),
(486124, "2023-07-18 02:23:44", "1. Clopidrogel 300mg 6Tab", 1),
(486124, "2023-07-18 02:23:44", "2. Tigagreller 180mg 1Tab 둘 다 지금 복용해주세요", 1),
(486124, "2023-07-18 02:23:44", "", 1),
(486124, "2023-07-18 02:23:44", "====== Text Order =========", 1),
(486124, "2023-07-18 02:23:44", "보호자 대기 시켜주세요", 1),
(486124, "2023-07-18 02:23:44", "", 1),
(486124, "2023-07-18 02:23:44", "===== PCI Order ===========", 1),
(486124, "2023-07-18 02:23:44", "....", 1);
# chatroom.py
import datetime
from Domain.message import Message
from Domain.people._employee import Employee
class ChatRoom:
def __init__(self, chat_room_id, created_time=None, employee_list=None, message_list=None):
self.chat_room_id = chat_room_id
if created_time is not None:
self.created_time = self.date_time_converter(created_time)
else:
self.created_time = created_time
if employee_list is not None: # 리스트 초기화
self.employee_list = employee_list
else:
self.employee_list = list()
if message_list is not None:
self.message_list = message_list
else:
self.message_list = list()
def append_or_extend_message(self, value):
if isinstance(value, list):
assert isinstance(value[0], Message)
for m in value:
if m not in self.message_list:
self.message_list.append(m)
else:
assert isinstance(value, Message)
if value not in self.message_list:
self.message_list.append(value)
def get_messages(self):
result_message_list = [x for x in self.message_list if x.chat_room_id == self.chat_room_id]
return result_message_list
def get_date_str(self, format_=None):
if format_ is None:
return self.created_time.strftime('%Y-%m-%d %H:%M:%S')
else:
return self.created_time.strftime(format_)
def __str__(self):
return self.__repr__()
def __repr__(self):
return f"{self.__dict__}"
def to_dict_for_insert(self):
origin_dict = self.__dict__.copy()
origin_dict['created_time'] = self.created_time.strftime('%Y-%m-%d %H:%M:%S')
if self.chat_room_id is None:
del origin_dict['chat_room_id']
del origin_dict['employee_list']
del origin_dict['message_list']
return origin_dict
@staticmethod
def date_time_converter(created_time):
if isinstance(created_time, str):
result = datetime.datetime.strptime(created_time, '%Y-%m-%d %H:%M:%S')
return result
if isinstance(created_time, datetime.datetime):
return created_time
else:
raise 'datetime 확인불가'
- DB로 연결되어 동작되어있던 클라이언트 컨트롤러에서 떼어내어 Client 개체를 통해 동작하도록 구현하고 있음
- 오가는 데이터들이 많고, 다양한 자료형으로 구조되다보니 작업이 더딤
# 버그 수정한 json function
def decode_obj(self, o, **kwargs):
try:
dict_obj = super().decode(o, **kwargs)
except:
dict_obj = o
if 'user_talk_room_id' in dict_obj.keys():
return UserTalkRoom(dict_obj['user_talk_room_id'], dict_obj['user_id'], dict_obj['talk_room_id'])
elif 'user_id' in dict_obj.keys():
return User(dict_obj['user_id'], dict_obj['username'], dict_obj['password'], dict_obj['nickname'])
elif 'message_id' in dict_obj.keys():
temp_user = User(*dict_obj['user_obj'].values())
return Message(dict_obj['message_id'], dict_obj['sender_user_id'], dict_obj['talk_room_id'],
dict_obj['send_time_stamp'], dict_obj['contents'], dict_obj['long_contents_id'], temp_user)
elif 'talk_room_id' in dict_obj.keys():
return TalkRoom(dict_obj['talk_room_id'], dict_obj['talk_room_name'], dict_obj['open_time_stamp'])
elif 'contents_id' in dict_obj.keys():
return LongContents(dict_obj['contents_id'], dict_obj['contents_type'], dict_obj['long_text'],
dict_obj['image'])
해당 함수는 binary code된 json string을 dictionary로 구조화한 뒤 key를 통해 어떤 클래스로 반환할지 결정하는 핸들러이다.
다만 str으로 이미 decode된 경우엔 에러가 난다. try except 문을 통해 직접 parameter로 입력된 object가 binary상태인지, 이미 converting 된 상태인지 구별하도록 간단하게 처리했다.
FramelessWindow를 사용하다보면, 프로그램이 최상단에 띄워지면서 가려지게 된다. 그러면서 기존에 편리하게 사용하던 QMessagebox들이 가려져서 보이지 않게 되는 문제가 발생한다. 이를 해결하기 위해 QDialog를 상속받는 NoFrameMessageBox를 새로 선언하여 작성하였다.