diff --git a/database/create_survey_tables.sql b/database/create_survey_tables.sql new file mode 100644 index 0000000..4377001 --- /dev/null +++ b/database/create_survey_tables.sql @@ -0,0 +1,70 @@ +-- 환자 사전 문진표 시스템 테이블 + +-- 1. 문진 메인 테이블 +CREATE TABLE IF NOT EXISTS patient_surveys ( + survey_id INTEGER PRIMARY KEY AUTOINCREMENT, + patient_id INTEGER, + survey_token TEXT UNIQUE NOT NULL, -- QR/링크용 고유 토큰 + survey_date DATE DEFAULT (date('now')), + status TEXT DEFAULT 'PENDING' CHECK(status IN ('PENDING', 'IN_PROGRESS', 'COMPLETED', 'REVIEWED')), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + completed_at DATETIME, + reviewed_at DATETIME, + reviewed_by TEXT, + notes TEXT, + FOREIGN KEY (patient_id) REFERENCES patients(patient_id) +); + +CREATE INDEX IF NOT EXISTS idx_survey_token ON patient_surveys(survey_token); +CREATE INDEX IF NOT EXISTS idx_survey_patient ON patient_surveys(patient_id); + +-- 2. 문진 응답 테이블 +CREATE TABLE IF NOT EXISTS survey_responses ( + response_id INTEGER PRIMARY KEY AUTOINCREMENT, + survey_id INTEGER NOT NULL, + category TEXT NOT NULL, -- 카테고리 (TEMPERATURE, BODY_TEMP, DIGESTION, etc) + question_code TEXT NOT NULL, -- 질문 코드 + question_text TEXT, -- 질문 내용 + answer_value TEXT, -- 응답 값 (JSON 또는 TEXT) + answer_type TEXT, -- SINGLE, MULTIPLE, TEXT, RANGE + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (survey_id) REFERENCES patient_surveys(survey_id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS idx_response_survey ON survey_responses(survey_id); +CREATE INDEX IF NOT EXISTS idx_response_category ON survey_responses(category); + +-- 3. 문진 템플릿 테이블 (질문 정의) +CREATE TABLE IF NOT EXISTS survey_templates ( + template_id INTEGER PRIMARY KEY AUTOINCREMENT, + category TEXT NOT NULL, + category_name TEXT NOT NULL, -- 카테고리 한글명 + question_code TEXT NOT NULL UNIQUE, + question_text TEXT NOT NULL, + question_subtext TEXT, -- 부가 설명 + input_type TEXT NOT NULL CHECK(input_type IN ('RADIO', 'CHECKBOX', 'RANGE', 'TEXT', 'TEXTAREA', 'NUMBER')), + options TEXT, -- JSON 배열 형태의 선택지 + is_required INTEGER DEFAULT 1, + sort_order INTEGER DEFAULT 0, + is_active INTEGER DEFAULT 1, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_template_category ON survey_templates(category); +CREATE INDEX IF NOT EXISTS idx_template_active ON survey_templates(is_active); + +-- 4. 문진 진행 상태 추적 테이블 +CREATE TABLE IF NOT EXISTS survey_progress ( + progress_id INTEGER PRIMARY KEY AUTOINCREMENT, + survey_id INTEGER NOT NULL, + category TEXT NOT NULL, + total_questions INTEGER DEFAULT 0, + answered_questions INTEGER DEFAULT 0, + is_completed INTEGER DEFAULT 0, + last_updated DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (survey_id) REFERENCES patient_surveys(survey_id) ON DELETE CASCADE, + UNIQUE(survey_id, category) +); + +CREATE INDEX IF NOT EXISTS idx_progress_survey ON survey_progress(survey_id); diff --git a/database/insert_survey_templates.py b/database/insert_survey_templates.py new file mode 100644 index 0000000..6417fb6 --- /dev/null +++ b/database/insert_survey_templates.py @@ -0,0 +1,596 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +문진표 템플릿 데이터 입력 스크립트 +""" +import sqlite3 +import json + +# 문진 항목 데이터 +survey_data = [ + # 1. 체온 감각 (TEMPERATURE) + { + 'category': 'TEMPERATURE', + 'category_name': '체온 감각', + 'questions': [ + { + 'code': 'COLD_SENSITIVITY', + 'text': '추위를 타시나요?', + 'type': 'RADIO', + 'options': ['심하게 탄다', '타는 편', '약간 탄다', '안탄다', '겨울이 싫다'], + 'required': True, + 'order': 1 + }, + { + 'code': 'HEAT_SENSITIVITY', + 'text': '더위를 타시나요?', + 'type': 'RADIO', + 'options': ['심하게 탄다', '타는 편', '약간 탄다', '안탄다', '선풍기/에어컨 바람이 싫다'], + 'required': True, + 'order': 2 + }, + { + 'code': 'SWEAT_AMOUNT', + 'text': '땀을 흘리는 정도는?', + 'type': 'RADIO', + 'options': ['건조하다/없다', '약간', '보통', '많다', '아주 많다'], + 'required': True, + 'order': 3 + }, + { + 'code': 'SWEAT_LOCATION', + 'text': '땀이 나는 부위는? (복수선택)', + 'type': 'CHECKBOX', + 'options': ['얼굴', '몸전체', '손발', '하체', '머리', '이마', '겨드랑이'], + 'required': False, + 'order': 4 + }, + { + 'code': 'SWEAT_TIMING', + 'text': '주로 언제 땀이 나나요? (복수선택)', + 'type': 'CHECKBOX', + 'options': ['잘 때', '식사할 때', '긴장할 때', '여름에', '일할 때', '수시로'], + 'required': False, + 'order': 5 + } + ] + }, + + # 2. 신체 부위별 온도 (BODY_TEMP) + { + 'category': 'BODY_TEMP', + 'category_name': '신체 부위별 온도', + 'questions': [ + { + 'code': 'HAND_TEMP', + 'text': '손의 온도는?', + 'type': 'RADIO', + 'options': ['매우 차다', '약간 차다', '보통', '따뜻하다', '뜨겁다', '화끈거림', '저림', '쥐'], + 'required': True, + 'order': 1 + }, + { + 'code': 'FOOT_TEMP', + 'text': '발의 온도는?', + 'type': 'RADIO', + 'options': ['매우 차다', '약간 차다', '보통', '따뜻하다', '뜨겁다', '화끈거림', '저림', '쥐'], + 'required': True, + 'order': 2 + }, + { + 'code': 'UPPER_ABDOMEN_TEMP', + 'text': '윗배의 온도는?', + 'type': 'RADIO', + 'options': ['매우 차다', '약간 차다', '보통', '따뜻하다', '뜨겁다', '시림'], + 'required': True, + 'order': 3 + }, + { + 'code': 'LOWER_ABDOMEN_TEMP', + 'text': '아랫배의 온도는?', + 'type': 'RADIO', + 'options': ['매우 차다', '약간 차다', '보통', '따뜻하다', '뜨겁다', '복부비만'], + 'required': True, + 'order': 4 + }, + { + 'code': 'WHOLE_BODY_TEMP', + 'text': '몸 전체의 느낌은?', + 'type': 'CHECKBOX', + 'options': ['매우 차다', '약간 차다', '보통', '따뜻하다', '뜨겁다', '무겁다', '아프다', '부종'], + 'required': True, + 'order': 5 + } + ] + }, + + # 3. 식성 및 소화 (DIGESTION) + { + 'category': 'DIGESTION', + 'category_name': '식성 및 소화', + 'questions': [ + { + 'code': 'FOOD_TEMP_PREF', + 'text': '음식 온도 선호는?', + 'type': 'RADIO', + 'options': ['찬 것', '시원한 것', '보통', '따뜻한 것', '뜨거운 것', '모두'], + 'required': True, + 'order': 1 + }, + { + 'code': 'TASTE_PREF', + 'text': '맛 선호는?', + 'type': 'RADIO', + 'options': ['신 것', '단 것', '매운 것', '짠 것', '쓴 것', '담백한 것', '모두'], + 'required': True, + 'order': 2 + }, + { + 'code': 'FOOD_HABITS', + 'text': '식습관 (복수선택)', + 'subtext': '주 1회 이상 섭취하는 것을 선택하세요', + 'type': 'CHECKBOX', + 'options': ['된장', '채식', '육류', '해물', '커피', '술', '담배'], + 'required': False, + 'order': 3 + }, + { + 'code': 'WATER_INTAKE', + 'text': '물을 마시는 정도는?', + 'type': 'RADIO', + 'options': ['많이 마심', '자주', '보통', '거의 안마심'], + 'required': True, + 'order': 4 + }, + { + 'code': 'APPETITE', + 'text': '식욕은 어떠신가요?', + 'type': 'RADIO', + 'options': ['없다', '별로', '보통', '좋다', '왕성', '아침 생략'], + 'required': True, + 'order': 5 + }, + { + 'code': 'MEAL_AMOUNT', + 'text': '식사량은?', + 'type': 'RADIO', + 'options': ['적다', '보통', '많다', '1공기 이하', '1공기 이상', '일정치 않다', '저녁에 많이'], + 'required': True, + 'order': 6 + }, + { + 'code': 'DIGESTION_POWER', + 'text': '소화력은?', + 'type': 'RADIO', + 'options': ['잘 된다', '보통', '약하다', '잘 안됨', '잘 체한다'], + 'required': True, + 'order': 7 + }, + { + 'code': 'INDIGESTION_SYMPTOMS', + 'text': '소화불량 증상 (복수선택)', + 'type': 'CHECKBOX', + 'options': ['막힌듯함', '답답함', '걸린듯함', '더부룩함', '그득함', '속쓰림', + '헛배부름', '가스참', '느글거림', '트림', '구토', '헛구역', '방귀', + '꾸룩소리남', '명치아픔', '복통', '딸꾹질', '하품'], + 'required': False, + 'order': 8 + } + ] + }, + + # 4. 대소변 (EXCRETION) + { + 'category': 'EXCRETION', + 'category_name': '대소변', + 'questions': [ + { + 'code': 'STOOL_FREQUENCY', + 'text': '대변 빈도는?', + 'subtext': '일 _회 / 매일 / 아침 / 불규칙', + 'type': 'TEXT', + 'required': True, + 'order': 1 + }, + { + 'code': 'STOOL_CONDITION', + 'text': '대변 상태는? (복수선택)', + 'type': 'CHECKBOX', + 'options': ['변비', '된편', '굵다', '토끼똥', '설사', '물변', '보통', '가늘다', '퍼진다', '냄새심함'], + 'required': True, + 'order': 2 + }, + { + 'code': 'STOOL_DIFFICULTY', + 'text': '배변 시 불편함은? (복수선택)', + 'type': 'CHECKBOX', + 'options': ['잘 나옴', '잘 안나옴', '시원치 않다', '오래봄', '힘들게 나옴', + '조금 나옴', '남아있는 듯함', '조금씩 자주', '지림', '못 참음'], + 'required': False, + 'order': 3 + }, + { + 'code': 'URINE_FREQUENCY_DAY', + 'text': '낮 소변 빈도는?', + 'subtext': '낮에 몇 시간마다 1회?', + 'type': 'NUMBER', + 'required': True, + 'order': 4 + }, + { + 'code': 'URINE_FREQUENCY_NIGHT', + 'text': '밤 소변 빈도는?', + 'subtext': '자다가 몇 회?', + 'type': 'NUMBER', + 'required': True, + 'order': 5 + }, + { + 'code': 'URINE_FREQUENCY_LEVEL', + 'text': '전체적인 소변 빈도는?', + 'type': 'RADIO', + 'options': ['거의 안봄', '가끔', '보통', '자주', '매우 자주', '밤에 오줌 싼다'], + 'required': True, + 'order': 6 + }, + { + 'code': 'URINE_COLOR', + 'text': '소변 색은?', + 'type': 'RADIO', + 'options': ['붉다', '노랗다', '보통', '탁하다', '맑다', '커피색'], + 'required': True, + 'order': 7 + }, + { + 'code': 'URINE_ABNORMAL', + 'text': '소변 이상 증상 (복수선택)', + 'type': 'CHECKBOX', + 'options': ['거품이 남', '기름이 뜸', '단내남', '뿌옇다', '정액이 나옴'], + 'required': False, + 'order': 8 + } + ] + }, + + # 5. 수면 (SLEEP) + { + 'category': 'SLEEP', + 'category_name': '수면', + 'questions': [ + { + 'code': 'SLEEP_HOURS', + 'text': '하루 수면 시간은?', + 'subtext': '예: 7시간', + 'type': 'NUMBER', + 'required': True, + 'order': 1 + }, + { + 'code': 'SLEEP_TIME', + 'text': '수면 시간대는?', + 'subtext': '예: 23시 ~ 06시', + 'type': 'TEXT', + 'required': False, + 'order': 2 + }, + { + 'code': 'SLEEP_SATISFACTION', + 'text': '수면이 충분한가요?', + 'type': 'RADIO', + 'options': ['잠 부족', '잠 충분'], + 'required': True, + 'order': 3 + }, + { + 'code': 'SLEEP_QUALITY', + 'text': '수면 상태는? (복수선택)', + 'type': 'CHECKBOX', + 'options': ['잘잠', '잘못잠', '거의 못잠', '가끔 못잠', '뒤척임', '곧 잔다', + '잠들기 어렵다', '깊이 잠', '얕은 잠', '잠귀 밝음', '잘 깸', '깨면 안옴'], + 'required': True, + 'order': 4 + }, + { + 'code': 'DREAM_FREQUENCY', + 'text': '꿈을 꾸는 빈도는?', + 'type': 'RADIO', + 'options': ['밤새 꿈', '자주 꾼다', '가끔 꿈', '거의 없다', '안꾼다', '잠꼬대'], + 'required': True, + 'order': 5 + }, + { + 'code': 'DREAM_TYPE', + 'text': '꾸는 꿈의 종류는? (복수선택)', + 'type': 'CHECKBOX', + 'options': ['무서운 꿈', '죽은 사람 꿈', '쫓기는 꿈', '개꿈', '기억 안남', '기억 남'], + 'required': False, + 'order': 6 + } + ] + }, + + # 6. 순환 및 정신 (CIRCULATION) + { + 'category': 'CIRCULATION', + 'category_name': '순환 및 정신 증상', + 'questions': [ + { + 'code': 'HEART_SYMPTOMS', + 'text': '심장 관련 증상 (복수선택)', + 'type': 'CHECKBOX', + 'options': ['가슴뜀', '가슴답답', '가슴뻐근', '한숨쉼', '호흡곤란', '숨참', '뒷목뻐근'], + 'required': False, + 'order': 1 + }, + { + 'code': 'HEAT_FLASH', + 'text': '열이 달아오르는 증상이 있나요?', + 'subtext': '있다면 빈도를 알려주세요 (예: 일 3회, 주 2회)', + 'type': 'TEXT', + 'required': False, + 'order': 2 + }, + { + 'code': 'MENTAL_SYMPTOMS', + 'text': '정신적 증상 (복수선택)', + 'type': 'CHECKBOX', + 'options': ['잘 놀람', '불안', '초조', '우울', '비관', '신경질', '짜증', + '매사 귀찮다', '손떨림', '졸도', '가슴 막힌 듯', '조이는 듯', + '기억력 격감', '건망증', '현기증', '눈 피로감'], + 'required': False, + 'order': 3 + }, + { + 'code': 'FATIGUE_SYMPTOMS', + 'text': '피로 증상 (복수선택)', + 'type': 'CHECKBOX', + 'options': ['피로', '기운이 없다', '아침에 잘 못일어난다', '의욕이 없다', '무겁다'], + 'required': False, + 'order': 4 + } + ] + }, + + # 7. 여성 건강 (WOMEN_HEALTH) + { + 'category': 'WOMEN_HEALTH', + 'category_name': '여성 건강', + 'questions': [ + { + 'code': 'MARITAL_STATUS', + 'text': '결혼 상태는?', + 'type': 'RADIO', + 'options': ['미혼', '기혼'], + 'required': False, + 'order': 1 + }, + { + 'code': 'MARRIAGE_YEARS', + 'text': '결혼한 지 몇 년?', + 'type': 'NUMBER', + 'required': False, + 'order': 2 + }, + { + 'code': 'BIRTH_COUNT', + 'text': '출산 횟수', + 'type': 'NUMBER', + 'required': False, + 'order': 3 + }, + { + 'code': 'INFERTILITY_YEARS', + 'text': '불임 기간 (년)', + 'type': 'NUMBER', + 'required': False, + 'order': 4 + }, + { + 'code': 'MISCARRIAGE', + 'text': '유산 경험', + 'subtext': '자연유산 _회, 인공유산 _회', + 'type': 'TEXT', + 'required': False, + 'order': 5 + }, + { + 'code': 'MENSTRUAL_CYCLE', + 'text': '생리 주기는?', + 'subtext': '_일 간격', + 'type': 'NUMBER', + 'required': False, + 'order': 6 + }, + { + 'code': 'MENSTRUAL_REGULARITY', + 'text': '생리 규칙성은?', + 'type': 'RADIO', + 'options': ['정상', '부정확', '건넘', '중단', '폐경', '계속 나옴'], + 'required': False, + 'order': 7 + }, + { + 'code': 'MENSTRUAL_DURATION', + 'text': '생리 기간은?', + 'subtext': '_일간 (_일 많고 _일 적다)', + 'type': 'TEXT', + 'required': False, + 'order': 8 + }, + { + 'code': 'MENSTRUAL_AMOUNT', + 'text': '생리량은?', + 'type': 'RADIO', + 'options': ['너무 많다', '많다', '보통', '약간 적다', '아주 적다', '줄어듬', '늦어짐', '빨라짐'], + 'required': False, + 'order': 9 + }, + { + 'code': 'MENSTRUAL_COLOR', + 'text': '생리 색은? (복수선택)', + 'type': 'CHECKBOX', + 'options': ['검붉다', '검다', '일부 덩어리', '찌꺼기', '묽다'], + 'required': False, + 'order': 10 + }, + { + 'code': 'MENSTRUAL_PAIN', + 'text': '생리통은?', + 'type': 'RADIO', + 'options': ['없다', '약간', '심하다', '극심'], + 'required': False, + 'order': 11 + }, + { + 'code': 'MENSTRUAL_PAIN_TIMING', + 'text': '생리통 시기는?', + 'subtext': '생리 _일 부터 _일간', + 'type': 'TEXT', + 'required': False, + 'order': 12 + }, + { + 'code': 'MENSTRUAL_PAIN_LOCATION', + 'text': '생리통 부위 (복수선택)', + 'type': 'CHECKBOX', + 'options': ['아랫배', '허리', '허벅지', '가슴', '머리', '전신', '몸살', '과민'], + 'required': False, + 'order': 13 + }, + { + 'code': 'VAGINAL_DISCHARGE', + 'text': '냉대하는?', + 'type': 'RADIO', + 'options': ['없다', '약간', '많다', '심하다'], + 'required': False, + 'order': 14 + }, + { + 'code': 'DISCHARGE_SYMPTOMS', + 'text': '냉대하 증상 (복수선택)', + 'type': 'CHECKBOX', + 'options': ['투명', '누렇다', '희다', '묽다', '냄새', '악취', '가렵다'], + 'required': False, + 'order': 15 + } + ] + }, + + # 8. 피부 (SKIN) + { + 'category': 'SKIN', + 'category_name': '피부', + 'questions': [ + { + 'code': 'SKIN_COLOR', + 'text': '피부 색은?', + 'type': 'RADIO', + 'options': ['보통', '약간 황색', '약간 검다', '희다', '창백', '누렇다', '약간 붉다'], + 'required': True, + 'order': 1 + }, + { + 'code': 'SKIN_TYPE', + 'text': '피부 타입은?', + 'type': 'RADIO', + 'options': ['보통', '섬세', '얇다', '약간 두텁다', '두텁다', '지성', '중성', '건성'], + 'required': True, + 'order': 2 + } + ] + }, + + # 9. 성품/체질 (PERSONALITY) + { + 'category': 'PERSONALITY', + 'category_name': '성품 및 체질', + 'questions': [ + { + 'code': 'PERSONALITY_TYPE1', + 'text': '성격 유형 1 - 태양인 성향 (복수선택)', + 'type': 'CHECKBOX', + 'options': ['저돌적이다', '기세가 강하다', '남의 말을 잘 듣지 않는다', + '거침이 없다', '독불장군이다', '안하무인이다', '뚜렷하다', + '말과 행동 빠름', '음식을 빨리 먹음', '눈매 예리/날카롭다', + '부지런함', '적극적', '활동적', '소변 자주 본다', '일을 안미룬다', + '나다니기를 좋아함', '분명하다', '나서기 잘함', '질투가 심하다'], + 'required': False, + 'order': 1 + }, + { + 'code': 'PERSONALITY_TYPE2', + 'text': '성격 유형 2 - 태음인 성향 (복수선택)', + 'type': 'CHECKBOX', + 'options': ['눕기 좋아함', '엉덩이 무겁다', '느긋함', '땀이 많다', '과묵하다', + '사람 좋다', '무던하다', '부드럽다', '가정적이다', '씻기를 싫어함', + '원만', '정중', '은근', '꾸준함', '우유부단', '된장/쓴 것 좋아함'], + 'required': False, + 'order': 2 + }, + { + 'code': 'PERSONALITY_TYPE3', + 'text': '성격 유형 3 - 소양인/소음인 성향 (복수선택)', + 'type': 'CHECKBOX', + 'options': ['약해 보인다', '겁이 많음', '세심하다', '소심', '차분함', '자상함', + '연약', '영민', '잘 미룬다', '깐깐', '치밀', '궁리는 많으나 실행은 적음'], + 'required': False, + 'order': 3 + } + ] + } +] + +def insert_survey_templates(): + conn = sqlite3.connect('/root/kdrug/database/kdrug.db') + cursor = conn.cursor() + + # 기존 템플릿 삭제 + cursor.execute("DELETE FROM survey_templates") + + inserted_count = 0 + + for category_data in survey_data: + category = category_data['category'] + category_name = category_data['category_name'] + + for question in category_data['questions']: + cursor.execute(""" + INSERT INTO survey_templates + (category, category_name, question_code, question_text, question_subtext, + input_type, options, is_required, sort_order) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + """, ( + category, + category_name, + question['code'], + question['text'], + question.get('subtext'), + question['type'], + json.dumps(question.get('options', []), ensure_ascii=False), + 1 if question.get('required', False) else 0, + question['order'] + )) + inserted_count += 1 + + conn.commit() + + # 결과 확인 + cursor.execute(""" + SELECT category, category_name, COUNT(*) as cnt + FROM survey_templates + GROUP BY category + ORDER BY MIN(template_id) + """) + + results = cursor.fetchall() + + print(f"✅ 문진 템플릿 {inserted_count}개 항목 입력 완료\n") + print("카테고리별 질문 수:") + for row in results: + print(f" {row[0]:20s} ({row[1]:15s}): {row[2]:2d}개") + + conn.close() + return inserted_count + +if __name__ == '__main__': + insert_survey_templates() diff --git a/templates/survey.html b/templates/survey.html new file mode 100644 index 0000000..add0e91 --- /dev/null +++ b/templates/survey.html @@ -0,0 +1,881 @@ + + +
+ + +환자명: -
+문진표를 불러오는 중...
+