diff --git a/app/models/measurement.py b/app/models/measurement.py index b9f17cb01ef4e371cc4bffdd6586ea894cc6432e..b5bb5f00f9eb321701c68d34f64f777aafbfdd77 100644 --- a/app/models/measurement.py +++ b/app/models/measurement.py @@ -7,9 +7,10 @@ class PhysiologicalMeasurement(db.Model): id = db.Column('measurementID', db.Integer, primary_key=True) encounter_id = db.Column('encounterId', db.Integer, db.ForeignKey('encounters.encounterId'), nullable=False) + patient_id = db.Column('patientID', db.String(20), db.ForeignKey('patients.patientID'), nullable=False) # Các trường từ schema MySQL - measurementDateTime = db.Column(db.DateTime) + measurementDateTime = db.Column(db.DateTime, default=datetime.utcnow) end_tidal_co2 = db.Column(db.Float) feed_vol = db.Column(db.Float) feed_vol_adm = db.Column(db.Float) @@ -26,5 +27,16 @@ class PhysiologicalMeasurement(db.Model): tidal_vol_kg = db.Column(db.Float) tidal_vol_spon = db.Column(db.Float) + # Trường bổ sung cho mẫu mới + temperature = db.Column(db.Float) + heart_rate = db.Column(db.Integer) + respiratory_rate = db.Column(db.Integer) + blood_pressure_systolic = db.Column(db.Integer) + blood_pressure_diastolic = db.Column(db.Integer) + oxygen_saturation = db.Column(db.Float) + bmi = db.Column(db.Float) + tidal_volume = db.Column(db.Float) + notes = db.Column(db.Text) + def __repr__(self): return f'<PhysiologicalMeasurement {self.id} for Encounter {self.encounter_id}>' \ No newline at end of file diff --git a/app/models/patient.py b/app/models/patient.py index 90a1b69c69b0326cedfdc85d5a7ab0bb9f9265cf..60a06ef83dda20a014dab1840467959e36ebfecf 100644 --- a/app/models/patient.py +++ b/app/models/patient.py @@ -6,7 +6,7 @@ class Patient(db.Model): """Patient model containing basic patient information""" __tablename__ = 'patients' - id = db.Column('patientID', db.Integer, primary_key=True) + id = db.Column('patientID', db.String(20), primary_key=True) firstName = db.Column(db.String(50)) lastName = db.Column(db.String(50)) age = db.Column(db.Integer) @@ -54,7 +54,7 @@ class Encounter(db.Model): __tablename__ = 'encounters' id = db.Column('encounterId', db.Integer, primary_key=True) - patient_id = db.Column('patientID', db.Integer, db.ForeignKey('patients.patientID'), nullable=False) + patient_id = db.Column('patientID', db.String(20), db.ForeignKey('patients.patientID'), nullable=False) # Encounter details admissionDateTime = db.Column(db.DateTime, nullable=False) @@ -73,4 +73,9 @@ class Encounter(db.Model): updatedAt = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) def __repr__(self): - return f'<Encounter {self.id}>' \ No newline at end of file + return f'<Encounter {self.id}>' + + @property + def admission_date(self): + """Thuộc tính ảo để tương thích với code cũ. Trả về giá trị của admissionDateTime""" + return self.admissionDateTime \ No newline at end of file diff --git a/app/models/procedure.py b/app/models/procedure.py index cd60c87c2084f469c93da8b8c898a1e13a6597ec..5ffe1b7311f9f0d0f1ccb9019567eaf37da091eb 100644 --- a/app/models/procedure.py +++ b/app/models/procedure.py @@ -7,6 +7,7 @@ class Procedure(db.Model): id = db.Column('procedureID', db.Integer, primary_key=True) encounter_id = db.Column('encounterId', db.Integer, db.ForeignKey('encounters.encounterId'), nullable=False) + patient_id = db.Column('patientID', db.String(20), db.ForeignKey('patients.patientID'), nullable=False) # Procedure details procedureDateTime = db.Column(db.DateTime, nullable=False) diff --git a/app/models/referral.py b/app/models/referral.py index 94310bbbdf911291f4ee8b53912c72e61be8d775..f576e915af677130a3a960044e149b290c074aff 100644 --- a/app/models/referral.py +++ b/app/models/referral.py @@ -8,6 +8,7 @@ class Referral(db.Model): id = db.Column('referralID', db.Integer, primary_key=True) encounter_id = db.Column('encounterId', db.Integer, db.ForeignKey('encounters.encounterId'), nullable=False) + patient_id = db.Column('patientID', db.String(20), db.ForeignKey('patients.patientID'), nullable=False) # Referral fields from schema is_ml_recommended = db.Column(db.Boolean, default=False) diff --git a/app/models/report.py b/app/models/report.py index 0f9f0ba48a1112beb9abcd1c80da90c71d49077c..cc424d88ac5d845b42b8e57177d81ef4b5be6391 100644 --- a/app/models/report.py +++ b/app/models/report.py @@ -8,6 +8,7 @@ class Report(db.Model): id = db.Column('reportID', db.Integer, primary_key=True) user_id = db.Column('userID', db.Integer, db.ForeignKey('users.userID'), nullable=False) + patient_id = db.Column('patientID', db.String(20), db.ForeignKey('patients.patientID'), nullable=False) report_date = db.Column('reportDateTime', db.DateTime, nullable=False) report_title = db.Column('reportTitle', db.String(100), nullable=False) report_content = db.Column('reportContent', db.Text) diff --git a/app/routes/patients.py b/app/routes/patients.py index 77752d6695589d45341e8c1b4eeb18a9432a3bae..a9aa61176d57deac4d2a715912a79e1937c6a386 100644 --- a/app/routes/patients.py +++ b/app/routes/patients.py @@ -87,12 +87,12 @@ def patient_detail(patient_id): # Get latest measurements latest_measurement = PhysiologicalMeasurement.query.filter_by(patient_id=patient.id).order_by( - desc(PhysiologicalMeasurement.measurement_date) + desc(PhysiologicalMeasurement.measurementDateTime) ).first() # Get patient's measurements history measurements = PhysiologicalMeasurement.query.filter_by(patient_id=patient.id).order_by( - PhysiologicalMeasurement.measurement_date + PhysiologicalMeasurement.measurementDateTime ).all() # Get patient's referrals @@ -112,7 +112,7 @@ def patient_detail(patient_id): # Get patient's encounters encounters = Encounter.query.filter_by(patient_id=patient.id).order_by( - desc(Encounter.admission_date) + desc(Encounter.admissionDateTime) ).all() return render_template( @@ -202,7 +202,7 @@ def new_measurement(patient_id): bmi=float(request.form.get('bmi')) if request.form.get('bmi') else None, fio2=float(request.form.get('fio2')) if request.form.get('fio2') else None, tidal_volume=float(request.form.get('tidal_volume')) if request.form.get('tidal_volume') else None, - measurement_date=datetime.now(), + measurementDateTime=datetime.now(), notes=request.form.get('notes') ) diff --git a/app/routes/report.py b/app/routes/report.py index e289a9197a4030c7d9816d8e04901408d1c271bd..8456789fa5a6c8f1f5c7da0c8ee762ba0e05a425 100644 --- a/app/routes/report.py +++ b/app/routes/report.py @@ -330,7 +330,7 @@ def generate(): bmi_category, func.count().label('count') ).filter( - PhysiologicalMeasurement.measurement_date.between(start_date, end_date) + PhysiologicalMeasurement.measurementDateTime.between(start_date, end_date) ).group_by(bmi_category).all() # BMI statistics @@ -508,7 +508,7 @@ def create_stats_report(report_type): bmi_category, func.count().label('count') ).filter( - PhysiologicalMeasurement.measurement_date.between(start_date, end_date) + PhysiologicalMeasurement.measurementDateTime.between(start_date, end_date) ).group_by(bmi_category).all() # BMI statistics diff --git a/app/templates/patient_detail.html b/app/templates/patient_detail.html index 9f5f6c0a09111b18f8e40a10ecd417ae9c968c3a..7d27fe5bb64312a3504865a8f0404415c60b6072 100644 --- a/app/templates/patient_detail.html +++ b/app/templates/patient_detail.html @@ -11,7 +11,7 @@ <ol class="flex items-center space-x-4"> <li> <div> - <a href="{{ url_for('routes.dashboard') }}" class="text-gray-400 hover:text-gray-500 transition duration-150"> + <a href="{{ url_for('dashboard.index') }}" class="text-gray-400 hover:text-gray-500 transition duration-150"> <i class="fas fa-home"></i> <span class="sr-only">Dashboard</span> </a> @@ -20,7 +20,7 @@ <li> <div class="flex items-center"> <i class="fas fa-chevron-right text-gray-400 text-sm"></i> - <a href="{{ url_for('routes.patients') }}" class="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700 transition duration-150">Bệnh nhân</a> + <a href="{{ url_for('patients.index') }}" class="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700 transition duration-150">Bệnh nhân</a> </div> </li> <li> diff --git a/app/templates/upload.html b/app/templates/upload.html index d12ca9a39ac52b62f2dc3fc6ebf31ebb3571614f..b4e2b5f7c49a772c5765b2dfa9e7993ba9ec23df 100644 --- a/app/templates/upload.html +++ b/app/templates/upload.html @@ -20,7 +20,7 @@ <div class="lg:col-span-2"> <div class="bg-white shadow rounded-lg transition-all duration-300 hover:shadow-lg animate-fade-in"> <div class="px-4 py-5 sm:p-6"> - <form action="{{ url_for('routes.upload') }}" method="post" enctype="multipart/form-data" id="uploadForm"> + <form action="{{ url_for('upload.index') }}" method="post" enctype="multipart/form-data" id="uploadForm"> <div class="space-y-6"> <div> <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Tải lên tệp CSV</h3> diff --git a/init_database.py b/init_database.py index 58438a549a0a4a733570751579d4ab693caa452b..be6e3549f7545503e530f0346cb68d15e1f26196 100644 --- a/init_database.py +++ b/init_database.py @@ -65,9 +65,176 @@ def init_tables_and_admin(): app = create_app() with app.app_context(): - # Tạo tất cả bảng từ các mô hình + # Xóa các bảng hiện có để tạo lại với cấu trúc mới + try: + # Xóa các bảng theo thứ tự để tránh lỗi khóa ngoại + db.session.execute(db.text("SET FOREIGN_KEY_CHECKS = 0")) + + # Xóa các bảng có phụ thuộc + db.session.execute(db.text("DROP TABLE IF EXISTS physiologicalmeasurements")) + db.session.execute(db.text("DROP TABLE IF EXISTS procedures")) + db.session.execute(db.text("DROP TABLE IF EXISTS referrals")) + db.session.execute(db.text("DROP TABLE IF EXISTS reports")) + db.session.execute(db.text("DROP TABLE IF EXISTS encounters")) + + # Xóa bảng chính + db.session.execute(db.text("DROP TABLE IF EXISTS patients")) + + db.session.execute(db.text("SET FOREIGN_KEY_CHECKS = 1")) + db.session.commit() + print("Dropped existing tables to recreate schema.") + except Exception as e: + db.session.rollback() + print(f"Error dropping tables: {str(e)}") + + # Tạo tất cả bảng từ các mô hình với cấu trúc mới db.create_all() - print("All database tables created successfully.") + print("All database tables created successfully with new schema.") + + # Thêm các trường cần thiết vào bảng uploadedfiles + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN original_filename VARCHAR(256) DEFAULT ''")) + db.session.commit() + print("Added original_filename column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN file_size INT DEFAULT 0")) + db.session.commit() + print("Added file_size column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN file_type VARCHAR(64) DEFAULT 'csv'")) + db.session.commit() + print("Added file_type column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN delimiter VARCHAR(10) DEFAULT ','")) + db.session.commit() + print("Added delimiter column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN encoding VARCHAR(20) DEFAULT 'utf-8'")) + db.session.commit() + print("Added encoding column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN status VARCHAR(20) DEFAULT 'pending'")) + db.session.commit() + print("Added status column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN process_start DATETIME NULL")) + db.session.commit() + print("Added process_start column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN process_end DATETIME NULL")) + db.session.commit() + print("Added process_end column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN total_records INT DEFAULT 0")) + db.session.commit() + print("Added total_records column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN processed_records INT DEFAULT 0")) + db.session.commit() + print("Added processed_records column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN error_records INT DEFAULT 0")) + db.session.commit() + print("Added error_records column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN error_details TEXT NULL")) + db.session.commit() + print("Added error_details column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN process_referrals BOOLEAN DEFAULT 0")) + db.session.commit() + print("Added process_referrals column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN description TEXT NULL")) + db.session.commit() + print("Added description column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN notes TEXT NULL")) + db.session.commit() + print("Added notes column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP")) + db.session.commit() + print("Added created_at column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")) + db.session.commit() + print("Added updated_at column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") + + try: + db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN upload_date DATETIME DEFAULT CURRENT_TIMESTAMP")) + db.session.commit() + print("Added upload_date column to uploadedfiles table.") + except Exception as e: + db.session.rollback() + print(f"Note: {str(e)}") # Thêm trường status vào bảng reports nếu chưa tồn tại try: diff --git a/requirements.txt b/requirements.txt index 82778b9ab5fec19a16e6384bd363c0105cee16c2..10cbecb7b841f5232b5dcaa4b996ac3a247a117a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -# Web framework +# Web framework và các extension Flask==2.2.3 Flask-SQLAlchemy==3.0.3 Flask-Migrate==4.0.4 @@ -6,12 +6,17 @@ Flask-Login==0.6.2 Flask-Bcrypt==1.0.1 Flask-WTF==1.1.1 WTForms==3.0.1 +Jinja2==3.1.2 +itsdangerous==2.1.2 +click==8.1.3 +MarkupSafe==2.1.2 email-validator==2.0.0 # Database mysqlclient==2.1.1 SQLAlchemy==2.0.4 alembic==1.10.2 +mysql-connector-python==8.0.32 # Data processing and visualization pandas==1.5.3 @@ -20,6 +25,14 @@ matplotlib==3.7.1 openpyxl==3.1.2 Pillow==9.5.0 reportlab==4.0.0 +seaborn==0.12.2 +plotly==5.14.1 + +# File handling +pyexcel==0.7.0 +pyexcel-xlsx==0.6.0 +pypdf2==3.0.1 +XlsxWriter==3.0.9 # Utilities python-dotenv==1.0.0 @@ -31,6 +44,13 @@ requests==2.28.2 pdfkit==1.0.0 xlsxwriter==3.0.9 beautifulsoup4==4.12.0 +lxml==4.9.2 +markdown==3.4.1 +PyYAML==6.0 +python-dateutil==2.8.2 +six==1.16.0 +wrapt==1.15.0 +unicodecsv==0.14.1 # Development & Testing pytest==7.3.1 @@ -38,3 +58,6 @@ pytest-flask==1.2.0 coverage==7.2.2 black==23.1.0 flake8==6.0.0 +isort==5.12.0 +autopep8==2.0.2 +mypy==1.1.1