diff --git a/.gitignore b/.gitignore
index b1766a09d531676fa9226c667ed403e1d6bafb69..c34cb7a092fa70a4ad3728c46a35ad4ecc173e33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,7 +56,6 @@ local_settings.py
 db.sqlite3
 
 # Flask stuff:
-instance/
 .webassets-cache
 
 # Scrapy stuff:
@@ -124,9 +123,6 @@ dmypy.json
 out/
 .idea_modules/
 
-# Tệp cấu hình cá nhân
-instance/config.py
-
 # Tệp tạm thời
 *.swp
 *~
diff --git a/app/__init__.py b/app/__init__.py
index 929e985f92273c9a02bd8e30f349ed99f24c5ef0..397c934f5ef4e0f37fdcb51cd3a35c44cd2890d3 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -30,7 +30,6 @@ def create_app(config_class=None): # Có thể bỏ config_class hoặc dùng ch
     # --- Quan trọng: Đảm bảo các cấu hình cần thiết được thiết lập --- 
     # Nếu không có trong file config.py, cần có giá trị mặc định ở đây
     app.config.setdefault('SECRET_KEY', 'a_default_secret_key_change_me')
-    # Sử dụng giá trị mặc định giống trong instance/config.py hoặc một placeholder an toàn hơn
     app.config.setdefault('SQLALCHEMY_DATABASE_URI', 'mysql://root:MinhDZ3009@localhost/ccu') 
     app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', False)
     app.config.setdefault('UPLOAD_FOLDER', os.path.join(app.instance_path, 'uploads'))
diff --git a/app/models/uploaded_file.py b/app/models/uploaded_file.py
index 2230d4390dcf4087b062ab8998dc51eb5dbd7787..11cb090d31a27797c688e2a0fcd41f166f74cbdc 100644
--- a/app/models/uploaded_file.py
+++ b/app/models/uploaded_file.py
@@ -18,7 +18,7 @@ class UploadedFile(db.Model):
     file_encoding = db.Column(db.String(20), default='utf-8')
 
     # Status tracking
-    status = db.Column(db.String(20), default='pending')
+    status = db.Column(db.String(50), default='pending')
     process_start = db.Column(db.DateTime)
     process_end = db.Column(db.DateTime)
 
@@ -26,7 +26,7 @@ class UploadedFile(db.Model):
     total_records = db.Column(db.Integer, default=0)
     processed_records = db.Column(db.Integer, default=0)
     error_records = db.Column(db.Integer, default=0)
-    error_details = db.Column(db.Text)
+    error_details = db.Column(db.Text(length=16777215))
 
     process_referrals = db.Column(db.Boolean, default=False)
     description = db.Column(db.Text)
diff --git a/app/routes/patients.py b/app/routes/patients.py
index 429cc4d0d62112d6175041d2c29323b5d9786bb7..ffedbe5d48b10fd9f53fd1dacad51ff7854edccb 100644
--- a/app/routes/patients.py
+++ b/app/routes/patients.py
@@ -8,6 +8,7 @@ from app.models.referral import Referral
 from app.models.procedure import Procedure
 from app.models.report import Report
 from sqlalchemy import desc, or_, func
+from sqlalchemy.orm import joinedload
 from datetime import datetime
 from app import csrf
 
@@ -118,8 +119,10 @@ def patient_detail(patient_id):
         desc(Report.report_date)
     ).all()
     
-    # Get patient's encounters
-    encounters = Encounter.query.filter_by(patientID=patient_id).order_by(
+    # Get patient's encounters with dietitian info preloaded
+    encounters = Encounter.query.filter_by(patientID=patient_id).options(
+        joinedload(Encounter.dietitian)
+    ).order_by(
         desc(Encounter.admissionDateTime)
     ).all()
     
diff --git a/app/routes/upload.py b/app/routes/upload.py
index 6eaaad182e23aa21922956e8a055d5f4cb9e8465..517e0cb3cec535849ebf7d1015c2ae54c8183594 100644
--- a/app/routes/upload.py
+++ b/app/routes/upload.py
@@ -1,21 +1,29 @@
 import os
 import csv
 import io
-import pandas as pd
-from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, send_from_directory
+import json
+import uuid
+from datetime import datetime
+from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, send_from_directory, session
 from flask_login import login_required, current_user
 from werkzeug.utils import secure_filename
+from werkzeug.datastructures import FileStorage
 from app import db
 from app.models.uploaded_file import UploadedFile
 from app.models.patient import Patient, Encounter
 from app.models.measurement import PhysiologicalMeasurement
 from app.models.referral import Referral
-from app.utils.csv_handler import process_csv, validate_csv
-from datetime import datetime
+from app.utils.csv_handler import process_csv
 from sqlalchemy import desc
+from flask_wtf import FlaskForm
+from flask_wtf.csrf import generate_csrf, validate_csrf
 
 upload_bp = Blueprint('upload', __name__, url_prefix='/upload')
 
+# Tạo form rỗng để chứa CSRF token
+class UploadForm(FlaskForm):
+    pass
+
 def allowed_file(filename):
     return '.' in filename and \
            filename.rsplit('.', 1)[1].lower() in ['csv']
@@ -23,134 +31,160 @@ def allowed_file(filename):
 @upload_bp.route('/', methods=['GET', 'POST'])
 @login_required
 def index():
+    form = UploadForm()
+
+    # Lấy 3 bản ghi upload gần nhất để hiển thị ở sidebar
+    recent_uploads = UploadedFile.query.order_by(UploadedFile.upload_date.desc()).limit(3).all()
+
     if request.method == 'POST':
-        # Check if the post request has the file part
+        # Kiểm tra CSRF token
+        if not form.validate():
+            flash('CSRF token không hợp lệ. Vui lòng thử lại.', 'error')
+            return redirect(url_for('upload.index'))
+        
+        # Kiểm tra xem có file được gửi lên hay không
         if 'file' not in request.files:
-            flash('No file part', 'error')
+            flash('Không có file nào được chọn', 'error')
             return redirect(request.url)
         
         file = request.files['file']
         
-        # If the user does not select a file, the browser submits an empty file
+        # Nếu người dùng không chọn file
         if file.filename == '':
-            flash('No selected file', 'error')
+            flash('Không có file nào được chọn', 'error')
             return redirect(request.url)
         
-        if file and allowed_file(file.filename):
-            # Secure filename to prevent security issues
+        # Nếu file tồn tại và có đuôi là csv
+        if file and file.filename.lower().endswith('.csv'):
+            # Lưu file vào thư mục upload
             filename = secure_filename(file.filename)
+            unique_filename = f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{uuid.uuid4().hex}_{filename}"
             
-            # Create a unique filename
-            unique_filename = f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{filename}"
-            
-            # Ensure upload folder exists
-            upload_folder = current_app.config['UPLOAD_FOLDER']
+            # Tạo thư mục upload nếu chưa tồn tại
+            upload_folder = os.path.join(current_app.root_path, '..', 'uploads')
             os.makedirs(upload_folder, exist_ok=True)
             
-            # Save file to the upload folder
             file_path = os.path.join(upload_folder, unique_filename)
             file.save(file_path)
             
-            # Get file size
-            file_size = os.path.getsize(file_path)
+            # Lấy thông tin từ form
+            delimiter = request.form.get('delimiter', 'comma')
+            encoding = request.form.get('encoding', 'utf-8')
+            description = request.form.get('description', '')
+            process_referrals = 'process_referrals' in request.form
             
-            # Get form parameters
-            delimiter = {
+            delimiter_map = {
                 'comma': ',',
                 'semicolon': ';',
                 'tab': '\t'
-            }.get(request.form.get('delimiter', 'comma'), ',')
+            }
             
-            file_encoding = request.form.get('encoding', 'utf-8')
-            description = request.form.get('description', '')
-            process_referrals = True if request.form.get('process_referrals') else False
+            actual_delimiter = delimiter_map.get(delimiter, ',')
             
-            # Create uploaded file record
-            uploaded_file = UploadedFile(
-                filename=unique_filename,
+            # Tạo bản ghi lưu thông tin upload
+            upload_record = UploadedFile(
                 original_filename=filename,
-                file_path=file_path,
-                file_size=file_size,
-                uploaded_by=current_user.id,
-                delimiter=delimiter,
-                file_encoding=file_encoding,
-                process_referrals=process_referrals,
+                fileName=unique_filename,
+                filePath=file_path,
+                file_type='csv',
+                file_size=os.path.getsize(file_path),
+                file_encoding=encoding,
+                delimiter=actual_delimiter,
                 description=description,
-                status='pending'
+                user_id=current_user.userID,
+                upload_date=datetime.now(),
+                status='processing'
             )
             
-            db.session.add(uploaded_file)
-            db.session.commit()
-            
-            # Validate the CSV file format
-            validation_result = validate_csv(file_path, delimiter, file_encoding)
-            
-            if validation_result['valid']:
-                # Process the CSV in the background (in a real app, this would be a Celery/RQ task)
-                try:
-                    # Update file status to processing
-                    uploaded_file.status = 'processing'
-                    uploaded_file.process_start = datetime.now()
-                    db.session.commit()
-                    
-                    # Process the CSV file
-                    result = process_csv(uploaded_file.id)
-                    
-                    # Update file status based on processing result
-                    if result['success']:
-                        uploaded_file.status = 'completed'
-                        uploaded_file.total_records = result['total_records']
-                        uploaded_file.processed_records = result['processed_records']
-                        uploaded_file.error_records = result['error_records']
-                        uploaded_file.error_details = result.get('error_details', '')
-                        
-                        flash(f'File uploaded and processed successfully. {result["processed_records"]} records processed.', 'success')
+            try:
+                db.session.add(upload_record)
+                db.session.commit()
+                
+                # Xử lý file CSV
+                result = process_csv(
+                    uploaded_file_id=upload_record.id
+                )
+                
+                # Cập nhật trạng thái
+                upload_record.status = 'completed' if result.get('success') else 'failed'
+                if result.get('success'):
+                    upload_record.processed_records = result.get('processed_records', 0)
+                    upload_record.error_records = result.get('error_records', 0)
+                    upload_record.total_records = result.get('total_records', 0)
+                    if result.get('errors'):
+                        upload_record.error_details = json.dumps(result['errors'])
+                else:
+                    upload_record.error_details = json.dumps([{'row': 0, 'error': result.get('error', 'Unknown error')}])
+                
+                db.session.commit()
+                
+                if result.get('success'):
+                    processed_count = result.get('processed_records', 0)
+                    error_count = result.get('error_records', 0)
+                    if error_count > 0:
+                        # Hiển thị thông báo lỗi dễ hiểu cho người dùng
+                        error_details = result.get('error_details', '{}')
+                        try:
+                            error_json = json.loads(error_details)
+                            if 'summary' in error_json:
+                                # Đây là lỗi đã được tóm tắt
+                                flash(f"{error_json.get('summary')} {error_json.get('message', '')}", 'warning')
+                            else:
+                                flash(f'Đã tải lên và xử lý {processed_count} bản ghi, nhưng có {error_count} lỗi. Xem chi tiết trong lịch sử tải lên.', 'warning')
+                        except:
+                            flash(f'Đã tải lên và xử lý {processed_count} bản ghi, nhưng có {error_count} lỗi. Xem chi tiết trong lịch sử tải lên.', 'warning')
+                        upload_record.status = 'completed_with_errors'
                     else:
-                        uploaded_file.status = 'failed'
-                        uploaded_file.error_details = result.get('error', 'Unknown error')
-                        
-                        flash(f'Error processing file: {result.get("error", "Unknown error")}', 'error')
-                    
-                    uploaded_file.process_end = datetime.now()
-                    db.session.commit()
-                    
-                except Exception as e:
-                    uploaded_file.status = 'failed'
-                    uploaded_file.error_details = str(e)
-                    uploaded_file.process_end = datetime.now()
-                    db.session.commit()
-                    
-                    flash(f'Error processing file: {str(e)}', 'error')
-            else:
-                uploaded_file.status = 'failed'
-                uploaded_file.error_details = validation_result['error']
+                        flash(f'Đã tải lên và xử lý thành công {processed_count} bản ghi từ {filename}.', 'success')
+                        upload_record.status = 'completed'
+                else:
+                    error_msg = result.get('error', 'Unknown processing error')
+                    # Đơn giản hóa thông báo lỗi cho người dùng cuối
+                    if 'Data too long' in error_msg:
+                        user_error_message = "Dữ liệu lỗi quá lớn để lưu chi tiết. Vui lòng kiểm tra tóm tắt lỗi trong lịch sử tải lên."
+                        # Ghi log lỗi đầy đủ cho dev
+                        current_app.logger.error(f"Data too long error during CSV processing for file ID {upload_record.id}: {error_msg}")
+                    elif 'already exists' in error_msg: # Có thể bắt thêm các lỗi cụ thể khác
+                        user_error_message = "Phát hiện dữ liệu bệnh nhân trùng lặp. Xem chi tiết trong lịch sử tải lên."
+                    else:
+                        user_error_message = f"Xử lý file thất bại. Vui lòng kiểm tra định dạng file hoặc xem chi tiết lỗi trong lịch sử tải lên."
+                        # Ghi log lỗi không xác định
+                        current_app.logger.error(f"Unknown error during CSV processing for file ID {upload_record.id}: {error_msg}")
+
+                    flash(user_error_message, 'error')
+                    upload_record.status = 'failed'
+                
                 db.session.commit()
                 
-                flash(f'Invalid CSV file: {validation_result["error"]}', 'error')
-            
-            return redirect(url_for('upload.history'))
+                return redirect(url_for('upload.history'))
+                
+            except Exception as e:
+                db.session.rollback()
+                upload_record.status = 'failed'
+                upload_record.error_details = json.dumps([{"row": 0, "error": str(e)}])
+                db.session.add(upload_record)
+                db.session.commit()
+                
+                flash(f'Lỗi khi xử lý file: {str(e)}', 'error')
+                return redirect(url_for('upload.index'))
         else:
-            flash('File type not allowed. Please upload a CSV file.', 'error')
-    
-    # Get recent upload history for sidebar
-    recent_uploads = UploadedFile.query.order_by(
-        UploadedFile.upload_date.desc()
-    ).limit(5).all()
+            flash('File không hợp lệ. Chỉ chấp nhận file .csv', 'error')
+            return redirect(request.url)
     
-    return render_template('upload.html', recent_uploads=recent_uploads)
+    return render_template('upload.html', form=form, recent_uploads=recent_uploads)
 
 @upload_bp.route('/history')
 @login_required
 def history():
-    # Get upload history with pagination
     page = request.args.get('page', 1, type=int)
-    per_page = request.args.get('per_page', 10, type=int)
+    per_page = 10
     
-    uploads = UploadedFile.query.order_by(
-        UploadedFile.upload_date.desc()
-    ).paginate(page=page, per_page=per_page)
+    # Lấy danh sách các file đã upload, sắp xếp theo thời gian gần nhất
+    uploads = UploadedFile.query.order_by(UploadedFile.upload_date.desc()).paginate(
+        page=page, per_page=per_page, error_out=False
+    )
     
-    return render_template('upload_history.html', uploads=uploads, current_page=page, per_page=per_page)
+    return render_template('upload_history.html', uploads=uploads)
 
 @upload_bp.route('/preview/<int:file_id>')
 @login_required
@@ -158,14 +192,14 @@ def preview(file_id):
     uploaded_file = UploadedFile.query.get_or_404(file_id)
     
     # Check if file exists
-    if not os.path.exists(uploaded_file.file_path):
+    if not os.path.exists(uploaded_file.filePath):
         flash('File not found.', 'error')
         return redirect(url_for('upload.history'))
     
     try:
         # Read the first 10 rows of the CSV file
         df = pd.read_csv(
-            uploaded_file.file_path,
+            uploaded_file.filePath,
             delimiter=uploaded_file.delimiter,
             encoding=uploaded_file.file_encoding,
             nrows=10
@@ -189,36 +223,72 @@ def preview(file_id):
 def download(file_id):
     uploaded_file = UploadedFile.query.get_or_404(file_id)
     
-    # Check if file exists
-    if not os.path.exists(uploaded_file.file_path):
-        flash('File not found.', 'error')
+    # Chỉ cho phép người tải lên hoặc admin tải xuống
+    if uploaded_file.user_id != current_user.userID and not current_user.is_admin:
+        flash('Bạn không có quyền tải xuống file này', 'error')
         return redirect(url_for('upload.history'))
     
-    # Return file for download
+    # Kiểm tra xem file có tồn tại không
+    if not os.path.exists(uploaded_file.filePath):
+        flash('File không tồn tại trên hệ thống', 'error')
+        return redirect(url_for('upload.history'))
+    
+    # Trả về file để tải xuống
     return send_from_directory(
-        os.path.dirname(uploaded_file.file_path),
-        os.path.basename(uploaded_file.file_path),
+        os.path.dirname(uploaded_file.filePath),
+        os.path.basename(uploaded_file.filePath),
         as_attachment=True,
-        attachment_filename=uploaded_file.original_filename
+        download_name=uploaded_file.original_filename,
+        mimetype='text/csv'
     )
 
 @upload_bp.route('/delete/<int:file_id>', methods=['POST'])
 @login_required
 def delete(file_id):
+    # --- DEBUG CSRF --- 
+    current_app.logger.info(f"--- Entering delete route for file {file_id} ---")
+    submitted_token = request.form.get('csrf_token')
+    session_token = session.get('csrf_token') # Lấy token từ session (nếu có)
+    current_app.logger.info(f"CSRF token received in form for file {file_id}: {submitted_token}")
+    current_app.logger.info(f"CSRF token from session: {session_token}")
+    form_data = ', '.join([f"{k}={v}" for k, v in request.form.items()])
+    current_app.logger.info(f"All form data: {form_data}")
+    # ---------------------
+
+    # Tạo form tạm thời để xác thực CSRF token
+    form = UploadForm()
+    
+    # Kiểm tra CSRF token với phương thức validate() của form
+    if not form.validate():
+        current_app.logger.warning(f"Invalid CSRF token attempt for file {file_id}")
+        flash('CSRF token không hợp lệ. Vui lòng thử lại.', 'error')
+        return redirect(url_for('upload.history'))
+
+    current_app.logger.info(f"CSRF token validated successfully for file {file_id}.")
+
+    # Lấy thông tin file từ database
     uploaded_file = UploadedFile.query.get_or_404(file_id)
     
-    # Check if user has permission (admin or owner)
-    if not current_user.is_admin and uploaded_file.uploaded_by != current_user.id:
-        flash('You do not have permission to delete this file.', 'error')
+    # Chỉ cho phép người tải lên hoặc admin xóa
+    if uploaded_file.user_id != current_user.userID and not current_user.is_admin:
+        flash('Bạn không có quyền xóa file này', 'error')
         return redirect(url_for('upload.history'))
     
-    # Delete the file if it exists
-    if os.path.exists(uploaded_file.file_path):
-        os.remove(uploaded_file.file_path)
+    # Kiểm tra xem file có tồn tại không và xóa
+    if os.path.exists(uploaded_file.filePath):
+        try:
+            os.remove(uploaded_file.filePath)
+        except Exception as e:
+            # Không báo lỗi nếu không xóa được file, vẫn xóa bản ghi
+            pass
     
-    # Delete the database record
-    db.session.delete(uploaded_file)
-    db.session.commit()
+    # Xóa bản ghi trong database
+    try:
+        db.session.delete(uploaded_file)
+        db.session.commit()
+        flash('Đã xóa file thành công', 'success')
+    except Exception as e:
+        db.session.rollback()
+        flash(f'Lỗi khi xóa file: {str(e)}', 'error')
     
-    flash('File has been deleted.', 'success')
     return redirect(url_for('upload.history'))
diff --git a/app/templates/base.html b/app/templates/base.html
index f27b1f0497fa2af8682d336df1994b1aa8c3be40..0d607c9d774f7abd91092dbe0f92c44f1152a7de 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -382,10 +382,10 @@
                                         </a>
                                         <a href="{{ url_for('auth.logout') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">
                                             <i class="fas fa-sign-out-alt mr-2"></i> Đăng xuất
-                                        </a>
-                                    </div>
-                                </div>
-                            </div>
+                    </a>
+                </div>
+            </div>
+        </div>
                         </div>
                     </div>
                 </div>
@@ -397,13 +397,13 @@
                 <!-- Page Header -->
                 <div class="page-header mb-8">
                     <h1 class="text-3xl font-bold text-gray-800 animate-fade-in">{% block header %}Dashboard{% endblock %}</h1>
-                </div>
-                
+            </div>
+
                 <!-- Flash Messages -->
-                {% with messages = get_flashed_messages(with_categories=true) %}
-                    {% if messages %}
+            {% with messages = get_flashed_messages(with_categories=true) %}
+                {% if messages %}
                         <div class="mb-6 animate-slide-in-top">
-                            {% for category, message in messages %}
+                        {% for category, message in messages %}
                                 <div class="alert-message py-3 px-4 mb-2 rounded-md flex items-center justify-between {% if category == 'error' %}bg-red-100 text-red-700{% elif category == 'warning' %}bg-yellow-100 text-yellow-700{% elif category == 'info' %}bg-blue-100 text-blue-700{% else %}bg-green-100 text-green-700{% endif %}">
                                     <div class="flex items-center">
                                         {% if category == 'error' %}
@@ -415,20 +415,20 @@
                                         {% else %}
                                             <i class="fas fa-check-circle mr-2"></i>
                                         {% endif %}
-                                        {{ message }}
+                                {{ message }}
                                     </div>
                                     <button class="text-gray-500 hover:text-gray-700 focus:outline-none" onclick="this.parentElement.style.display='none'">
                                         <i class="fas fa-times"></i>
                                     </button>
-                                </div>
-                            {% endfor %}
-                        </div>
-                    {% endif %}
-                {% endwith %}
-                
+                            </div>
+                        {% endfor %}
+                    </div>
+                {% endif %}
+            {% endwith %}
+
                 <!-- Page Content -->
                 <div class="animate-fade-in">
-                    {% block content %}{% endblock %}
+                {% block content %}{% endblock %}
                 </div>
             </div>
         </div>
diff --git a/app/templates/patient_detail.html b/app/templates/patient_detail.html
index da9509ee4324370da1064d4fd1d1a22476e439c2..6c8c95b831cd57bbeadf429f8d76e8b3461d1ab5 100644
--- a/app/templates/patient_detail.html
+++ b/app/templates/patient_detail.html
@@ -438,6 +438,10 @@
                         Lịch sử Lượt khám (Encounters)
                     </h3>
                     <!-- Nút thêm encounter mới nếu cần -->
+                    <button type="button" class="inline-flex items-center px-3 py-1 border border-transparent rounded text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200">
+                        <i class="fas fa-plus mr-2"></i>
+                        Thêm lượt khám
+                    </button>
                 </div>
                  <div class="border-t border-gray-200 px-4 py-5 sm:p-6">
                     {% if encounters %}
@@ -445,27 +449,30 @@
                         <table class="min-w-full divide-y divide-gray-200">
                              <thead class="bg-gray-50">
                                 <tr>
-                                    <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID Encounter</th>
+                                    <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
                                     <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ngày nhập viện</th>
                                     <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ngày ra viện</th>
+                                    <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Chuyên gia DD</th>
                                     <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ngày vào CCU</th>
                                     <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ngày ra CCU</th>
-                                    <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Chuyên gia DD</th>
                                     <th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Thao tác</th>
                                 </tr>
                             </thead>
                              <tbody class="bg-white divide-y divide-gray-200">
                                 {% for encounter in encounters %}
-                                <tr class="hover:bg-gray-50">
+                                <tr class="encounter-row hover:bg-gray-100 cursor-pointer transition duration-150 ease-in-out" data-encounter-id="{{ encounter.id }}">
                                      <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ encounter.id }}</td>
                                      <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ encounter.admissionDateTime.strftime('%d/%m/%Y %H:%M') if encounter.admissionDateTime else 'N/A' }}</td>
-                                     <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ encounter.dischargeDateTime.strftime('%d/%m/%Y %H:%M') if encounter.dischargeDateTime else 'Đang điều trị' }}</td>
+                                     <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
+                                         {{ encounter.dischargeDateTime.strftime('%d/%m/%Y %H:%M') if encounter.dischargeDateTime else 'Đang điều trị' }}
+                                     </td>
+                                     <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
+                                         {{ encounter.dietitian.fullName if encounter.dietitian else 'Chưa gán' }}
+                                     </td>
                                      <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ encounter.ccuAdmissionDateTime.strftime('%d/%m/%Y %H:%M') if encounter.ccuAdmissionDateTime else 'N/A' }}</td>
                                      <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ encounter.ccuDischargeDateTime.strftime('%d/%m/%Y %H:%M') if encounter.ccuDischargeDateTime else 'N/A' }}</td>
-                                     <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ encounter.dietitian.fullName if encounter.dietitian else 'Chưa gán' }}</td>
                                      <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
-                                        <a href="#" class="text-blue-600 hover:text-blue-900">Chi tiết</a>
-                                        {# Thêm nút sửa/xóa encounter nếu cần #}
+                                        <a href="#" class="text-blue-600 hover:text-blue-900 view-encounter-details" data-encounter-id="{{ encounter.id }}">Chi tiết</a>
                                      </td>
                                 </tr>
                                 {% endfor %}
diff --git a/app/templates/upload.html b/app/templates/upload.html
index 43863856301c8c5ba5fe7b4a633f458ee9ba8bce..0a18294f9e9684c6dc58b7a56e48af76f596daa7 100644
--- a/app/templates/upload.html
+++ b/app/templates/upload.html
@@ -21,6 +21,7 @@
             <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('upload.index') }}" method="post" enctype="multipart/form-data" id="uploadForm">
+                        {{ form.csrf_token }}
                         <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>
@@ -88,7 +89,9 @@
                             </div>
                             
                             <div>
-                                <button type="submit" id="submitBtn" disabled class="w-full inline-flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-gray-400 focus:outline-none transition-all duration-200">
+                                <button type="submit" id="submitBtn" disabled 
+                                        style="background-color: #9ca3af;" 
+                                        class="w-full inline-flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white transition-all duration-200 ease-in-out">
                                     <i class="fas fa-cloud-upload-alt mr-2"></i>
                                     Tải lên
                                 </button>
@@ -103,7 +106,7 @@
         <div class="lg:col-span-1">
             <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">
-                    <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Hướng dẫn tải lên</h3>
+                    <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Hướng dẫn tải lên (Dữ liệu Bệnh nhân Ban đầu)</h3>
                     <div class="space-y-4">
                         <div class="flex items-start">
                             <div class="flex-shrink-0">
@@ -122,7 +125,7 @@
                                 </div>
                             </div>
                             <div class="ml-3 text-sm text-gray-600">
-                                <p>Kiểm tra xem tệp có các cột: <code>encounterId</code>, <code>bmi</code>, <code>fio2</code>, <code>tidal_vol</code>, <code>referral</code>.</p>
+                                <p>Kiểm tra xem tệp có đầy đủ các cột theo thứ tự: <code>patientID</code>, <code>firstName</code>, <code>lastName</code>, <code>age</code>, <code>gender</code>, <code>height</code>, <code>weight</code>, <code>blood_type</code>, <code>measurementDateTime</code>, <code>temperature</code>, <code>heart_rate</code>, <code>blood_pressure_systolic</code>, ... (và các cột đo lường khác).</p>
                             </div>
                         </div>
                         <div class="flex items-start">
@@ -148,20 +151,20 @@
                     </div>
                     
                     <div class="mt-6 border-t border-gray-200 pt-4">
-                        <h4 class="text-sm font-medium text-gray-900 mb-2">Mẫu định dạng CSV</h4>
+                        <h4 class="text-sm font-medium text-gray-900 mb-2">Mẫu định dạng CSV (Bệnh nhân + Đo lường ban đầu)</h4>
                         <div class="bg-gray-50 p-3 rounded-md overflow-x-auto">
                             <code class="text-xs">
-                                encounterId,bmi,fio2,tidal_vol,referral<br>
-                                P-10001,23.5,60,450,yes<br>
-                                P-10002,19.2,45,500,no<br>
-                                P-10003,32.5,70,400,yes
+                                patientID,firstName,lastName,age,gender,height,weight,blood_type,measurementDateTime,temperature,heart_rate,blood_pressure_systolic,blood_pressure_diastolic,oxygen_saturation,resp_rate,fio2,tidal_vol,...,bmi,referral<br>
+                                P-00001,John,Doe,65,male,175.0,80.5,A+,2024-01-15T10:00:00,36.8,85,130,80,97.5,18,21.0,480,...,26.3,0<br>
+                                P-00002,Jane,Smith,42,female,162.0,55.2,O-,2024-01-15T10:05:00,37.1,72,118,75,99.0,16,21.0,420,...,21.0,1<br>
+                                ...
                             </code>
                         </div>
                     </div>
                     
                     <div class="mt-6">
-                        <a href="{{ url_for('static', filename='templates/sample.csv') }}" class="text-primary-600 hover:text-primary-500 text-sm font-medium flex items-center">
-                            <i class="fas fa-download mr-1"></i> Tải xuống mẫu CSV
+                        <a href="{{ url_for('static', filename='templates/initial_patient_data_sample.csv') }}" class="text-primary-600 hover:text-primary-500 text-sm font-medium flex items-center">
+                            <i class="fas fa-download mr-1"></i> Tải xuống mẫu CSV (Bệnh nhân ban đầu)
                         </a>
                     </div>
                 </div>
@@ -171,26 +174,42 @@
                 <div class="px-4 py-5 sm:p-6">
                     <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Lịch sử tải lên</h3>
                     <div class="space-y-3">
-                        {% for i in range(3) %}
-                        <div class="border-b border-gray-200 pb-3 last:border-0 last:pb-0">
-                            <div class="flex justify-between items-start">
-                                <div>
-                                    <p class="text-sm font-medium text-gray-900">data_{{ 20230301 + i }}.csv</p>
-                                    <p class="text-xs text-gray-500">{{ (3-i) }} ngày trước</p>
+                        {% if recent_uploads %}
+                            {% for upload in recent_uploads %}
+                            <div class="border-b border-gray-200 pb-3 last:border-0 last:pb-0">
+                                <div class="flex justify-between items-start">
+                                    <div>
+                                        <p class="text-sm font-medium text-gray-900">{{ upload.original_filename }}</p>
+                                        <p class="text-xs text-gray-500">{{ upload.upload_date.strftime('%d/%m/%Y %H:%M') }}</p>
+                                    </div>
+                                    <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium 
+                                        {% if upload.status == 'completed' %}bg-green-100 text-green-800
+                                        {% elif upload.status == 'processing' %}bg-yellow-100 text-yellow-800
+                                        {% elif upload.status == 'failed' %}bg-red-100 text-red-800
+                                        {% elif upload.status == 'completed_with_errors' %}bg-orange-100 text-orange-800
+                                        {% else %}bg-gray-100 text-gray-800{% endif %}">
+                                        {% if upload.status == 'completed' %}Thành công
+                                        {% elif upload.status == 'processing' %}Đang xử lý
+                                        {% elif upload.status == 'failed' %}Thất bại
+                                        {% elif upload.status == 'completed_with_errors' %}Hoàn thành (có lỗi)
+                                        {% else %}{{ upload.status|capitalize }}{% endif %}
+                                    </span>
+                                </div>
+                                <div class="mt-1 text-xs text-gray-500">
+                                    {% if upload.total_records %}Đã nhập {{ upload.processed_records }} / {{ upload.total_records }} bản ghi{% endif %}
+                                    {% if upload.error_records > 0 %} ({{ upload.error_records }} lỗi){% endif %}
                                 </div>
-                                <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
-                                    Thành công
-                                </span>
                             </div>
-                            <div class="mt-1 text-xs text-gray-500">
-                                Đã nhập {{ 120 + i * 15 }} bản ghi
+                            {% endfor %}
+                        {% else %}
+                            <div class="text-sm text-gray-500 text-center py-4">
+                                Chưa có lịch sử tải lên
                             </div>
-                        </div>
-                        {% endfor %}
+                        {% endif %}
                     </div>
                     
                     <div class="mt-4">
-                        <a href="#" class="text-primary-600 hover:text-primary-500 text-sm font-medium">
+                        <a href="{{ url_for('upload.history') }}" class="text-primary-600 hover:text-primary-500 text-sm font-medium">
                             Xem tất cả lịch sử
                         </a>
                     </div>
@@ -202,6 +221,7 @@
 {% endblock %}
 
 {% block scripts %}
+{{ super() }}
 <script>
     document.addEventListener('DOMContentLoaded', () => {
         const fileInput = document.getElementById('file-upload');
@@ -257,11 +277,13 @@
         function handleFiles(file) {
             if (file.type !== 'text/csv' && !file.name.endsWith('.csv')) {
                 alert('Vui lòng chọn tệp CSV');
+                resetFileInput();
                 return;
             }
             
             if (file.size > 10 * 1024 * 1024) {
                 alert('Kích thước tệp quá lớn. Tối đa 10MB.');
+                resetFileInput();
                 return;
             }
             
@@ -271,18 +293,20 @@
         function showFilePreview(file) {
             fileName.textContent = file.name;
             filePreview.classList.remove('hidden');
-            submitBtn.disabled = false;
-            submitBtn.classList.remove('bg-gray-400');
-            submitBtn.classList.add('bg-primary-600', 'hover:bg-primary-700', 'transform', 'hover:-translate-y-1');
+            submitBtn.disabled = false; 
+            submitBtn.style.backgroundColor = '#0284c7';
+            submitBtn.style.cursor = 'pointer';
         }
         
-        removeFile.addEventListener('click', function() {
+        function resetFileInput() {
             fileInput.value = '';
             filePreview.classList.add('hidden');
-            submitBtn.disabled = true;
-            submitBtn.classList.add('bg-gray-400');
-            submitBtn.classList.remove('bg-primary-600', 'hover:bg-primary-700', 'transform', 'hover:-translate-y-1');
-        });
+            submitBtn.disabled = true; 
+            submitBtn.style.backgroundColor = '#9ca3af';
+            submitBtn.style.cursor = 'not-allowed';
+        }
+
+        removeFile.addEventListener('click', resetFileInput);
         
         // Xử lý submit form
         const uploadForm = document.getElementById('uploadForm');
diff --git a/app/templates/upload_history.html b/app/templates/upload_history.html
new file mode 100644
index 0000000000000000000000000000000000000000..c5d083a66f65eb9cc5e3203d40c4308c79565d0e
--- /dev/null
+++ b/app/templates/upload_history.html
@@ -0,0 +1,228 @@
+{% extends "base.html" %}
+
+{% block title %}Lịch sử Tải lên - CCU_BVNM{% endblock %}
+
+{% block content %}
+<div class="animate-slide-in">
+    <div class="md:flex md:items-center md:justify-between mb-6">
+        <div class="flex-1 min-w-0">
+            <h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">
+                Lịch sử Tải lên
+            </h2>
+            <p class="mt-1 text-sm text-gray-500">
+                Xem lại các tệp đã tải lên và trạng thái xử lý.
+            </p>
+        </div>
+        <div class="mt-4 flex md:mt-0 md:ml-4">
+            <a href="{{ url_for('upload.index') }}" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 transition duration-200">
+                <i class="fas fa-upload mr-2"></i> Tải lên tệp mới
+            </a>
+        </div>
+    </div>
+
+    <div class="bg-white shadow overflow-hidden sm:rounded-lg">
+        <div class="overflow-x-auto">
+            <table class="min-w-full divide-y divide-gray-200">
+                <thead class="bg-gray-50">
+                    <tr>
+                        <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tên tệp gốc</th>
+                        <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ngày tải lên</th>
+                        <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Người tải</th>
+                        <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Trạng thái</th>
+                        <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tổng số dòng</th>
+                        <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Đã xử lý</th>
+                        <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Lỗi</th>
+                        <th scope="col" class="relative px-6 py-3">
+                            <span class="sr-only">Actions</span>
+                        </th>
+                    </tr>
+                </thead>
+                <tbody class="bg-white divide-y divide-gray-200">
+                    {% if uploads.items %}
+                        {% for upload in uploads.items %}
+                            <tr class="hover:bg-gray-50">
+                                <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ upload.original_filename }}</td>
+                                <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ upload.upload_date.strftime('%d/%m/%Y %H:%M') if upload.upload_date else 'N/A' }}</td>
+                                <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ upload.uploader.email if upload.uploader else 'N/A' }}</td>
+                                <td class="px-6 py-4 whitespace-nowrap text-sm">
+                                    {% if upload.status == 'completed' %}
+                                        <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">Hoàn thành</span>
+                                    {% elif upload.status == 'processing' %}
+                                        <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">Đang xử lý</span>
+                                    {% elif upload.status == 'failed' %}
+                                        <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">Thất bại</span>
+                                     {% elif upload.status == 'completed_with_errors' %}
+                                        <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-orange-100 text-orange-800">Hoàn thành (có lỗi)</span>
+                                    {% else %}
+                                        <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{{ upload.status|capitalize }}</span>
+                                    {% endif %}
+                                </td>
+                                <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-center">{{ upload.total_records if upload.total_records is not none else '-' }}</td>
+                                <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-center">{{ upload.processed_records if upload.processed_records is not none else '-' }}</td>
+                                <td class="px-6 py-4 whitespace-nowrap text-sm text-center {% if upload.error_records > 0 %}text-red-600 font-semibold{% else %}text-gray-500{% endif %}">
+                                    {{ upload.error_records if upload.error_records is not none else '-' }}
+                                </td>
+                                <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
+                                    {# Nút Xem trước nếu cần #}
+                                    {# <a href="{{ url_for('upload.preview', file_id=upload.id) }}" class="text-indigo-600 hover:text-indigo-900">Preview</a> #}
+                                    {% if upload.error_records > 0 and upload.error_details %}
+                                         <button type="button" class="text-yellow-600 hover:text-yellow-900 view-errors" data-errors='{{ upload.error_details }}'>Xem lỗi</button>
+                                    {% endif %}
+                                    <a href="{{ url_for('upload.download', file_id=upload.id) }}" class="text-blue-600 hover:text-blue-900">Tải xuống</a>
+                                    <form action="{{ url_for('upload.delete', file_id=upload.id) }}" method="POST" class="inline" onsubmit="return confirm('Bạn có chắc chắn muốn xóa tệp này và lịch sử của nó?');">
+                                        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
+                                        <button type="submit" class="text-red-600 hover:text-red-900">Xóa</button>
+                                    </form>
+                                </td>
+                            </tr>
+                        {% endfor %}
+                    {% else %}
+                        <tr>
+                            <td colspan="8" class="px-6 py-10 text-center text-sm text-gray-500">
+                                Chưa có lịch sử tải lên nào.
+                            </td>
+                        </tr>
+                    {% endif %}
+                </tbody>
+            </table>
+        </div>
+
+        <!-- Pagination -->
+        {% if uploads.pages > 1 %}
+        <nav class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6" aria-label="Pagination">
+            <div class="hidden sm:block">
+                <p class="text-sm text-gray-700">
+                    Hiển thị
+                    <span class="font-medium">{{ uploads.offset + 1 }}</span>
+                    đến
+                    <span class="font-medium">{{ uploads.offset + uploads.items|length }}</span>
+                    của
+                    <span class="font-medium">{{ uploads.total }}</span>
+                    kết quả
+                </p>
+            </div>
+            <div class="flex-1 flex justify-between sm:justify-end">
+                <a href="{{ url_for('upload.history', page=uploads.prev_num) if uploads.has_prev else '#' }}"
+                   class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 {% if not uploads.has_prev %}opacity-50 cursor-not-allowed{% endif %}">
+                    Trước
+                </a>
+                <a href="{{ url_for('upload.history', page=uploads.next_num) if uploads.has_next else '#' }}"
+                   class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 {% if not uploads.has_next %}opacity-50 cursor-not-allowed{% endif %}">
+                    Sau
+                </a>
+            </div>
+        </nav>
+        {% endif %}
+    </div>
+
+    <!-- Modal để hiển thị lỗi -->
+    <div id="errorModal" class="fixed z-10 inset-0 overflow-y-auto hidden" aria-labelledby="modal-title" role="dialog" aria-modal="true">
+        <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
+            <div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
+            <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
+            <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
+                <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
+                    <div class="sm:flex sm:items-start">
+                        <div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
+                            <i class="fas fa-exclamation-triangle text-red-600"></i>
+                        </div>
+                        <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
+                            <h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">
+                                Chi tiết lỗi xử lý CSV
+                            </h3>
+                            <div class="mt-2">
+                                <div class="text-sm text-gray-500 max-h-96 overflow-y-auto">
+                                    <pre id="errorDetailsContent" class="whitespace-pre-wrap"></pre>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
+                    <button type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" id="closeErrorModal">
+                        Đóng
+                    </button>
+                </div>
+            </div>
+        </div>
+    </div>
+
+</div>
+{% endblock %}
+
+{% block scripts %}
+{{ super() }}
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+    const errorModal = document.getElementById('errorModal');
+    const errorDetailsContent = document.getElementById('errorDetailsContent');
+    const closeErrorModalBtn = document.getElementById('closeErrorModal');
+    const viewErrorButtons = document.querySelectorAll('.view-errors');
+
+    viewErrorButtons.forEach(button => {
+        button.addEventListener('click', () => {
+            try {
+                const errorsJson = button.getAttribute('data-errors');
+                const errors = JSON.parse(errorsJson);
+                
+                // Kiểm tra xem lỗi có được tóm tắt không
+                if (errors.summary) {
+                    // Đây là dạng tóm tắt lỗi
+                    let formattedSummary = `${errors.summary}\n\n`;
+                    
+                    if (errors.sample_patients && errors.sample_patients.length) {
+                        formattedSummary += `Ví dụ các ID bệnh nhân đã tồn tại: ${errors.sample_patients.join(', ')}\n\n`;
+                    }
+                    
+                    if (errors.message) {
+                        formattedSummary += errors.message;
+                    }
+                    
+                    errorDetailsContent.textContent = formattedSummary;
+                } else {
+                    // Dạng lỗi chi tiết theo từng dòng
+                    let formattedErrors = "";
+                    
+                    if (Array.isArray(errors)) {
+                        errors.forEach(err => {
+                            if (err.summary) {
+                                // Đây là thông báo tóm tắt ở cuối
+                                formattedErrors += `\n${err.summary}\n`;
+                            } else {
+                                formattedErrors += `Dòng ${err.row}: ${err.error}\n`;
+                                if(err.data) {
+                                    formattedErrors += `  Dữ liệu: ${JSON.stringify(err.data)}\n\n`;
+                                } else {
+                                    formattedErrors += '\n';
+                                }
+                            }
+                        });
+                    } else {
+                        formattedErrors = "Định dạng lỗi không đúng.";
+                    }
+                    
+                    errorDetailsContent.textContent = formattedErrors;
+                }
+                
+                errorModal.classList.remove('hidden');
+            } catch (e) {
+                console.error("Error parsing or displaying errors:", e);
+                errorDetailsContent.textContent = "Không thể hiển thị chi tiết lỗi.";
+                errorModal.classList.remove('hidden');
+            }
+        });
+    });
+
+    closeErrorModalBtn.addEventListener('click', () => {
+        errorModal.classList.add('hidden');
+    });
+
+    // Đóng modal khi click bên ngoài
+    errorModal.addEventListener('click', (event) => {
+        if (event.target === errorModal) {
+            errorModal.classList.add('hidden');
+        }
+    });
+});
+</script>
+{% endblock %} 
\ No newline at end of file
diff --git a/app/utils/csv_handler.py b/app/utils/csv_handler.py
index 06776857218a0210f12ff923d88b8f6335690341..ea80c0e7d62dc6894228618a44a0e455eadeef72 100644
--- a/app/utils/csv_handler.py
+++ b/app/utils/csv_handler.py
@@ -1,290 +1,285 @@
 import os
 import csv
-import pandas as pd
+# import pandas as pd # Không dùng pandas nữa
 import json
+from flask import current_app
 from datetime import datetime
 from app import db
 from app.models.patient import Patient, Encounter
 from app.models.measurement import PhysiologicalMeasurement
 from app.models.referral import Referral
 from app.models.uploaded_file import UploadedFile
+from sqlalchemy.exc import IntegrityError
+from dateutil.parser import parse as parse_datetime_string # Thư viện tốt hơn để parse datetime
 
+# --- Helper Functions --- 
+def _parse_float(value):
+    if value is None or value == '':
+        return None
+    try:
+        return float(value)
+    except (ValueError, TypeError):
+        return None
+
+def _parse_int(value):
+    if value is None or value == '':
+        return None
+    try:
+        # Thử chuyển đổi sang float trước để xử lý số thập phân (vd: "1.0")
+        float_val = float(value)
+        # Sau đó chuyển sang int
+        return int(float_val)
+    except (ValueError, TypeError):
+        return None
+
+def _parse_datetime(value):
+    if value is None or value == '':
+        return None
+    try:
+        # Sử dụng dateutil.parser để linh hoạt hơn
+        return parse_datetime_string(value)
+    except (ValueError, TypeError):
+        # Thử định dạng cụ thể nếu parser chung thất bại (tùy chọn)
+        # try:
+        #     return datetime.strptime(value, '%Y-%m-%d %H:%M:%S') 
+        # except ValueError:
+        #     return None
+        return None
+
+# --- Validation (Giữ nguyên hoặc đơn giản hóa nếu chỉ cần kiểm tra header) ---
 def validate_csv(file_path, delimiter=',', encoding='utf-8'):
-    """
-    Validate if a CSV file has the required columns and format
-    
-    Args:
-        file_path (str): Path to the CSV file
-        delimiter (str): CSV delimiter character
-        encoding (str): File encoding
-        
-    Returns:
-        dict: Dictionary with validation result
-            {
-                'valid': bool,
-                'error': str or None
-            }
-    """
+    """Validate CSV header for patient upload format."""
     try:
-        # Check if file exists
         if not os.path.exists(file_path):
             return {'valid': False, 'error': 'File does not exist'}
-        
-        # Check file size (max 10MB)
+
         file_size = os.path.getsize(file_path)
-        if file_size > 10 * 1024 * 1024:  # 10MB
+        if file_size > 10 * 1024 * 1024: # 10MB
             return {'valid': False, 'error': 'File is too large (max 10MB)'}
-        
-        # Try to read the file
-        df = pd.read_csv(file_path, delimiter=delimiter, encoding=encoding, nrows=1)
-        
-        # Check if the required columns exist
-        required_columns = ['encounterId', 'patient_id', 'firstName', 'lastName', 'age', 'gender']
-        optional_columns = ['bmi', 'admission_date', 'fio2', 'resp_rate', 'tidal_vol', 'pip', 'referral']
-        
-        missing_columns = [col for col in required_columns if col not in df.columns]
-        
-        if missing_columns:
-            return {
-                'valid': False, 
-                'error': f'Missing required columns: {", ".join(missing_columns)}'
-            }
-        
-        # All checks passed
+
+        with open(file_path, 'r', encoding=encoding, newline='') as csvfile:
+            reader = csv.reader(csvfile, delimiter=delimiter)
+            header = next(reader, None) # Đọc dòng header
+
+            if not header:
+                return {'valid': False, 'error': 'CSV file is empty or header is missing.'}
+
+            # Kiểm tra các cột bắt buộc tối thiểu (có thể mở rộng)
+            required_patient_cols = ['patientID', 'firstName', 'lastName', 'age', 'gender']
+            required_measurement_cols = ['measurementDateTime']
+            required_cols = required_patient_cols + required_measurement_cols
+
+            missing_cols = [col for col in required_cols if col not in header]
+            if missing_cols:
+                return {'valid': False, 'error': f'Missing required columns: {", ".join(missing_cols)}'}
+
         return {'valid': True, 'error': None}
-        
+
     except Exception as e:
-        return {'valid': False, 'error': str(e)}
+        return {'valid': False, 'error': f'CSV validation error: {str(e)}'}
 
+# --- Rewritten process_csv function ---
 def process_csv(uploaded_file_id):
     """
-    Process a CSV file and insert data into database
-    
-    Args:
-        uploaded_file_id (int): ID of the uploaded file record
-        
-    Returns:
-        dict: Processing result
-            {
-                'success': bool,
-                'total_records': int,
-                'processed_records': int,
-                'error_records': int,
-                'error_details': str or None
-            }
+    Process the combined Patient + Initial Measurement CSV file.
+    Creates Patient, initial Encounter, initial Measurement, and initial Referral.
     """
+    uploaded_file = UploadedFile.query.get(uploaded_file_id)
+    if not uploaded_file:
+        return {'success': False, 'error': f'Uploaded file record not found (ID: {uploaded_file_id})'}
+
+    total_records = 0
+    processed_records = 0
+    error_records = 0
+    errors = []
+
     try:
-        # Get the uploaded file record
-        uploaded_file = UploadedFile.query.get(uploaded_file_id)
-        
-        if not uploaded_file:
-            return {
-                'success': False,
-                'error': f'Uploaded file record not found (ID: {uploaded_file_id})'
-            }
-        
-        # Read the CSV file
-        df = pd.read_csv(
-            uploaded_file.file_path,
-            delimiter=uploaded_file.delimiter,
-            encoding=uploaded_file.file_encoding
-        )
-        
-        # Initialize counters
-        total_records = len(df)
-        processed_records = 0
-        error_records = 0
-        errors = []
-        
-        # Process each row
-        for index, row in df.iterrows():
-            try:
-                # Process patient data
-                patient = process_patient_data(row)
-                
-                # Process encounter data
-                encounter = process_encounter_data(row, patient.id)
-                
-                # Process measurement data if columns exist
-                has_measurement_data = any(col in df.columns for col in [
-                    'fio2', 'resp_rate', 'tidal_vol', 'pip', 'end_tidal_co2',
-                    'peep', 'tidal_vol_actual', 'tidal_vol_kg'
-                ])
-                
-                if has_measurement_data:
-                    measurement = process_measurement_data(row, patient.id, encounter.id)
-                
-                # Process referral if needed and column exists
-                if uploaded_file.process_referrals and 'referral' in df.columns:
-                    process_referral_data(row, patient.id, encounter.id)
-                
-                processed_records += 1
-                
-            except Exception as e:
-                error_records += 1
-                errors.append({
-                    'row': index + 1,
-                    'error': str(e),
-                    'data': row.to_dict()
-                })
-        
-        # Update uploaded file record with results
-        uploaded_file.total_records = total_records
-        uploaded_file.processed_records = processed_records
-        uploaded_file.error_records = error_records
-        
-        # Store error details as JSON
-        if errors:
-            uploaded_file.error_details = json.dumps(errors)
-        
-        db.session.commit()
-        
-        return {
-            'success': True,
-            'total_records': total_records,
-            'processed_records': processed_records,
-            'error_records': error_records,
-            'error_details': json.dumps(errors) if errors else None
-        }
-        
-    except Exception as e:
-        return {
-            'success': False,
-            'error': str(e)
-        }
+        with open(uploaded_file.filePath, 'r', encoding=uploaded_file.file_encoding, newline='') as csvfile:
+            # Sử dụng DictReader để dễ dàng truy cập cột bằng tên
+            reader = csv.DictReader(csvfile, delimiter=uploaded_file.delimiter)
+            
+            # Kiểm tra header có khớp không (an toàn hơn)
+            expected_headers = [
+                'patientID', 'firstName', 'lastName', 'age', 'gender', 'height', 'weight', 'blood_type',
+                'measurementDateTime', 'temperature', 'heart_rate', 'blood_pressure_systolic',
+                'blood_pressure_diastolic', 'oxygen_saturation', 'resp_rate', 'fio2',
+                'tidal_vol', 'end_tidal_co2', 'feed_vol', 'feed_vol_adm', 'fio2_ratio',
+                'insp_time', 'oxygen_flow_rate', 'peep', 'pip', 'sip', 'tidal_vol_actual',
+                'tidal_vol_kg', 'tidal_vol_spon', 'bmi', 'referral'
+            ]
+            if not reader.fieldnames or any(h not in reader.fieldnames for h in expected_headers):
+                missing = [h for h in expected_headers if h not in (reader.fieldnames or [])]
+                # Cho phép thiếu các cột không bắt buộc nếu cần
+                # Đây là check nghiêm ngặt, yêu cầu mọi cột phải có
+                # if missing:
+                #     raise ValueError(f"CSV header mismatch. Missing or incorrect columns: {', '.join(missing)}")
+                pass # Bỏ qua check nghiêm ngặt nếu muốn linh hoạt hơn
 
-def process_patient_data(row):
-    """Process patient data from a CSV row"""
-    # Check if patient already exists
-    patient = None
-    
-    if 'patient_id' in row and pd.notna(row['patient_id']):
-        patient_id = str(row['patient_id']).strip()
-        patient = Patient.query.filter_by(patient_id=patient_id).first()
-    
-    if not patient:
-        # Create new patient
-        patient = Patient(
-            patient_id=str(row['patient_id']) if 'patient_id' in row and pd.notna(row['patient_id']) else f"P-{datetime.now().strftime('%Y%m%d%H%M%S')}",
-            firstName=str(row['firstName']) if 'firstName' in row and pd.notna(row['firstName']) else None,
-            lastName=str(row['lastName']) if 'lastName' in row and pd.notna(row['lastName']) else None,
-            gender=str(row['gender']) if 'gender' in row and pd.notna(row['gender']) else None,
-            date_of_birth=calculate_dob(row['age']) if 'age' in row and pd.notna(row['age']) else None,
-            height=float(row['height']) if 'height' in row and pd.notna(row['height']) else None,
-            weight=float(row['weight']) if 'weight' in row and pd.notna(row['weight']) else None,
-            status='active'
-        )
-        
-        db.session.add(patient)
-        db.session.commit()
-    
-    return patient
+            for i, row in enumerate(reader):
+                total_records += 1
+                row_num = i + 2 # +1 vì index bắt đầu từ 0, +1 nữa vì bỏ qua header
+                try:
+                    # --- 1. Process Patient --- 
+                    patient_id = row.get('patientID')
+                    if not patient_id:
+                        raise ValueError("Missing patientID")
+                    
+                    # Kiểm tra xem Patient đã tồn tại chưa, nếu có thì bỏ qua (hoặc cập nhật tùy logic)
+                    existing_patient = Patient.query.get(patient_id)
+                    if existing_patient:
+                         errors.append({'row': row_num, 'error': f'Patient {patient_id} already exists. Skipping.'})
+                         error_records += 1
+                         continue # Bỏ qua dòng này
+                         
+                    height = _parse_float(row.get('height'))
+                    weight = _parse_float(row.get('weight'))
+                    
+                    new_patient = Patient(
+                        id=patient_id,
+                        firstName=row.get('firstName'),
+                        lastName=row.get('lastName'),
+                        age=_parse_int(row.get('age')),
+                        gender=row.get('gender', '').lower() or None, # Đảm bảo lowercase hoặc None
+                        height=height,
+                        weight=weight,
+                        blood_type=row.get('blood_type'),
+                        # admission_date sẽ được xác định bởi Encounter đầu tiên
+                    )
+                    new_patient.calculate_bmi() # Tự tính BMI
+                    
+                    # --- 2. Process Initial Encounter --- 
+                    measurement_time = _parse_datetime(row.get('measurementDateTime'))
+                    if not measurement_time:
+                         # Nếu không có thời gian đo lường, dùng thời gian hiện tại
+                         measurement_time = datetime.utcnow()
 
-def process_encounter_data(row, patient_id):
-    """Process encounter data from a CSV row"""
-    # Check if encounter already exists
-    encounter = None
-    
-    if 'encounterId' in row and pd.notna(row['encounterId']):
-        encounter_id = str(row['encounterId']).strip()
-        encounter = Encounter.query.filter_by(encounter_id=encounter_id).first()
-    
-    if not encounter:
-        # Create new encounter
-        encounter = Encounter(
-            encounter_id=str(row['encounterId']) if 'encounterId' in row and pd.notna(row['encounterId']) else f"E-{datetime.now().strftime('%Y%m%d%H%M%S')}",
-            patient_id=patient_id,
-            admission_date=parse_date(row['admission_date']) if 'admission_date' in row and pd.notna(row['admission_date']) else datetime.now(),
-            discharge_date=parse_date(row['discharge_date']) if 'discharge_date' in row and pd.notna(row['discharge_date']) else None,
-            encounter_type='inpatient',
-            primary_diagnosis=str(row['diagnosis']) if 'diagnosis' in row and pd.notna(row['diagnosis']) else None
-        )
-        
-        db.session.add(encounter)
+                    # Đặt admission_date của Patient bằng thời gian Encounter đầu tiên
+                    new_patient.admission_date = measurement_time 
+                         
+                    initial_encounter = Encounter(
+                        patientID=new_patient.id,
+                        admissionDateTime=measurement_time
+                        # dischargeDateTime sẽ là None
+                    )
+                    
+                    # --- 3. Process Initial Measurement --- 
+                    initial_measurement = PhysiologicalMeasurement(
+                        patient_id=new_patient.id,
+                        # encounter_id sẽ được gán sau khi encounter được flush
+                        measurementDateTime=measurement_time,
+                        temperature=_parse_float(row.get('temperature')),
+                        heart_rate=_parse_int(row.get('heart_rate')),
+                        blood_pressure_systolic=_parse_int(row.get('blood_pressure_systolic')),
+                        blood_pressure_diastolic=_parse_int(row.get('blood_pressure_diastolic')),
+                        oxygen_saturation=_parse_float(row.get('oxygen_saturation')),
+                        resp_rate=_parse_int(row.get('resp_rate')),
+                        fio2=_parse_float(row.get('fio2')),
+                        fio2_ratio=_parse_float(row.get('fio2_ratio')),
+                        peep=_parse_float(row.get('peep')),
+                        pip=_parse_float(row.get('pip')),
+                        sip=_parse_float(row.get('sip')),
+                        insp_time=_parse_float(row.get('insp_time')),
+                        oxygen_flow_rate=_parse_float(row.get('oxygen_flow_rate')),
+                        end_tidal_co2=_parse_float(row.get('end_tidal_co2')),
+                        tidal_vol=_parse_float(row.get('tidal_vol')),
+                        tidal_vol_kg=_parse_float(row.get('tidal_vol_kg')),
+                        tidal_vol_actual=_parse_float(row.get('tidal_vol_actual')),
+                        tidal_vol_spon=_parse_float(row.get('tidal_vol_spon')),
+                        feed_vol=_parse_float(row.get('feed_vol')),
+                        feed_vol_adm=_parse_float(row.get('feed_vol_adm')),
+                        bmi=_parse_float(row.get('bmi')) # Có thể lấy BMI từ CSV hoặc để Patient tự tính
+                        # notes có thể thêm sau này nếu cần
+                    )
+                    
+                    # --- 4. Process Initial Referral (Optional) --- 
+                    initial_referral = None
+                    referral_flag = _parse_int(row.get('referral')) # Parse '1' hoặc '0'
+                    if referral_flag == 1:
+                        initial_referral = Referral(
+                            patient_id=new_patient.id,
+                            # encounter_id sẽ gán sau
+                            is_ml_recommended=True, # Giả định referral từ CSV là do ML/auto
+                            referral_status='ML Recommended', # Hoặc 'Pending Review'
+                            referralRequestedDateTime=measurement_time
+                            # dietitian_id, notes có thể thêm sau
+                        )
+
+                    # --- Add to Session and Commit (từng bản ghi hoặc theo batch) --- 
+                    db.session.add(new_patient)
+                    db.session.add(initial_encounter)
+                    db.session.flush() # Flush để lấy initial_encounter.id
+                    
+                    initial_measurement.encounter_id = initial_encounter.id
+                    db.session.add(initial_measurement)
+                    
+                    if initial_referral:
+                        initial_referral.encounter_id = initial_encounter.id
+                        db.session.add(initial_referral)
+                        
+                    db.session.commit() # Commit cho mỗi bệnh nhân để tránh lỗi lớn
+                    processed_records += 1
+
+                except IntegrityError as ie:
+                    db.session.rollback()
+                    error_records += 1
+                    errors.append({'row': row_num, 'error': f'Database integrity error (possibly duplicate ID?): {str(ie)}'})
+                except Exception as e:
+                    db.session.rollback()
+                    error_records += 1
+                    # Ghi log lỗi chi tiết hơn
+                    error_message = f'Error processing row: {str(e)}'
+                    current_app.logger.error(f"CSV Processing Error (Row {row_num}): {error_message} Data: {row}", exc_info=True)
+                    errors.append({'row': row_num, 'error': error_message})
+
+    except Exception as e:
+        # Lỗi đọc file hoặc lỗi không mong muốn khác
+        db.session.rollback()
+        uploaded_file.status = 'failed'
+        error_message = f'Failed to process CSV file: {str(e)}'
+        uploaded_file.error_details = error_message
+        current_app.logger.error(f"CSV File Processing Failed (File ID: {uploaded_file_id}): {error_message}", exc_info=True)
         db.session.commit()
-    
-    return encounter
+        return {'success': False, 'error': error_message}
 
-def process_measurement_data(row, patient_id, encounter_id):
-    """Process physiological measurement data from a CSV row"""
-    # Create new measurement
-    measurement = PhysiologicalMeasurement(
-        patient_id=patient_id,
-        encounter_id=encounter_id,
-        temperature=float(row['temperature']) if 'temperature' in row and pd.notna(row['temperature']) else None,
-        heart_rate=int(row['heart_rate']) if 'heart_rate' in row and pd.notna(row['heart_rate']) else None,
-        respiratory_rate=float(row['resp_rate']) if 'resp_rate' in row and pd.notna(row['resp_rate']) else None,
-        blood_pressure_systolic=int(row['bp_systolic']) if 'bp_systolic' in row and pd.notna(row['bp_systolic']) else None,
-        blood_pressure_diastolic=int(row['bp_diastolic']) if 'bp_diastolic' in row and pd.notna(row['bp_diastolic']) else None,
-        oxygen_saturation=float(row['oxygen_saturation']) if 'oxygen_saturation' in row and pd.notna(row['oxygen_saturation']) else None,
-        bmi=float(row['bmi']) if 'bmi' in row and pd.notna(row['bmi']) else None,
-        fio2=float(row['fio2']) if 'fio2' in row and pd.notna(row['fio2']) else None,
-        tidal_volume=float(row['tidal_vol']) if 'tidal_vol' in row and pd.notna(row['tidal_vol']) else None,
-        peak_inspiratory_pressure=float(row['pip']) if 'pip' in row and pd.notna(row['pip']) else None,
-        measurement_date=datetime.now()
-    )
-    
-    db.session.add(measurement)
-    db.session.commit()
+    # Cập nhật trạng thái file sau khi xử lý xong
+    uploaded_file.total_records = total_records
+    uploaded_file.processed_records = processed_records
+    uploaded_file.error_records = error_records
+    uploaded_file.status = 'completed' if error_records == 0 else 'completed_with_errors'
     
-    return measurement
-
-def process_referral_data(row, patient_id, encounter_id):
-    """Process referral data from a CSV row"""
-    # Check if referral is needed
-    if 'referral' in row and pd.notna(row['referral']):
-        referral_value = str(row['referral']).strip().lower()
-        
-        if referral_value in ['yes', 'true', '1']:
-            # Create new referral
-            referral = Referral(
-                patient_id=patient_id,
-                referralRequestedDateTime=datetime.now(),
-                referral_source='CSV Import',
-                reason='Automatically generated from imported data',
-                referral_status='new',
-                priority=3,  # Medium priority
-                ml_prediction=True,
-                ml_confidence=float(row['ml_confidence']) if 'ml_confidence' in row and pd.notna(row['ml_confidence']) else 0.8
-            )
-            
-            db.session.add(referral)
-            db.session.commit()
-            
-            return referral
+    # Xử lý lưu error_details một cách thông minh để tránh quá lớn
+    if errors:
+        # Nếu có quá nhiều lỗi trùng lặp, hãy tóm tắt thay vì lưu tất cả
+        if len(errors) > 10 and all(e.get('error', '').startswith('Patient') and 'already exists' in e.get('error', '') for e in errors[:10]):
+            patient_ids = [e.get('error').split()[1] for e in errors if 'already exists' in e.get('error', '')]
+            error_summary = {
+                'summary': f'Phát hiện {len(patient_ids)} bệnh nhân đã tồn tại trong hệ thống.',
+                'sample_patients': patient_ids[:5],  # Chỉ lấy 5 mẫu
+                'message': f'Vui lòng kiểm tra lại dữ liệu hoặc sử dụng tính năng cập nhật thay vì thêm mới.'
+            }
+            uploaded_file.error_details = json.dumps(error_summary)
+        else:
+            # Chỉ lưu tối đa 20 lỗi đầu tiên để tránh quá lớn
+            limited_errors = errors[:20]
+            if len(errors) > 20:
+                limited_errors.append({'summary': f'... và {len(errors) - 20} lỗi khác không hiển thị.'})
+            uploaded_file.error_details = json.dumps(limited_errors)
     
-    return None
+    uploaded_file.process_end = datetime.utcnow()
+    db.session.commit()
 
-def calculate_dob(age):
-    """Calculate approximate date of birth from age"""
-    if pd.isna(age):
-        return None
-    
-    try:
-        age = int(age)
-        today = datetime.now().date()
-        year = today.year - age
-        return datetime(year, 1, 1).date()  # January 1st as approximate birth date
-    except:
-        return None
+    return {
+        'success': True,
+        'total_records': total_records,
+        'processed_records': processed_records,
+        'error_records': error_records,
+        'error_details': uploaded_file.error_details
+    }
 
-def parse_date(date_str):
-    """Parse date string in various formats"""
-    if pd.isna(date_str):
-        return None
-    
-    try:
-        # Try multiple date formats
-        for fmt in ['%Y-%m-%d', '%Y/%m/%d', '%d-%m-%Y', '%d/%m/%Y', '%m-%d-%Y', '%m/%d/%Y']:
-            try:
-                return datetime.strptime(str(date_str).strip(), fmt)
-            except:
-                continue
-        
-        # If date is numeric (timestamp)
-        if isinstance(date_str, (int, float)):
-            return datetime.fromtimestamp(date_str)
-        
-        return None
-    except:
-        return None
+# --- Xóa các hàm process_patient/encounter/measurement/referral_data cũ --- 
+# def process_patient_data(row): ... (đã xóa)
+# def process_encounter_data(row, patient_id): ... (đã xóa)
+# def process_measurement_data(row, patient_id, encounter_id): ... (đã xóa)
+# def process_referral_data(row, patient_id, encounter_id): ... (đã xóa)
+# def calculate_dob(age): ... (đã xóa - dùng trực tiếp age)
+# def parse_date(date_str): ... (đã xóa - dùng helper _parse_datetime)
diff --git a/generate_patients.py b/generate_patients.py
index 93b30e25a4b92eece2328b69c5984025eec395f7..922baf6460d445f7970c128c3e3ef6809c8c8428 100644
--- a/generate_patients.py
+++ b/generate_patients.py
@@ -1,22 +1,23 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-import json
+import csv  # Import thư viện csv
 import random
 # Bạn cần cài đặt thư viện Faker: pip install Faker
 from faker import Faker
 import os
+from datetime import datetime # Cần datetime để thêm cột thời gian
 
 # Lấy đường dẫn thư mục của script hiện tại
 script_dir = os.path.dirname(os.path.abspath(__file__))
-output_file = os.path.join(script_dir, 'patients_data.json')
+output_file = os.path.join(script_dir, 'patients_data.csv') # Đổi tên file thành .csv
 
 fake = Faker('en_US') # Có thể dùng 'vi_VN' nếu muốn tên Việt
 
 def generate_measurement(age):
     """Tạo dữ liệu đo lường ngẫu nhiên nhưng hợp lý theo độ tuổi"""
     measurements = {}
-
+    measurements['measurementDateTime'] = datetime.utcnow().isoformat() # Thêm thời gian đo
     # Nhiệt độ (Temperature): 36.0 - 37.5, có thể cao hơn nếu già/bệnh
     base_temp = random.uniform(36.0, 37.2)
     if age > 70 or random.random() < 0.1: # 10% chance of higher temp
@@ -66,20 +67,43 @@ def generate_measurement(age):
     measurements['tidal_vol'] = random.randint(350, 550)
 
     # Các chỉ số khác (tùy chọn, để None hoặc giá trị ngẫu nhiên cơ bản)
-    measurements['end_tidal_co2'] = round(random.uniform(35, 45), 1) if random.random() < 0.5 else None
-    measurements['feed_vol'] = random.randint(100, 500) if random.random() < 0.3 else None
-    measurements['peep'] = random.randint(4, 8) if random.random() < 0.4 else None
-    measurements['pip'] = random.randint(25, 35) if random.random() < 0.4 else None
+    measurements['end_tidal_co2'] = round(random.uniform(35, 45), 1) if random.random() < 0.5 else ''
+    measurements['feed_vol'] = random.randint(100, 500) if random.random() < 0.3 else ''
+    measurements['feed_vol_adm'] = random.randint(80, measurements['feed_vol']) if measurements['feed_vol'] and random.random() < 0.8 else ''
+    measurements['fio2_ratio'] = round(measurements['fio2'] / 100, 2) if measurements['fio2'] and measurements['fio2'] > 21 else ''
+    measurements['insp_time'] = round(random.uniform(0.8, 1.5), 1) if random.random() < 0.6 else ''
+    measurements['oxygen_flow_rate'] = random.randint(2, 10) if measurements['fio2'] > 21 else ''
+    measurements['peep'] = random.randint(4, 8) if random.random() < 0.4 else ''
+    measurements['pip'] = random.randint(25, 35) if random.random() < 0.4 else ''
+    measurements['sip'] = random.randint(20, measurements['pip']) if measurements['pip'] and random.random() < 0.7 else ''
+    measurements['tidal_vol_actual'] = random.randint(int(measurements['tidal_vol']*0.9), measurements['tidal_vol']) if measurements['tidal_vol'] else ''
+    measurements['tidal_vol_kg'] = '' # Sẽ tính toán sau dựa trên weight
+    measurements['tidal_vol_spon'] = random.randint(20, 80) if random.random() < 0.2 else ''
+    measurements['bmi'] = '' # Sẽ tính toán sau dựa trên height/weight
+
+    # Thêm referral ngẫu nhiên (yes/no)
+    measurements['referral'] = 1 if random.random() < 0.25 else 0 # 25% cần referral ban đầu
 
     return measurements
 
-def generate_patients(num_patients=50):
-    patients = []
+def generate_patient_rows(num_patients=50):
+    patient_data_rows = []
     blood_types = ['A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-']
     genders = ['male', 'female', 'other']
 
+    # Định nghĩa Header CSV cho file upload bệnh nhân
+    header = [
+        'patientID', 'firstName', 'lastName', 'age', 'gender', 'height', 'weight', 'blood_type',
+        # Thêm các trường đo lường ban đầu vào cùng hàng
+        'measurementDateTime', 'temperature', 'heart_rate', 'blood_pressure_systolic',
+        'blood_pressure_diastolic', 'oxygen_saturation', 'resp_rate', 'fio2',
+        'tidal_vol', 'end_tidal_co2', 'feed_vol', 'feed_vol_adm', 'fio2_ratio',
+        'insp_time', 'oxygen_flow_rate', 'peep', 'pip', 'sip', 'tidal_vol_actual',
+        'tidal_vol_kg', 'tidal_vol_spon', 'bmi', 'referral'
+    ]
+
     for i in range(num_patients):
-        patient_id_val = f"P-{i+1:05d}" # Sử dụng patient_id_val để tránh trùng tên biến
+        patient_id_val = f"P-{i+1:05d}"
         age = random.randint(18, 90)
         gender = random.choices(genders, weights=[48, 48, 4], k=1)[0]
 
@@ -93,37 +117,64 @@ def generate_patients(num_patients=50):
             lastName = fake.last_name()
             height = round(random.uniform(150, 175), 1)
             weight = round(random.gauss(mu=height * 0.4, sigma=8), 1)
-        else:
+        else: # 'other'
             firstName = fake.first_name_nonbinary()
             lastName = fake.last_name()
             height = round(random.uniform(155, 180), 1)
             weight = round(random.gauss(mu=height * 0.42, sigma=9), 1)
 
-        weight = max(40, min(150, weight))
+        weight = max(40, min(150, weight)) # Đảm bảo cân nặng hợp lý
 
-        patient = {
-            "patientID": patient_id_val, # Sử dụng patient_id_val
+        # Tạo thông tin bệnh nhân cơ bản
+        patient_info = {
+            "patientID": patient_id_val,
             "firstName": firstName,
             "lastName": lastName,
             "age": age,
             "gender": gender,
             "height": height,
             "weight": weight,
-            "blood_type": random.choice(blood_types),
-            "initial_measurement": generate_measurement(age)
+            "blood_type": random.choice(blood_types)
         }
-        patients.append(patient)
 
-    return patients
+        # Tạo thông tin đo lường ban đầu
+        initial_measurement = generate_measurement(age)
+
+        # Tính toán các giá trị còn thiếu
+        if height > 0:
+            height_m = height / 100
+            bmi_value = round(weight / (height_m * height_m), 1)
+            initial_measurement['bmi'] = bmi_value
+            if initial_measurement.get('tidal_vol'): # Dùng .get() để tránh lỗi nếu key không tồn tại
+                try:
+                     ideal_body_weight = (height - 152.4) * 0.91 + (50 if gender == 'male' else 45.5) # Công thức tham khảo
+                     if ideal_body_weight > 0:
+                         initial_measurement['tidal_vol_kg'] = round(initial_measurement['tidal_vol'] / ideal_body_weight, 1)
+                except: # Bắt lỗi nếu tính toán thất bại
+                    initial_measurement['tidal_vol_kg'] = ''
+
+
+        # Kết hợp thông tin bệnh nhân và đo lường thành một hàng dữ liệu CSV
+        row_data = [
+            patient_info.get(col, '') or '' for col in header[:8] # Lấy thông tin bệnh nhân
+        ] + [
+            initial_measurement.get(col, '') or '' for col in header[8:] # Lấy thông tin đo lường, dùng '' nếu None
+        ]
+        patient_data_rows.append(row_data)
+
+    return header, patient_data_rows
 
 if __name__ == "__main__":
     num = 50
-    print(f"Generating {num} patient records...")
-    patient_data = generate_patients(num)
+    print(f"Generating {num} patient records for CSV...")
+    csv_header, patient_rows = generate_patient_rows(num)
 
     try:
-        with open(output_file, 'w', encoding='utf-8') as f:
-            json.dump(patient_data, f, indent=4, ensure_ascii=False)
+        # Ghi vào file CSV
+        with open(output_file, 'w', newline='', encoding='utf-8') as f:
+            writer = csv.writer(f)
+            writer.writerow(csv_header) # Ghi dòng header
+            writer.writerows(patient_rows) # Ghi tất cả các dòng dữ liệu
         print(f"Successfully generated {num} patient records to {output_file}")
     except IOError as e:
         print(f"Error writing to file {output_file}: {e}")
diff --git a/instance/config.py b/instance/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf49ab180fc684da210474ad4d303aeae5a8dddd
--- /dev/null
+++ b/instance/config.py
@@ -0,0 +1,31 @@
+# Production configuration settings
+SECRET_KEY = 'replace_with_strong_key_in_production'
+DEBUG = True  # True for development
+
+# Database settings
+import os
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# Cấu hình MySQL 
+SQLALCHEMY_DATABASE_URI = 'mysql://root:MinhDZ3009@localhost/ccu'
+
+# Cấu hình SQLite - hiện đang bị comment
+# SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, 'instance', 'ccu_htlm.db')
+
+SQLALCHEMY_TRACK_MODIFICATIONS = False
+
+# Upload folder
+UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'instance', 'uploads')
+MAX_CONTENT_LENGTH = 10 * 1024 * 1024  # 10MB upload limit
+
+# Application settings
+APP_NAME = "CCU HTM - Critical Care Unit Patient Management"
+ADMIN_EMAIL = "admin@ccuhtm.com"
+
+# Mail settings (if needed for reports or notifications)
+MAIL_SERVER = 'smtp.example.com'
+MAIL_PORT = 587
+MAIL_USE_TLS = True
+MAIL_USERNAME = 'username'
+MAIL_PASSWORD = 'password'
+MAIL_DEFAULT_SENDER = 'admin@ccuhtm.com'
diff --git a/instance/uploads/20250416150540_patients_data.csv b/instance/uploads/20250416150540_patients_data.csv
new file mode 100644
index 0000000000000000000000000000000000000000..fcdcdec0745556220bb3e6897cf464a7c513b793
--- /dev/null
+++ b/instance/uploads/20250416150540_patients_data.csv
@@ -0,0 +1,51 @@
+patientID,firstName,lastName,age,gender,height,weight,blood_type,measurementDateTime,temperature,heart_rate,blood_pressure_systolic,blood_pressure_diastolic,oxygen_saturation,resp_rate,fio2,tidal_vol,end_tidal_co2,feed_vol,feed_vol_adm,fio2_ratio,insp_time,oxygen_flow_rate,peep,pip,sip,tidal_vol_actual,tidal_vol_kg,tidal_vol_spon,bmi,referral
+P-00001,Alexandra,Hobbs,24,female,174.0,62.6,O-,2025-04-16T07:34:02.746051,36.7,75,125,71,92.6,13,21.0,492,39.7,,,,1.1,,,,,455,7.6,,20.7,
+P-00002,Sean,Raymond,52,male,178.0,74.9,AB+,2025-04-16T07:34:02.746051,36.7,80,146,93,98.7,13,21.0,534,43.0,,,,1.3,,,,,526,7.3,26,23.6,1
+P-00003,Christina,Garcia,56,female,159.2,52.8,A+,2025-04-16T07:34:02.746051,36.3,70,130,93,97.0,19,33.4,419,37.6,,,0.33,1.3,9,,,,400,8.1,,20.8,1
+P-00004,Emily,Payne,63,female,167.6,66.6,AB+,2025-04-16T07:34:02.746051,36.7,97,151,81,98.0,17,21.0,402,,,,,,,,32,,387,6.8,,23.7,1
+P-00005,Jessica,Taylor,85,female,150.9,60.1,AB-,2025-04-16T07:34:02.746051,37.9,107,148,97,98.7,16,21.0,420,42.8,,,,1.1,,7,28,,412,9.5,22,26.4,
+P-00006,Brianna,Jacobs,69,female,152.9,57.9,AB+,2025-04-16T07:34:02.746051,36.9,70,139,103,94.5,14,59.9,364,37.8,,,0.6,1.1,4,,,,345,7.9,48,24.8,1
+P-00007,Kevin,Martin,28,male,161.7,64.6,AB-,2025-04-16T07:34:02.746051,36.1,66,120,80,97.0,12,21.0,444,36.1,,,,1.4,,4,,,401,7.6,,24.7,
+P-00008,Cheyenne,York,87,female,157.9,68.8,B-,2025-04-16T07:34:02.746051,36.9,79,125,90,98.8,14,21.0,423,36.5,,,,,,,30,30,397,8.4,28,27.6,
+P-00009,Rebecca,Carpenter,49,female,166.8,73.3,AB+,2025-04-16T07:34:02.746051,36.6,50,140,83,94.9,12,21.0,415,36.3,,,,,,,,,403,7.1,,26.3,1
+P-00010,Carl,Bell,53,male,178.4,68.4,O-,2025-04-16T07:34:02.746051,37.2,84,135,89,97.1,16,21.0,520,,,,,1.5,,7,,,513,7.1,,21.5,
+P-00011,Rachel,Butler,27,female,167.2,66.2,AB-,2025-04-16T07:34:02.746051,36.5,45,117,80,97.8,18,21.0,428,,,,,,,6,,,411,7.3,77,23.7,
+P-00012,Megan,Soto,59,female,168.5,51.3,O+,2025-04-16T07:34:02.746051,36.5,80,138,86,97.5,20,21.0,489,,,,,,,,,,485,8.1,,18.1,
+P-00013,Brandon,Horton,63,male,185.0,74.7,B+,2025-04-16T07:34:02.746051,37.4,79,130,81,96.5,20,40.4,387,39.6,,,0.4,0.9,8,,,,377,4.9,,21.8,
+P-00014,Mandy,Perry,77,female,163.1,68.3,B-,2025-04-16T07:34:02.747129,37.0,105,132,83,94.9,18,21.0,430,,396,221,,1.5,,,,,402,7.8,,25.7,1
+P-00015,Melissa,Wallace,38,female,159.5,65.2,O+,2025-04-16T07:34:02.747129,37.3,60,113,75,97.0,12,21.0,383,41.1,,,,0.9,,,,,378,7.4,,25.6,
+P-00016,Jeffery,Prince,45,male,177.0,65.1,A-,2025-04-16T07:34:02.747129,37.0,69,120,76,96.6,19,21.0,482,,,,,0.9,,8,,,481,6.7,,20.8,
+P-00017,Dillon,Perez,24,male,169.6,89.1,O+,2025-04-16T07:34:02.747129,36.8,78,117,77,99.4,18,21.0,409,,473,82,,1.2,,5,29,27,383,6.2,79,31.0,
+P-00018,Jessica,Peterson,85,female,168.7,74.5,B-,2025-04-16T07:34:02.747129,37.7,81,138,94,97.6,14,21.0,513,43.9,,,,1.1,,4,,,494,8.5,,26.2,1
+P-00019,Stephen,Wood,86,male,168.9,63.9,O+,2025-04-16T07:34:02.747129,36.9,81,148,91,96.2,13,52.7,365,42.3,,,0.53,1.4,4,8,27,23,331,5.6,,22.4,
+P-00020,Jessica,Fields,66,female,159.1,65.8,A-,2025-04-16T07:34:02.747129,36.9,106,140,85,97.4,19,21.0,385,,358,156,,,,,,,385,7.5,,26.0,
+P-00021,Kevin,Keller,24,male,174.1,53.5,A+,2025-04-16T07:34:02.747129,37.1,65,114,83,96.3,13,21.0,442,,,,,,,,,,423,6.3,,17.7,
+P-00022,Susan,Tucker,42,other,177.0,81.3,A+,2025-04-16T07:34:02.747129,36.7,73,124,83,97.7,16,21.0,396,,,,,,,6,,,361,5.8,77,26.0,
+P-00023,Justin,Miller,26,male,176.8,103.4,O+,2025-04-16T07:34:02.747129,37.0,80,123,71,97.3,12,21.0,382,42.1,349,318,,,,5,,,355,5.3,,33.1,1
+P-00024,Andrea,Webster,87,other,158.7,55.9,AB+,2025-04-16T07:34:02.747129,36.4,96,138,91,96.8,14,21.0,506,,248,208,,1.5,,,29,29,460,9.9,,22.2,
+P-00025,Matthew,Griffin,58,male,168.1,76.1,AB+,2025-04-16T07:34:02.747129,37.1,84,147,101,96.6,15,21.0,362,40.9,,,,,,,35,30,355,5.6,,26.9,
+P-00026,Andrew,Douglas,63,male,180.6,102.1,AB+,2025-04-16T07:34:02.747129,36.4,103,126,85,99.4,12,21.0,372,36.7,228,168,,1.1,,,31,22,346,4.9,,31.3,
+P-00027,Anthony,Bruce,19,male,161.8,83.0,A-,2025-04-16T07:34:02.747129,36.2,40,116,81,98.4,17,48.1,397,36.5,287,273,0.48,0.8,7,8,,,388,6.8,,31.7,
+P-00028,John,Gray,30,male,183.6,71.7,O+,2025-04-16T07:34:02.747129,36.1,71,113,78,99.0,20,21.0,467,40.5,,,,,,4,,,442,6.0,,21.3,1
+P-00029,Casey,Riley,86,male,179.9,90.6,B-,2025-04-16T07:34:02.747129,36.8,94,144,100,97.3,17,21.0,439,35.6,,,,1.4,,8,29,,434,5.9,,28.0,
+P-00030,Jessica,Williams,69,female,171.3,63.3,AB+,2025-04-16T07:34:02.747129,37.0,99,143,91,98.3,14,21.0,369,35.7,,,,,,,,,356,5.9,,21.6,
+P-00031,Madison,Bell,56,female,160.6,71.5,O+,2025-04-16T07:34:02.748156,36.3,65,137,95,96.2,16,53.4,468,44.9,,,0.53,,5,5,,,450,8.8,,27.7,
+P-00032,Debra,Wilson,78,female,172.5,78.5,AB-,2025-04-16T07:34:02.748156,36.4,88,138,85,97.6,17,21.0,535,35.8,,,,1.2,,5,26,,499,8.4,,26.4,
+P-00033,Andrew,Lewis,73,male,170.9,98.3,A+,2025-04-16T07:34:02.748156,38.0,90,142,87,99.3,15,21.0,456,44.2,,,,1.1,,,,,450,6.8,,33.7,
+P-00034,Travis,Serrano,79,male,181.1,82.1,O-,2025-04-16T07:34:02.748156,37.1,91,141,85,98.0,14,21.0,537,38.1,,,,0.8,,,,,505,7.1,,25.0,
+P-00035,Brian,Munoz,24,male,182.9,91.0,B+,2025-04-16T07:34:02.748156,36.7,53,118,70,97.7,14,28.7,376,43.0,,,0.29,1.4,5,5,,,339,4.8,56,27.2,
+P-00036,Kenneth,Henry,37,male,169.8,73.9,B-,2025-04-16T07:34:02.748156,36.1,81,123,80,97.7,18,21.0,522,41.9,499,,,,,8,29,24,478,7.9,67,25.6,
+P-00037,Andrea,Castaneda,27,female,154.7,74.5,B+,2025-04-16T07:34:02.748156,36.5,79,112,72,99.3,12,21.0,547,,462,138,,,,,,,510,11.5,70,31.1,
+P-00038,Melissa,Riggs,50,female,166.2,64.8,AB-,2025-04-16T07:34:02.748156,37.0,68,137,85,99.2,17,21.0,364,,,,,,,,,,342,6.3,,23.5,1
+P-00039,Juan,Moore,88,other,177.3,73.5,AB+,2025-04-16T07:34:02.748156,36.9,80,131,96,93.2,20,21.0,485,,,,,,,,,,451,7.1,,23.4,1
+P-00040,Monique,Williamson,21,female,170.6,65.4,O-,2025-04-16T07:34:02.748156,36.7,67,121,80,99.3,18,21.0,357,,,,,,,5,,,349,5.8,,22.5,
+P-00041,David,Stanley,46,other,173.5,70.9,O-,2025-04-16T07:34:02.748156,37.0,82,113,71,98.4,17,21.0,416,35.6,,,,,,,28,,405,6.4,,23.6,
+P-00042,Ryan,Jenkins,19,male,176.1,89.8,A+,2025-04-16T07:34:02.748156,36.3,68,117,76,97.9,13,21.0,462,,,,,1.3,,,28,,459,6.5,,29.0,
+P-00043,Kathleen,Jenkins,33,female,171.9,83.4,O-,2025-04-16T07:34:02.748156,36.4,67,125,76,96.4,15,21.0,435,35.7,,,,1.4,,5,,,404,6.9,,28.2,
+P-00044,Sabrina,Ross,31,female,152.1,68.0,AB+,2025-04-16T07:34:02.748156,36.4,67,141,78,98.8,20,21.0,478,,,,,,,,26,25,444,10.6,,29.4,1
+P-00045,Michael,Castaneda,22,male,166.7,85.7,AB+,2025-04-16T07:34:02.748156,36.9,80,115,78,98.2,13,21.0,380,38.8,,,,,,,,,357,6.0,,30.8,
+P-00046,Angela,Marsh,51,female,166.2,67.8,AB-,2025-04-16T07:34:02.749120,37.1,74,129,91,97.6,12,21.0,511,42.2,,,,1.1,,,,,460,8.8,,24.5,
+P-00047,Latoya,Fox,76,other,164.9,55.7,A+,2025-04-16T07:34:02.749120,37.2,98,127,94,98.7,19,21.0,407,37.1,,,,1.2,,,32,32,399,7.2,,20.5,1
+P-00048,Patricia,Friedman,49,female,169.7,61.2,A+,2025-04-16T07:34:02.749120,36.8,40,121,71,99.0,13,21.0,369,35.8,,,,1.5,,,33,,334,6.0,,21.3,
+P-00049,Adrian,Paul,22,male,170.9,92.7,O-,2025-04-16T07:34:02.749120,36.9,73,117,73,95.6,17,33.9,467,42.5,,,0.34,,6,,29,23,452,7.0,,31.7,
+P-00050,Matthew,Morgan,67,male,187.4,90.1,O+,2025-04-16T07:34:02.749120,37.1,45,143,93,99.3,19,56.3,440,,,,0.56,,10,5,29,,409,5.4,,25.7,1
diff --git a/instance/uploads/20250416150623_patients_data.csv b/instance/uploads/20250416150623_patients_data.csv
new file mode 100644
index 0000000000000000000000000000000000000000..fcdcdec0745556220bb3e6897cf464a7c513b793
--- /dev/null
+++ b/instance/uploads/20250416150623_patients_data.csv
@@ -0,0 +1,51 @@
+patientID,firstName,lastName,age,gender,height,weight,blood_type,measurementDateTime,temperature,heart_rate,blood_pressure_systolic,blood_pressure_diastolic,oxygen_saturation,resp_rate,fio2,tidal_vol,end_tidal_co2,feed_vol,feed_vol_adm,fio2_ratio,insp_time,oxygen_flow_rate,peep,pip,sip,tidal_vol_actual,tidal_vol_kg,tidal_vol_spon,bmi,referral
+P-00001,Alexandra,Hobbs,24,female,174.0,62.6,O-,2025-04-16T07:34:02.746051,36.7,75,125,71,92.6,13,21.0,492,39.7,,,,1.1,,,,,455,7.6,,20.7,
+P-00002,Sean,Raymond,52,male,178.0,74.9,AB+,2025-04-16T07:34:02.746051,36.7,80,146,93,98.7,13,21.0,534,43.0,,,,1.3,,,,,526,7.3,26,23.6,1
+P-00003,Christina,Garcia,56,female,159.2,52.8,A+,2025-04-16T07:34:02.746051,36.3,70,130,93,97.0,19,33.4,419,37.6,,,0.33,1.3,9,,,,400,8.1,,20.8,1
+P-00004,Emily,Payne,63,female,167.6,66.6,AB+,2025-04-16T07:34:02.746051,36.7,97,151,81,98.0,17,21.0,402,,,,,,,,32,,387,6.8,,23.7,1
+P-00005,Jessica,Taylor,85,female,150.9,60.1,AB-,2025-04-16T07:34:02.746051,37.9,107,148,97,98.7,16,21.0,420,42.8,,,,1.1,,7,28,,412,9.5,22,26.4,
+P-00006,Brianna,Jacobs,69,female,152.9,57.9,AB+,2025-04-16T07:34:02.746051,36.9,70,139,103,94.5,14,59.9,364,37.8,,,0.6,1.1,4,,,,345,7.9,48,24.8,1
+P-00007,Kevin,Martin,28,male,161.7,64.6,AB-,2025-04-16T07:34:02.746051,36.1,66,120,80,97.0,12,21.0,444,36.1,,,,1.4,,4,,,401,7.6,,24.7,
+P-00008,Cheyenne,York,87,female,157.9,68.8,B-,2025-04-16T07:34:02.746051,36.9,79,125,90,98.8,14,21.0,423,36.5,,,,,,,30,30,397,8.4,28,27.6,
+P-00009,Rebecca,Carpenter,49,female,166.8,73.3,AB+,2025-04-16T07:34:02.746051,36.6,50,140,83,94.9,12,21.0,415,36.3,,,,,,,,,403,7.1,,26.3,1
+P-00010,Carl,Bell,53,male,178.4,68.4,O-,2025-04-16T07:34:02.746051,37.2,84,135,89,97.1,16,21.0,520,,,,,1.5,,7,,,513,7.1,,21.5,
+P-00011,Rachel,Butler,27,female,167.2,66.2,AB-,2025-04-16T07:34:02.746051,36.5,45,117,80,97.8,18,21.0,428,,,,,,,6,,,411,7.3,77,23.7,
+P-00012,Megan,Soto,59,female,168.5,51.3,O+,2025-04-16T07:34:02.746051,36.5,80,138,86,97.5,20,21.0,489,,,,,,,,,,485,8.1,,18.1,
+P-00013,Brandon,Horton,63,male,185.0,74.7,B+,2025-04-16T07:34:02.746051,37.4,79,130,81,96.5,20,40.4,387,39.6,,,0.4,0.9,8,,,,377,4.9,,21.8,
+P-00014,Mandy,Perry,77,female,163.1,68.3,B-,2025-04-16T07:34:02.747129,37.0,105,132,83,94.9,18,21.0,430,,396,221,,1.5,,,,,402,7.8,,25.7,1
+P-00015,Melissa,Wallace,38,female,159.5,65.2,O+,2025-04-16T07:34:02.747129,37.3,60,113,75,97.0,12,21.0,383,41.1,,,,0.9,,,,,378,7.4,,25.6,
+P-00016,Jeffery,Prince,45,male,177.0,65.1,A-,2025-04-16T07:34:02.747129,37.0,69,120,76,96.6,19,21.0,482,,,,,0.9,,8,,,481,6.7,,20.8,
+P-00017,Dillon,Perez,24,male,169.6,89.1,O+,2025-04-16T07:34:02.747129,36.8,78,117,77,99.4,18,21.0,409,,473,82,,1.2,,5,29,27,383,6.2,79,31.0,
+P-00018,Jessica,Peterson,85,female,168.7,74.5,B-,2025-04-16T07:34:02.747129,37.7,81,138,94,97.6,14,21.0,513,43.9,,,,1.1,,4,,,494,8.5,,26.2,1
+P-00019,Stephen,Wood,86,male,168.9,63.9,O+,2025-04-16T07:34:02.747129,36.9,81,148,91,96.2,13,52.7,365,42.3,,,0.53,1.4,4,8,27,23,331,5.6,,22.4,
+P-00020,Jessica,Fields,66,female,159.1,65.8,A-,2025-04-16T07:34:02.747129,36.9,106,140,85,97.4,19,21.0,385,,358,156,,,,,,,385,7.5,,26.0,
+P-00021,Kevin,Keller,24,male,174.1,53.5,A+,2025-04-16T07:34:02.747129,37.1,65,114,83,96.3,13,21.0,442,,,,,,,,,,423,6.3,,17.7,
+P-00022,Susan,Tucker,42,other,177.0,81.3,A+,2025-04-16T07:34:02.747129,36.7,73,124,83,97.7,16,21.0,396,,,,,,,6,,,361,5.8,77,26.0,
+P-00023,Justin,Miller,26,male,176.8,103.4,O+,2025-04-16T07:34:02.747129,37.0,80,123,71,97.3,12,21.0,382,42.1,349,318,,,,5,,,355,5.3,,33.1,1
+P-00024,Andrea,Webster,87,other,158.7,55.9,AB+,2025-04-16T07:34:02.747129,36.4,96,138,91,96.8,14,21.0,506,,248,208,,1.5,,,29,29,460,9.9,,22.2,
+P-00025,Matthew,Griffin,58,male,168.1,76.1,AB+,2025-04-16T07:34:02.747129,37.1,84,147,101,96.6,15,21.0,362,40.9,,,,,,,35,30,355,5.6,,26.9,
+P-00026,Andrew,Douglas,63,male,180.6,102.1,AB+,2025-04-16T07:34:02.747129,36.4,103,126,85,99.4,12,21.0,372,36.7,228,168,,1.1,,,31,22,346,4.9,,31.3,
+P-00027,Anthony,Bruce,19,male,161.8,83.0,A-,2025-04-16T07:34:02.747129,36.2,40,116,81,98.4,17,48.1,397,36.5,287,273,0.48,0.8,7,8,,,388,6.8,,31.7,
+P-00028,John,Gray,30,male,183.6,71.7,O+,2025-04-16T07:34:02.747129,36.1,71,113,78,99.0,20,21.0,467,40.5,,,,,,4,,,442,6.0,,21.3,1
+P-00029,Casey,Riley,86,male,179.9,90.6,B-,2025-04-16T07:34:02.747129,36.8,94,144,100,97.3,17,21.0,439,35.6,,,,1.4,,8,29,,434,5.9,,28.0,
+P-00030,Jessica,Williams,69,female,171.3,63.3,AB+,2025-04-16T07:34:02.747129,37.0,99,143,91,98.3,14,21.0,369,35.7,,,,,,,,,356,5.9,,21.6,
+P-00031,Madison,Bell,56,female,160.6,71.5,O+,2025-04-16T07:34:02.748156,36.3,65,137,95,96.2,16,53.4,468,44.9,,,0.53,,5,5,,,450,8.8,,27.7,
+P-00032,Debra,Wilson,78,female,172.5,78.5,AB-,2025-04-16T07:34:02.748156,36.4,88,138,85,97.6,17,21.0,535,35.8,,,,1.2,,5,26,,499,8.4,,26.4,
+P-00033,Andrew,Lewis,73,male,170.9,98.3,A+,2025-04-16T07:34:02.748156,38.0,90,142,87,99.3,15,21.0,456,44.2,,,,1.1,,,,,450,6.8,,33.7,
+P-00034,Travis,Serrano,79,male,181.1,82.1,O-,2025-04-16T07:34:02.748156,37.1,91,141,85,98.0,14,21.0,537,38.1,,,,0.8,,,,,505,7.1,,25.0,
+P-00035,Brian,Munoz,24,male,182.9,91.0,B+,2025-04-16T07:34:02.748156,36.7,53,118,70,97.7,14,28.7,376,43.0,,,0.29,1.4,5,5,,,339,4.8,56,27.2,
+P-00036,Kenneth,Henry,37,male,169.8,73.9,B-,2025-04-16T07:34:02.748156,36.1,81,123,80,97.7,18,21.0,522,41.9,499,,,,,8,29,24,478,7.9,67,25.6,
+P-00037,Andrea,Castaneda,27,female,154.7,74.5,B+,2025-04-16T07:34:02.748156,36.5,79,112,72,99.3,12,21.0,547,,462,138,,,,,,,510,11.5,70,31.1,
+P-00038,Melissa,Riggs,50,female,166.2,64.8,AB-,2025-04-16T07:34:02.748156,37.0,68,137,85,99.2,17,21.0,364,,,,,,,,,,342,6.3,,23.5,1
+P-00039,Juan,Moore,88,other,177.3,73.5,AB+,2025-04-16T07:34:02.748156,36.9,80,131,96,93.2,20,21.0,485,,,,,,,,,,451,7.1,,23.4,1
+P-00040,Monique,Williamson,21,female,170.6,65.4,O-,2025-04-16T07:34:02.748156,36.7,67,121,80,99.3,18,21.0,357,,,,,,,5,,,349,5.8,,22.5,
+P-00041,David,Stanley,46,other,173.5,70.9,O-,2025-04-16T07:34:02.748156,37.0,82,113,71,98.4,17,21.0,416,35.6,,,,,,,28,,405,6.4,,23.6,
+P-00042,Ryan,Jenkins,19,male,176.1,89.8,A+,2025-04-16T07:34:02.748156,36.3,68,117,76,97.9,13,21.0,462,,,,,1.3,,,28,,459,6.5,,29.0,
+P-00043,Kathleen,Jenkins,33,female,171.9,83.4,O-,2025-04-16T07:34:02.748156,36.4,67,125,76,96.4,15,21.0,435,35.7,,,,1.4,,5,,,404,6.9,,28.2,
+P-00044,Sabrina,Ross,31,female,152.1,68.0,AB+,2025-04-16T07:34:02.748156,36.4,67,141,78,98.8,20,21.0,478,,,,,,,,26,25,444,10.6,,29.4,1
+P-00045,Michael,Castaneda,22,male,166.7,85.7,AB+,2025-04-16T07:34:02.748156,36.9,80,115,78,98.2,13,21.0,380,38.8,,,,,,,,,357,6.0,,30.8,
+P-00046,Angela,Marsh,51,female,166.2,67.8,AB-,2025-04-16T07:34:02.749120,37.1,74,129,91,97.6,12,21.0,511,42.2,,,,1.1,,,,,460,8.8,,24.5,
+P-00047,Latoya,Fox,76,other,164.9,55.7,A+,2025-04-16T07:34:02.749120,37.2,98,127,94,98.7,19,21.0,407,37.1,,,,1.2,,,32,32,399,7.2,,20.5,1
+P-00048,Patricia,Friedman,49,female,169.7,61.2,A+,2025-04-16T07:34:02.749120,36.8,40,121,71,99.0,13,21.0,369,35.8,,,,1.5,,,33,,334,6.0,,21.3,
+P-00049,Adrian,Paul,22,male,170.9,92.7,O-,2025-04-16T07:34:02.749120,36.9,73,117,73,95.6,17,33.9,467,42.5,,,0.34,,6,,29,23,452,7.0,,31.7,
+P-00050,Matthew,Morgan,67,male,187.4,90.1,O+,2025-04-16T07:34:02.749120,37.1,45,143,93,99.3,19,56.3,440,,,,0.56,,10,5,29,,409,5.4,,25.7,1
diff --git a/instance/uploads/20250416152626_patients_data.csv b/instance/uploads/20250416152626_patients_data.csv
new file mode 100644
index 0000000000000000000000000000000000000000..fcdcdec0745556220bb3e6897cf464a7c513b793
--- /dev/null
+++ b/instance/uploads/20250416152626_patients_data.csv
@@ -0,0 +1,51 @@
+patientID,firstName,lastName,age,gender,height,weight,blood_type,measurementDateTime,temperature,heart_rate,blood_pressure_systolic,blood_pressure_diastolic,oxygen_saturation,resp_rate,fio2,tidal_vol,end_tidal_co2,feed_vol,feed_vol_adm,fio2_ratio,insp_time,oxygen_flow_rate,peep,pip,sip,tidal_vol_actual,tidal_vol_kg,tidal_vol_spon,bmi,referral
+P-00001,Alexandra,Hobbs,24,female,174.0,62.6,O-,2025-04-16T07:34:02.746051,36.7,75,125,71,92.6,13,21.0,492,39.7,,,,1.1,,,,,455,7.6,,20.7,
+P-00002,Sean,Raymond,52,male,178.0,74.9,AB+,2025-04-16T07:34:02.746051,36.7,80,146,93,98.7,13,21.0,534,43.0,,,,1.3,,,,,526,7.3,26,23.6,1
+P-00003,Christina,Garcia,56,female,159.2,52.8,A+,2025-04-16T07:34:02.746051,36.3,70,130,93,97.0,19,33.4,419,37.6,,,0.33,1.3,9,,,,400,8.1,,20.8,1
+P-00004,Emily,Payne,63,female,167.6,66.6,AB+,2025-04-16T07:34:02.746051,36.7,97,151,81,98.0,17,21.0,402,,,,,,,,32,,387,6.8,,23.7,1
+P-00005,Jessica,Taylor,85,female,150.9,60.1,AB-,2025-04-16T07:34:02.746051,37.9,107,148,97,98.7,16,21.0,420,42.8,,,,1.1,,7,28,,412,9.5,22,26.4,
+P-00006,Brianna,Jacobs,69,female,152.9,57.9,AB+,2025-04-16T07:34:02.746051,36.9,70,139,103,94.5,14,59.9,364,37.8,,,0.6,1.1,4,,,,345,7.9,48,24.8,1
+P-00007,Kevin,Martin,28,male,161.7,64.6,AB-,2025-04-16T07:34:02.746051,36.1,66,120,80,97.0,12,21.0,444,36.1,,,,1.4,,4,,,401,7.6,,24.7,
+P-00008,Cheyenne,York,87,female,157.9,68.8,B-,2025-04-16T07:34:02.746051,36.9,79,125,90,98.8,14,21.0,423,36.5,,,,,,,30,30,397,8.4,28,27.6,
+P-00009,Rebecca,Carpenter,49,female,166.8,73.3,AB+,2025-04-16T07:34:02.746051,36.6,50,140,83,94.9,12,21.0,415,36.3,,,,,,,,,403,7.1,,26.3,1
+P-00010,Carl,Bell,53,male,178.4,68.4,O-,2025-04-16T07:34:02.746051,37.2,84,135,89,97.1,16,21.0,520,,,,,1.5,,7,,,513,7.1,,21.5,
+P-00011,Rachel,Butler,27,female,167.2,66.2,AB-,2025-04-16T07:34:02.746051,36.5,45,117,80,97.8,18,21.0,428,,,,,,,6,,,411,7.3,77,23.7,
+P-00012,Megan,Soto,59,female,168.5,51.3,O+,2025-04-16T07:34:02.746051,36.5,80,138,86,97.5,20,21.0,489,,,,,,,,,,485,8.1,,18.1,
+P-00013,Brandon,Horton,63,male,185.0,74.7,B+,2025-04-16T07:34:02.746051,37.4,79,130,81,96.5,20,40.4,387,39.6,,,0.4,0.9,8,,,,377,4.9,,21.8,
+P-00014,Mandy,Perry,77,female,163.1,68.3,B-,2025-04-16T07:34:02.747129,37.0,105,132,83,94.9,18,21.0,430,,396,221,,1.5,,,,,402,7.8,,25.7,1
+P-00015,Melissa,Wallace,38,female,159.5,65.2,O+,2025-04-16T07:34:02.747129,37.3,60,113,75,97.0,12,21.0,383,41.1,,,,0.9,,,,,378,7.4,,25.6,
+P-00016,Jeffery,Prince,45,male,177.0,65.1,A-,2025-04-16T07:34:02.747129,37.0,69,120,76,96.6,19,21.0,482,,,,,0.9,,8,,,481,6.7,,20.8,
+P-00017,Dillon,Perez,24,male,169.6,89.1,O+,2025-04-16T07:34:02.747129,36.8,78,117,77,99.4,18,21.0,409,,473,82,,1.2,,5,29,27,383,6.2,79,31.0,
+P-00018,Jessica,Peterson,85,female,168.7,74.5,B-,2025-04-16T07:34:02.747129,37.7,81,138,94,97.6,14,21.0,513,43.9,,,,1.1,,4,,,494,8.5,,26.2,1
+P-00019,Stephen,Wood,86,male,168.9,63.9,O+,2025-04-16T07:34:02.747129,36.9,81,148,91,96.2,13,52.7,365,42.3,,,0.53,1.4,4,8,27,23,331,5.6,,22.4,
+P-00020,Jessica,Fields,66,female,159.1,65.8,A-,2025-04-16T07:34:02.747129,36.9,106,140,85,97.4,19,21.0,385,,358,156,,,,,,,385,7.5,,26.0,
+P-00021,Kevin,Keller,24,male,174.1,53.5,A+,2025-04-16T07:34:02.747129,37.1,65,114,83,96.3,13,21.0,442,,,,,,,,,,423,6.3,,17.7,
+P-00022,Susan,Tucker,42,other,177.0,81.3,A+,2025-04-16T07:34:02.747129,36.7,73,124,83,97.7,16,21.0,396,,,,,,,6,,,361,5.8,77,26.0,
+P-00023,Justin,Miller,26,male,176.8,103.4,O+,2025-04-16T07:34:02.747129,37.0,80,123,71,97.3,12,21.0,382,42.1,349,318,,,,5,,,355,5.3,,33.1,1
+P-00024,Andrea,Webster,87,other,158.7,55.9,AB+,2025-04-16T07:34:02.747129,36.4,96,138,91,96.8,14,21.0,506,,248,208,,1.5,,,29,29,460,9.9,,22.2,
+P-00025,Matthew,Griffin,58,male,168.1,76.1,AB+,2025-04-16T07:34:02.747129,37.1,84,147,101,96.6,15,21.0,362,40.9,,,,,,,35,30,355,5.6,,26.9,
+P-00026,Andrew,Douglas,63,male,180.6,102.1,AB+,2025-04-16T07:34:02.747129,36.4,103,126,85,99.4,12,21.0,372,36.7,228,168,,1.1,,,31,22,346,4.9,,31.3,
+P-00027,Anthony,Bruce,19,male,161.8,83.0,A-,2025-04-16T07:34:02.747129,36.2,40,116,81,98.4,17,48.1,397,36.5,287,273,0.48,0.8,7,8,,,388,6.8,,31.7,
+P-00028,John,Gray,30,male,183.6,71.7,O+,2025-04-16T07:34:02.747129,36.1,71,113,78,99.0,20,21.0,467,40.5,,,,,,4,,,442,6.0,,21.3,1
+P-00029,Casey,Riley,86,male,179.9,90.6,B-,2025-04-16T07:34:02.747129,36.8,94,144,100,97.3,17,21.0,439,35.6,,,,1.4,,8,29,,434,5.9,,28.0,
+P-00030,Jessica,Williams,69,female,171.3,63.3,AB+,2025-04-16T07:34:02.747129,37.0,99,143,91,98.3,14,21.0,369,35.7,,,,,,,,,356,5.9,,21.6,
+P-00031,Madison,Bell,56,female,160.6,71.5,O+,2025-04-16T07:34:02.748156,36.3,65,137,95,96.2,16,53.4,468,44.9,,,0.53,,5,5,,,450,8.8,,27.7,
+P-00032,Debra,Wilson,78,female,172.5,78.5,AB-,2025-04-16T07:34:02.748156,36.4,88,138,85,97.6,17,21.0,535,35.8,,,,1.2,,5,26,,499,8.4,,26.4,
+P-00033,Andrew,Lewis,73,male,170.9,98.3,A+,2025-04-16T07:34:02.748156,38.0,90,142,87,99.3,15,21.0,456,44.2,,,,1.1,,,,,450,6.8,,33.7,
+P-00034,Travis,Serrano,79,male,181.1,82.1,O-,2025-04-16T07:34:02.748156,37.1,91,141,85,98.0,14,21.0,537,38.1,,,,0.8,,,,,505,7.1,,25.0,
+P-00035,Brian,Munoz,24,male,182.9,91.0,B+,2025-04-16T07:34:02.748156,36.7,53,118,70,97.7,14,28.7,376,43.0,,,0.29,1.4,5,5,,,339,4.8,56,27.2,
+P-00036,Kenneth,Henry,37,male,169.8,73.9,B-,2025-04-16T07:34:02.748156,36.1,81,123,80,97.7,18,21.0,522,41.9,499,,,,,8,29,24,478,7.9,67,25.6,
+P-00037,Andrea,Castaneda,27,female,154.7,74.5,B+,2025-04-16T07:34:02.748156,36.5,79,112,72,99.3,12,21.0,547,,462,138,,,,,,,510,11.5,70,31.1,
+P-00038,Melissa,Riggs,50,female,166.2,64.8,AB-,2025-04-16T07:34:02.748156,37.0,68,137,85,99.2,17,21.0,364,,,,,,,,,,342,6.3,,23.5,1
+P-00039,Juan,Moore,88,other,177.3,73.5,AB+,2025-04-16T07:34:02.748156,36.9,80,131,96,93.2,20,21.0,485,,,,,,,,,,451,7.1,,23.4,1
+P-00040,Monique,Williamson,21,female,170.6,65.4,O-,2025-04-16T07:34:02.748156,36.7,67,121,80,99.3,18,21.0,357,,,,,,,5,,,349,5.8,,22.5,
+P-00041,David,Stanley,46,other,173.5,70.9,O-,2025-04-16T07:34:02.748156,37.0,82,113,71,98.4,17,21.0,416,35.6,,,,,,,28,,405,6.4,,23.6,
+P-00042,Ryan,Jenkins,19,male,176.1,89.8,A+,2025-04-16T07:34:02.748156,36.3,68,117,76,97.9,13,21.0,462,,,,,1.3,,,28,,459,6.5,,29.0,
+P-00043,Kathleen,Jenkins,33,female,171.9,83.4,O-,2025-04-16T07:34:02.748156,36.4,67,125,76,96.4,15,21.0,435,35.7,,,,1.4,,5,,,404,6.9,,28.2,
+P-00044,Sabrina,Ross,31,female,152.1,68.0,AB+,2025-04-16T07:34:02.748156,36.4,67,141,78,98.8,20,21.0,478,,,,,,,,26,25,444,10.6,,29.4,1
+P-00045,Michael,Castaneda,22,male,166.7,85.7,AB+,2025-04-16T07:34:02.748156,36.9,80,115,78,98.2,13,21.0,380,38.8,,,,,,,,,357,6.0,,30.8,
+P-00046,Angela,Marsh,51,female,166.2,67.8,AB-,2025-04-16T07:34:02.749120,37.1,74,129,91,97.6,12,21.0,511,42.2,,,,1.1,,,,,460,8.8,,24.5,
+P-00047,Latoya,Fox,76,other,164.9,55.7,A+,2025-04-16T07:34:02.749120,37.2,98,127,94,98.7,19,21.0,407,37.1,,,,1.2,,,32,32,399,7.2,,20.5,1
+P-00048,Patricia,Friedman,49,female,169.7,61.2,A+,2025-04-16T07:34:02.749120,36.8,40,121,71,99.0,13,21.0,369,35.8,,,,1.5,,,33,,334,6.0,,21.3,
+P-00049,Adrian,Paul,22,male,170.9,92.7,O-,2025-04-16T07:34:02.749120,36.9,73,117,73,95.6,17,33.9,467,42.5,,,0.34,,6,,29,23,452,7.0,,31.7,
+P-00050,Matthew,Morgan,67,male,187.4,90.1,O+,2025-04-16T07:34:02.749120,37.1,45,143,93,99.3,19,56.3,440,,,,0.56,,10,5,29,,409,5.4,,25.7,1
diff --git "a/migrations/versions/33d355767436_thay_\304\221\341\273\225i_\304\221\341\273\231_d\303\240i_c\341\273\231t_status_trong_b\341\272\243ng_.py" "b/migrations/versions/33d355767436_thay_\304\221\341\273\225i_\304\221\341\273\231_d\303\240i_c\341\273\231t_status_trong_b\341\272\243ng_.py"
new file mode 100644
index 0000000000000000000000000000000000000000..d7527f1db477a4fa36f164f651a65d1b54870b38
--- /dev/null
+++ "b/migrations/versions/33d355767436_thay_\304\221\341\273\225i_\304\221\341\273\231_d\303\240i_c\341\273\231t_status_trong_b\341\272\243ng_.py"
@@ -0,0 +1,46 @@
+"""Thay đổi độ dài cột status trong bảng uploadedfiles
+
+Revision ID: 33d355767436
+Revises: 78006a2a15a0
+Create Date: 2025-04-16 18:32:35.386926
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import mysql
+
+# revision identifiers, used by Alembic.
+revision = '33d355767436'
+down_revision = '78006a2a15a0'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    with op.batch_alter_table('uploadedfiles', schema=None) as batch_op:
+        batch_op.alter_column('status',
+               existing_type=mysql.VARCHAR(length=20),
+               type_=sa.String(length=50),
+               existing_nullable=True)
+        batch_op.alter_column('error_details',
+               existing_type=mysql.LONGTEXT(),
+               type_=sa.Text(length=16777215),
+               existing_nullable=True)
+
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    with op.batch_alter_table('uploadedfiles', schema=None) as batch_op:
+        batch_op.alter_column('error_details',
+               existing_type=sa.Text(length=16777215),
+               type_=mysql.LONGTEXT(),
+               existing_nullable=True)
+        batch_op.alter_column('status',
+               existing_type=sa.String(length=50),
+               type_=mysql.VARCHAR(length=20),
+               existing_nullable=True)
+
+    # ### end Alembic commands ###
diff --git "a/migrations/versions/78006a2a15a0_t\341\272\241o_l\341\272\241i_migration.py" "b/migrations/versions/78006a2a15a0_t\341\272\241o_l\341\272\241i_migration.py"
new file mode 100644
index 0000000000000000000000000000000000000000..9500b5951dfd3c724c811cef249c9d64353430e5
--- /dev/null
+++ "b/migrations/versions/78006a2a15a0_t\341\272\241o_l\341\272\241i_migration.py"
@@ -0,0 +1,38 @@
+"""Tạo lại migration
+
+Revision ID: 78006a2a15a0
+Revises: 
+Create Date: 2025-04-16 18:25:19.976243
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import mysql
+
+# revision identifiers, used by Alembic.
+revision = '78006a2a15a0'
+down_revision = None
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    with op.batch_alter_table('uploadedfiles', schema=None) as batch_op:
+        batch_op.alter_column('error_details',
+               existing_type=mysql.LONGTEXT(),
+               type_=sa.Text(length=16777215),
+               existing_nullable=True)
+
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    with op.batch_alter_table('uploadedfiles', schema=None) as batch_op:
+        batch_op.alter_column('error_details',
+               existing_type=sa.Text(length=16777215),
+               type_=mysql.LONGTEXT(),
+               existing_nullable=True)
+
+    # ### end Alembic commands ###
diff --git a/migrations/versions/cbc9db9dd958_update_database_schema_to_match_models.py b/migrations/versions/cbc9db9dd958_update_database_schema_to_match_models.py
deleted file mode 100644
index 929010b2abd919bb618b473a30207b5fbac5e3b9..0000000000000000000000000000000000000000
--- a/migrations/versions/cbc9db9dd958_update_database_schema_to_match_models.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""Update database schema to match models
-
-Revision ID: cbc9db9dd958
-Revises: 
-Create Date: 2025-04-13 19:06:46.600555
-
-"""
-from alembic import op
-import sqlalchemy as sa
-from sqlalchemy.dialects import mysql
-
-# revision identifiers, used by Alembic.
-revision = 'cbc9db9dd958'
-down_revision = None
-branch_labels = None
-depends_on = None
-
-
-def upgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    op.create_table('activity_logs',
-    sa.Column('id', sa.Integer(), nullable=False),
-    sa.Column('user_id', sa.Integer(), nullable=False),
-    sa.Column('action', sa.String(length=255), nullable=False),
-    sa.Column('details', sa.Text(), nullable=True),
-    sa.Column('timestamp', sa.DateTime(), nullable=True),
-    sa.ForeignKeyConstraint(['user_id'], ['users.userID'], ),
-    sa.PrimaryKeyConstraint('id')
-    )
-    with op.batch_alter_table('users', schema=None) as batch_op:
-        batch_op.add_column(sa.Column('firstName', sa.String(length=50), nullable=False))
-        batch_op.add_column(sa.Column('lastName', sa.String(length=50), nullable=False))
-        batch_op.add_column(sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True))
-        batch_op.add_column(sa.Column('last_login', sa.DateTime(timezone=True), nullable=True))
-        batch_op.alter_column('password_hash',
-               existing_type=mysql.VARCHAR(length=255),
-               type_=sa.String(length=128),
-               nullable=True)
-        batch_op.drop_index('username')
-        batch_op.drop_column('username')
-
-    # ### end Alembic commands ###
-
-
-def downgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    with op.batch_alter_table('users', schema=None) as batch_op:
-        batch_op.add_column(sa.Column('username', mysql.VARCHAR(length=50), nullable=False))
-        batch_op.create_index('username', ['username'], unique=True)
-        batch_op.alter_column('password_hash',
-               existing_type=sa.String(length=128),
-               type_=mysql.VARCHAR(length=255),
-               nullable=False)
-        batch_op.drop_column('last_login')
-        batch_op.drop_column('created_at')
-        batch_op.drop_column('lastName')
-        batch_op.drop_column('firstName')
-
-    op.drop_table('activity_logs')
-    # ### end Alembic commands ###
diff --git a/patients_data.csv b/patients_data.csv
new file mode 100644
index 0000000000000000000000000000000000000000..fcdcdec0745556220bb3e6897cf464a7c513b793
--- /dev/null
+++ b/patients_data.csv
@@ -0,0 +1,51 @@
+patientID,firstName,lastName,age,gender,height,weight,blood_type,measurementDateTime,temperature,heart_rate,blood_pressure_systolic,blood_pressure_diastolic,oxygen_saturation,resp_rate,fio2,tidal_vol,end_tidal_co2,feed_vol,feed_vol_adm,fio2_ratio,insp_time,oxygen_flow_rate,peep,pip,sip,tidal_vol_actual,tidal_vol_kg,tidal_vol_spon,bmi,referral
+P-00001,Alexandra,Hobbs,24,female,174.0,62.6,O-,2025-04-16T07:34:02.746051,36.7,75,125,71,92.6,13,21.0,492,39.7,,,,1.1,,,,,455,7.6,,20.7,
+P-00002,Sean,Raymond,52,male,178.0,74.9,AB+,2025-04-16T07:34:02.746051,36.7,80,146,93,98.7,13,21.0,534,43.0,,,,1.3,,,,,526,7.3,26,23.6,1
+P-00003,Christina,Garcia,56,female,159.2,52.8,A+,2025-04-16T07:34:02.746051,36.3,70,130,93,97.0,19,33.4,419,37.6,,,0.33,1.3,9,,,,400,8.1,,20.8,1
+P-00004,Emily,Payne,63,female,167.6,66.6,AB+,2025-04-16T07:34:02.746051,36.7,97,151,81,98.0,17,21.0,402,,,,,,,,32,,387,6.8,,23.7,1
+P-00005,Jessica,Taylor,85,female,150.9,60.1,AB-,2025-04-16T07:34:02.746051,37.9,107,148,97,98.7,16,21.0,420,42.8,,,,1.1,,7,28,,412,9.5,22,26.4,
+P-00006,Brianna,Jacobs,69,female,152.9,57.9,AB+,2025-04-16T07:34:02.746051,36.9,70,139,103,94.5,14,59.9,364,37.8,,,0.6,1.1,4,,,,345,7.9,48,24.8,1
+P-00007,Kevin,Martin,28,male,161.7,64.6,AB-,2025-04-16T07:34:02.746051,36.1,66,120,80,97.0,12,21.0,444,36.1,,,,1.4,,4,,,401,7.6,,24.7,
+P-00008,Cheyenne,York,87,female,157.9,68.8,B-,2025-04-16T07:34:02.746051,36.9,79,125,90,98.8,14,21.0,423,36.5,,,,,,,30,30,397,8.4,28,27.6,
+P-00009,Rebecca,Carpenter,49,female,166.8,73.3,AB+,2025-04-16T07:34:02.746051,36.6,50,140,83,94.9,12,21.0,415,36.3,,,,,,,,,403,7.1,,26.3,1
+P-00010,Carl,Bell,53,male,178.4,68.4,O-,2025-04-16T07:34:02.746051,37.2,84,135,89,97.1,16,21.0,520,,,,,1.5,,7,,,513,7.1,,21.5,
+P-00011,Rachel,Butler,27,female,167.2,66.2,AB-,2025-04-16T07:34:02.746051,36.5,45,117,80,97.8,18,21.0,428,,,,,,,6,,,411,7.3,77,23.7,
+P-00012,Megan,Soto,59,female,168.5,51.3,O+,2025-04-16T07:34:02.746051,36.5,80,138,86,97.5,20,21.0,489,,,,,,,,,,485,8.1,,18.1,
+P-00013,Brandon,Horton,63,male,185.0,74.7,B+,2025-04-16T07:34:02.746051,37.4,79,130,81,96.5,20,40.4,387,39.6,,,0.4,0.9,8,,,,377,4.9,,21.8,
+P-00014,Mandy,Perry,77,female,163.1,68.3,B-,2025-04-16T07:34:02.747129,37.0,105,132,83,94.9,18,21.0,430,,396,221,,1.5,,,,,402,7.8,,25.7,1
+P-00015,Melissa,Wallace,38,female,159.5,65.2,O+,2025-04-16T07:34:02.747129,37.3,60,113,75,97.0,12,21.0,383,41.1,,,,0.9,,,,,378,7.4,,25.6,
+P-00016,Jeffery,Prince,45,male,177.0,65.1,A-,2025-04-16T07:34:02.747129,37.0,69,120,76,96.6,19,21.0,482,,,,,0.9,,8,,,481,6.7,,20.8,
+P-00017,Dillon,Perez,24,male,169.6,89.1,O+,2025-04-16T07:34:02.747129,36.8,78,117,77,99.4,18,21.0,409,,473,82,,1.2,,5,29,27,383,6.2,79,31.0,
+P-00018,Jessica,Peterson,85,female,168.7,74.5,B-,2025-04-16T07:34:02.747129,37.7,81,138,94,97.6,14,21.0,513,43.9,,,,1.1,,4,,,494,8.5,,26.2,1
+P-00019,Stephen,Wood,86,male,168.9,63.9,O+,2025-04-16T07:34:02.747129,36.9,81,148,91,96.2,13,52.7,365,42.3,,,0.53,1.4,4,8,27,23,331,5.6,,22.4,
+P-00020,Jessica,Fields,66,female,159.1,65.8,A-,2025-04-16T07:34:02.747129,36.9,106,140,85,97.4,19,21.0,385,,358,156,,,,,,,385,7.5,,26.0,
+P-00021,Kevin,Keller,24,male,174.1,53.5,A+,2025-04-16T07:34:02.747129,37.1,65,114,83,96.3,13,21.0,442,,,,,,,,,,423,6.3,,17.7,
+P-00022,Susan,Tucker,42,other,177.0,81.3,A+,2025-04-16T07:34:02.747129,36.7,73,124,83,97.7,16,21.0,396,,,,,,,6,,,361,5.8,77,26.0,
+P-00023,Justin,Miller,26,male,176.8,103.4,O+,2025-04-16T07:34:02.747129,37.0,80,123,71,97.3,12,21.0,382,42.1,349,318,,,,5,,,355,5.3,,33.1,1
+P-00024,Andrea,Webster,87,other,158.7,55.9,AB+,2025-04-16T07:34:02.747129,36.4,96,138,91,96.8,14,21.0,506,,248,208,,1.5,,,29,29,460,9.9,,22.2,
+P-00025,Matthew,Griffin,58,male,168.1,76.1,AB+,2025-04-16T07:34:02.747129,37.1,84,147,101,96.6,15,21.0,362,40.9,,,,,,,35,30,355,5.6,,26.9,
+P-00026,Andrew,Douglas,63,male,180.6,102.1,AB+,2025-04-16T07:34:02.747129,36.4,103,126,85,99.4,12,21.0,372,36.7,228,168,,1.1,,,31,22,346,4.9,,31.3,
+P-00027,Anthony,Bruce,19,male,161.8,83.0,A-,2025-04-16T07:34:02.747129,36.2,40,116,81,98.4,17,48.1,397,36.5,287,273,0.48,0.8,7,8,,,388,6.8,,31.7,
+P-00028,John,Gray,30,male,183.6,71.7,O+,2025-04-16T07:34:02.747129,36.1,71,113,78,99.0,20,21.0,467,40.5,,,,,,4,,,442,6.0,,21.3,1
+P-00029,Casey,Riley,86,male,179.9,90.6,B-,2025-04-16T07:34:02.747129,36.8,94,144,100,97.3,17,21.0,439,35.6,,,,1.4,,8,29,,434,5.9,,28.0,
+P-00030,Jessica,Williams,69,female,171.3,63.3,AB+,2025-04-16T07:34:02.747129,37.0,99,143,91,98.3,14,21.0,369,35.7,,,,,,,,,356,5.9,,21.6,
+P-00031,Madison,Bell,56,female,160.6,71.5,O+,2025-04-16T07:34:02.748156,36.3,65,137,95,96.2,16,53.4,468,44.9,,,0.53,,5,5,,,450,8.8,,27.7,
+P-00032,Debra,Wilson,78,female,172.5,78.5,AB-,2025-04-16T07:34:02.748156,36.4,88,138,85,97.6,17,21.0,535,35.8,,,,1.2,,5,26,,499,8.4,,26.4,
+P-00033,Andrew,Lewis,73,male,170.9,98.3,A+,2025-04-16T07:34:02.748156,38.0,90,142,87,99.3,15,21.0,456,44.2,,,,1.1,,,,,450,6.8,,33.7,
+P-00034,Travis,Serrano,79,male,181.1,82.1,O-,2025-04-16T07:34:02.748156,37.1,91,141,85,98.0,14,21.0,537,38.1,,,,0.8,,,,,505,7.1,,25.0,
+P-00035,Brian,Munoz,24,male,182.9,91.0,B+,2025-04-16T07:34:02.748156,36.7,53,118,70,97.7,14,28.7,376,43.0,,,0.29,1.4,5,5,,,339,4.8,56,27.2,
+P-00036,Kenneth,Henry,37,male,169.8,73.9,B-,2025-04-16T07:34:02.748156,36.1,81,123,80,97.7,18,21.0,522,41.9,499,,,,,8,29,24,478,7.9,67,25.6,
+P-00037,Andrea,Castaneda,27,female,154.7,74.5,B+,2025-04-16T07:34:02.748156,36.5,79,112,72,99.3,12,21.0,547,,462,138,,,,,,,510,11.5,70,31.1,
+P-00038,Melissa,Riggs,50,female,166.2,64.8,AB-,2025-04-16T07:34:02.748156,37.0,68,137,85,99.2,17,21.0,364,,,,,,,,,,342,6.3,,23.5,1
+P-00039,Juan,Moore,88,other,177.3,73.5,AB+,2025-04-16T07:34:02.748156,36.9,80,131,96,93.2,20,21.0,485,,,,,,,,,,451,7.1,,23.4,1
+P-00040,Monique,Williamson,21,female,170.6,65.4,O-,2025-04-16T07:34:02.748156,36.7,67,121,80,99.3,18,21.0,357,,,,,,,5,,,349,5.8,,22.5,
+P-00041,David,Stanley,46,other,173.5,70.9,O-,2025-04-16T07:34:02.748156,37.0,82,113,71,98.4,17,21.0,416,35.6,,,,,,,28,,405,6.4,,23.6,
+P-00042,Ryan,Jenkins,19,male,176.1,89.8,A+,2025-04-16T07:34:02.748156,36.3,68,117,76,97.9,13,21.0,462,,,,,1.3,,,28,,459,6.5,,29.0,
+P-00043,Kathleen,Jenkins,33,female,171.9,83.4,O-,2025-04-16T07:34:02.748156,36.4,67,125,76,96.4,15,21.0,435,35.7,,,,1.4,,5,,,404,6.9,,28.2,
+P-00044,Sabrina,Ross,31,female,152.1,68.0,AB+,2025-04-16T07:34:02.748156,36.4,67,141,78,98.8,20,21.0,478,,,,,,,,26,25,444,10.6,,29.4,1
+P-00045,Michael,Castaneda,22,male,166.7,85.7,AB+,2025-04-16T07:34:02.748156,36.9,80,115,78,98.2,13,21.0,380,38.8,,,,,,,,,357,6.0,,30.8,
+P-00046,Angela,Marsh,51,female,166.2,67.8,AB-,2025-04-16T07:34:02.749120,37.1,74,129,91,97.6,12,21.0,511,42.2,,,,1.1,,,,,460,8.8,,24.5,
+P-00047,Latoya,Fox,76,other,164.9,55.7,A+,2025-04-16T07:34:02.749120,37.2,98,127,94,98.7,19,21.0,407,37.1,,,,1.2,,,32,32,399,7.2,,20.5,1
+P-00048,Patricia,Friedman,49,female,169.7,61.2,A+,2025-04-16T07:34:02.749120,36.8,40,121,71,99.0,13,21.0,369,35.8,,,,1.5,,,33,,334,6.0,,21.3,
+P-00049,Adrian,Paul,22,male,170.9,92.7,O-,2025-04-16T07:34:02.749120,36.9,73,117,73,95.6,17,33.9,467,42.5,,,0.34,,6,,29,23,452,7.0,,31.7,
+P-00050,Matthew,Morgan,67,male,187.4,90.1,O+,2025-04-16T07:34:02.749120,37.1,45,143,93,99.3,19,56.3,440,,,,0.56,,10,5,29,,409,5.4,,25.7,1
diff --git a/run.py b/run.py
index b3246be65a01da032ca09b3283e4b03d225dedef..8429f4c9626d6e2124d2dbb804996fe58eeca774 100644
--- a/run.py
+++ b/run.py
@@ -229,7 +229,7 @@ def perform_database_reset(app, db, bcrypt):
                     )
                     db.session.add(dietitian_profile)
                 
-                # Commit all changes
+                # Commit all changes for users and dietitians
                 db.session.commit()
                 print("\n[DB] Database reset completed successfully.")
                 print("[Admin] Default admin account created:")
@@ -246,85 +246,13 @@ def perform_database_reset(app, db, bcrypt):
 
                 print("\nImportant: Please change passwords after logging in!")
                 
-                # --- Add Patients from JSON --- 
-                print("[DB] Adding patients from JSON file...")
-                json_file_path = os.path.join(os.path.dirname(__file__), 'patients_data.json')
-                if not os.path.exists(json_file_path):
-                    print(f"[Warning] patients_data.json not found at {json_file_path}. Skipping patient data loading.")
-                else:
-                    try:
-                        with open(json_file_path, 'r', encoding='utf-8') as f:
-                            patients_json_data = json.load(f)
-                        
-                        patients_added = 0
-                        measurements_added = 0
-                        for patient_data in patients_json_data:
-                            try:
-                                # Create Patient
-                                new_patient = Patient(
-                                    id=patient_data.get('patientID'), 
-                                    firstName=patient_data.get('firstName'),
-                                    lastName=patient_data.get('lastName'),
-                                    age=patient_data.get('age'),
-                                    gender=patient_data.get('gender'),
-                                    height=patient_data.get('height'),
-                                    weight=patient_data.get('weight'),
-                                    blood_type=patient_data.get('blood_type'),
-                                    admission_date=datetime.utcnow() 
-                                )
-                                new_patient.calculate_bmi() 
-                                db.session.add(new_patient)
-                                # Không cần flush vì khóa chính (id) đã có giá trị
-
-                                # Create an initial Encounter for the patient
-                                initial_encounter = Encounter(
-                                    patientID=new_patient.id, # Link using patient's string ID
-                                    admissionDateTime=new_patient.admission_date # Use patient's admission time
-                                    # Các trường khác sẽ là NULL hoặc default
-                                )
-                                db.session.add(initial_encounter)
-                                db.session.flush() # Flush để lấy encounter.id (Integer)
-
-                                # Create Initial Measurement, linking to the new encounter
-                                initial_measurement_data = patient_data.get('initial_measurement', {})
-                                if initial_measurement_data:
-                                    new_measurement = PhysiologicalMeasurement(
-                                        patient_id=new_patient.id, 
-                                        encounter_id=initial_encounter.id, # Sử dụng ID của encounter vừa tạo
-                                        measurementDateTime=datetime.utcnow(),
-                                        temperature=initial_measurement_data.get('temperature'),
-                                        heart_rate=initial_measurement_data.get('heart_rate'),
-                                        blood_pressure_systolic=initial_measurement_data.get('blood_pressure_systolic'),
-                                        blood_pressure_diastolic=initial_measurement_data.get('blood_pressure_diastolic'),
-                                        oxygen_saturation=initial_measurement_data.get('oxygen_saturation'),
-                                        resp_rate=initial_measurement_data.get('resp_rate'),
-                                        fio2=initial_measurement_data.get('fio2'),
-                                        tidal_vol=initial_measurement_data.get('tidal_vol'),
-                                        end_tidal_co2=initial_measurement_data.get('end_tidal_co2'),
-                                        feed_vol=initial_measurement_data.get('feed_vol'),
-                                        peep=initial_measurement_data.get('peep'),
-                                        pip=initial_measurement_data.get('pip')
-                                    )
-                                    db.session.add(new_measurement)
-                                    measurements_added += 1
-                                
-                                patients_added += 1
-                            except Exception as patient_err:
-                                print(f"[Error] Could not add patient {patient_data.get('patientID')}: {patient_err}")
-                                db.session.rollback() 
-                        
-                        db.session.commit() 
-                        print(f"[DB] Successfully added {patients_added} patients and {measurements_added} initial measurements.")
-
-                    except FileNotFoundError:
-                         print(f"[Error] patients_data.json not found at {json_file_path}.")
-                    except json.JSONDecodeError:
-                        print(f"[Error] Could not decode JSON from {json_file_path}.")
-                    except Exception as json_err:
-                        print(f"[Error] Failed loading patient data from JSON: {json_err}")
-                        db.session.rollback()
-
-                # Kết thúc phần thêm bệnh nhân
+                # --- REMOVED Patient Data Loading --- 
+                # Đoạn code tự động thêm bệnh nhân từ JSON/CSV đã bị xóa.
+                # Việc thêm bệnh nhân ban đầu sẽ được thực hiện qua trang Upload.
+                print("\n[DB] Patient data loading from file during reset has been removed.")
+                print("[DB] Please use the Upload page to add initial patient data.")
+                
+                # --- Kết thúc phần thêm bệnh nhân (đã xóa) --- 
 
                 return True
             except Exception as e: