diff --git a/app/routes/patients.py b/app/routes/patients.py index bd4904bfc8a62292ce6d06f3c4be9d6d6eeb7bdc..98deb4d3b61b6f4883186313f50d22e24561c368 100644 --- a/app/routes/patients.py +++ b/app/routes/patients.py @@ -1356,7 +1356,8 @@ def new_encounter(patient_id): create_notification_for_admins( f"New encounter ({custom_id}) for patient {patient.full_name} by {current_user.full_name}.", admin_link, - exclude_user_id=current_user.userID) + exclude_user_id=current_user.userID + ) if (patient.assigned_dietitian_user_id and patient.assigned_dietitian_user_id != current_user.userID): @@ -1480,18 +1481,83 @@ def delete_encounter(patient_id, encounter_pk): create_notification(dietitian_id_assigned, dietitian_message, admin_link) # --- THÊM LOGIC HỦY ASSIGNMENT --- + from app.models.patient_dietitian_assignment import PatientDietitianAssignment # DI CHUYỂN IMPORT LÊN ĐÂY + unassignment_successful = False # Flag để kiểm tra if dietitian_id_assigned: # Nếu encounter bị xóa có gán dietitian active_assignment = PatientDietitianAssignment.get_active_assignment(patient.id) # Chỉ hủy assignment nếu dietitian của assignment đang active trùng với dietitian của encounter bị xóa if active_assignment and active_assignment.dietitian_id == dietitian_id_assigned: - current_app.logger.info(f"Deactivating assignment {active_assignment.id} for patient {patient.id} due to deletion of encounter {deleted_encounter_id}") - active_assignment.is_active = False - active_assignment.end_date = datetime.utcnow() - db.session.add(active_assignment) - # Gửi thêm thông báo về việc hủy assignment - deactivation_message = f"Your assignment to patient {patient.full_name} was deactivated because the related encounter was deleted." - deactivation_link = ('patients.patient_detail', {'patient_id': patient.id}) - create_notification(dietitian_id_assigned, deactivation_message, deactivation_link) + current_app.logger.info(f"Examining assignment {active_assignment.id} for patient {patient.id} due to deletion of encounter {deleted_encounter_id}") + + # Kiểm tra xem có encounters nào khác của patient này đang gán cho CÙNG dietitian không + other_same_dietitian_encounters = Encounter.query.filter( + Encounter.patient_id == patient.id, + Encounter.encounterID != deleted_encounter_id, + Encounter.dietitian_id == dietitian_id_assigned + ).all() + + # Nếu không còn encounter nào khác có cùng dietitian, hủy gán + if not other_same_dietitian_encounters: + current_app.logger.info(f"No other encounters with dietitian {dietitian_id_assigned} found. Attempting to deactivate assignment.") + try: + # --- BẮT ĐẦU KHỐI TRY RIÊNG CHO UNASSIGNMENT --- + # 1. Hủy assignment hiện tại + active_assignment.is_active = False + active_assignment.end_date = datetime.utcnow() + db.session.add(active_assignment) + + # 2. Cập nhật trường assigned_dietitian_user_id của patient về null + patient.assigned_dietitian_user_id = None + db.session.add(patient) + + # 3. (Số thứ tự cũ là 4) Đảm bảo không còn encounter nào liên kết với dietitian đã bị hủy + encounter_updates = Encounter.query.filter( + Encounter.patient_id == patient.id, + Encounter.dietitian_id == dietitian_id_assigned + ).update({"dietitian_id": None}, synchronize_session=False) + if encounter_updates > 0: + current_app.logger.info(f"Updated {encounter_updates} other encounters to remove dietitian link") + + # 4. (Số thứ tự cũ là 5) Cập nhật các assignment khác có thể vẫn đang active (phòng hờ) + other_active_assignments = PatientDietitianAssignment.query.filter( + PatientDietitianAssignment.patient_id == patient.id, + PatientDietitianAssignment.dietitian_id == dietitian_id_assigned, + PatientDietitianAssignment.is_active == True, + PatientDietitianAssignment.id != active_assignment.id + ).all() + for other_assignment in other_active_assignments: + other_assignment.is_active = False + other_assignment.end_date = datetime.utcnow() + db.session.add(other_assignment) + current_app.logger.info(f"Deactivated additional assignment {other_assignment.id}") + + # 5. (Số thứ tự cũ là 6) Kiểm tra và cập nhật bảng Dietitian + from app.models.dietitian import Dietitian + dietitian = Dietitian.query.filter_by(user_id=dietitian_id_assigned).first() + if dietitian: + dietitian.update_status_based_on_patient_count() # Cần chạy sau khi patient.assigned_dietitian_user_id đã được commit + db.session.add(dietitian) + current_app.logger.info(f"Dietitian {dietitian.formattedID} status marked for update.") + + # *** COMMIT RIÊNG CHO UNASSIGNMENT *** + db.session.commit() + unassignment_successful = True # Đánh dấu thành công + current_app.logger.info(f"Successfully committed unassignment changes for patient {patient.id} and dietitian {dietitian_id_assigned}.") + + # Gửi thông báo *sau khi* commit thành công + deactivation_message = f"Your assignment to patient {patient.full_name} was deactivated because the related encounter was deleted." + deactivation_link = ('patients.patient_detail', {'patient_id': patient.id}) + create_notification(dietitian_id_assigned, deactivation_message, deactivation_link) + + except Exception as unassign_error: + db.session.rollback() # Rollback CHỈ các thay đổi unassignment + current_app.logger.error(f"Error during unassignment process for patient {patient.id}: {unassign_error}", exc_info=True) + flash("An error occurred while unassigning the dietitian. Encounter deleted, but assignment might persist.", "warning") + # --- KẾT THÚC KHỐI TRY RIÊNG CHO UNASSIGNMENT --- + + else: + # Vẫn giữ assignment vì còn encounter khác với cùng dietitian + current_app.logger.info(f"Patient {patient.id} still has {len(other_same_dietitian_encounters)} other encounters with dietitian {dietitian_id_assigned}. Assignment maintained.") elif active_assignment: current_app.logger.info(f"Deleted encounter {deleted_encounter_id} had dietitian {dietitian_id_assigned}, but active assignment {active_assignment.id} is for dietitian {active_assignment.dietitian_id}. Assignment not deactivated.") else: @@ -1544,7 +1610,10 @@ def run_encounter_ml(patient_id, encounter_id): # Xử lý lỗi từ hàm dự đoán if prediction_error: # Sửa lỗi thụt lề - flash(f"ML Prediction Warning/Error: {prediction_error}", "warning") # Sửa lỗi thụt lề + # Format encounter ID as "E-XXXXX-XX" rather than just the numeric ID + encounter_display_id = encounter.custom_encounter_id or f"E-{encounter.encounterID:05d}-01" + error_message = prediction_error.replace(f"encounter {encounter.encounterID}", f"encounter {encounter_display_id}") + flash(f"ML Prediction Warning/Error: {error_message}", "warning") # Sửa lỗi thụt lề # Vẫn tiếp tục để lưu kết quả (có thể là None) if needs_intervention_result is not None: # Chỉ lưu kết quả nếu ML chạy không lỗi cơ bản # Sửa lỗi thụt lề @@ -1654,8 +1723,9 @@ def run_encounter_ml(patient_id, encounter_id): db.session.commit() # Commit tất cả thay đổi (encounter, referral, report, patient status) # Sửa lỗi thụt lề else: # needs_intervention_result is None (lỗi ML) # Sửa lỗi thụt lề - flash("An error occurred during ML prediction. Please check logs.", "danger") # Sửa lỗi thụt lề + # Bỏ thông báo lỗi ở đây vì đã xử lý ở trên với mã lỗi cụ thể # Không commit nếu ML lỗi + pass # Thêm pass để đảm bảo cú pháp đúng khi xóa nội dung của khối # Luôn redirect sau khi xử lý xong khối try (hoặc nếu ML lỗi) return redirect(url_for('patients.encounter_measurements', patient_id=patient_id, encounter_id=encounter_id)) # Sửa lỗi thụt lề @@ -1795,12 +1865,32 @@ def assign_dietitian(patient_id): # --- Update Patient and Related Objects --- if assigned_dietitian: + # --- ADD: Deactivate existing assignments FIRST --- + from app.models.patient_dietitian_assignment import PatientDietitianAssignment + deactivated_count = PatientDietitianAssignment.deactivate_existing_assignments(patient.id) + if deactivated_count: + current_app.logger.info(f"Deactivated {deactivated_count} previous assignment(s) for patient {patient.id}.") + # --- END ADD --- + + # --- ADD: Create NEW active assignment record --- + new_assignment = PatientDietitianAssignment( + patient_id=patient.id, + dietitian_id=assigned_dietitian.userID, + assignment_date=datetime.utcnow(), + is_active=True, + notes=notes # Thêm ghi chú từ form vào assignment + ) + db.session.add(new_assignment) + current_app.logger.info(f"Created new active assignment record for patient {patient.id} and dietitian {assigned_dietitian.userID}.") + # --- END ADD --- + + # Cập nhật thông tin Patient (giữ nguyên) patient.assigned_dietitian_user_id = assigned_dietitian.userID patient.assignment_date = datetime.utcnow() patient.status = PatientStatus.ASSESSMENT_IN_PROGRESS # Update patient status db.session.add(patient) - # Find the latest relevant referral needing assignment and update it + # Cập nhật Referral (giữ nguyên) latest_needing_referral = Referral.query.filter( Referral.patient_id == patient.id, Referral.referral_status == ReferralStatus.DIETITIAN_UNASSIGNED @@ -1817,7 +1907,7 @@ def assign_dietitian(patient_id): else: current_app.logger.warning(f"Could not find a referral needing assignment for patient {patient.id} when assigning dietitian.") - # --- ADD: Update latest ONGOING encounter with the assigned dietitian --- + # Cập nhật Encounter (giữ nguyên) latest_ongoing_encounter = Encounter.query.filter_by( patient_id=patient.id, status=EncounterStatus.ON_GOING @@ -1826,17 +1916,14 @@ def assign_dietitian(patient_id): if latest_ongoing_encounter: latest_ongoing_encounter.dietitian_id = assigned_dietitian.userID db.session.add(latest_ongoing_encounter) - # Đảm bảo refresh lại object để SQLAlchemy cập nhật relationship db.session.flush() - # Nếu chạy mà vẫn không hiện tên dietitian, thử thêm dòng này: db.session.refresh(latest_ongoing_encounter) current_app.logger.info(f"Updated latest ongoing encounter {latest_ongoing_encounter.encounterID} with assigned dietitian {assigned_dietitian.userID}. Relationship loaded: {latest_ongoing_encounter.assigned_dietitian is not None}") else: current_app.logger.warning(f"Could not find an ongoing encounter for patient {patient.id} to assign dietitian to.") - # --- END ADD --- - # --- ADD: Update dietitian_id for related PENDING reports --- - if latest_ongoing_encounter: # Chỉ cập nhật report nếu có encounter liên quan + # Cập nhật Report (giữ nguyên) + if latest_ongoing_encounter: associated_pending_reports = Report.query.filter_by( encounter_id=latest_ongoing_encounter.encounterID, status=ReportStatus.PENDING @@ -1844,16 +1931,16 @@ def assign_dietitian(patient_id): updated_report_count = 0 for report_to_update in associated_pending_reports: - if report_to_update.dietitian_id is None: # Chỉ cập nhật nếu chưa có dietitian + if report_to_update.dietitian_id is None: report_to_update.dietitian_id = assigned_dietitian.userID db.session.add(report_to_update) updated_report_count += 1 if updated_report_count > 0: current_app.logger.info(f"Updated dietitian_id for {updated_report_count} pending reports associated with encounter {latest_ongoing_encounter.encounterID}.") - # --- END ADD --- - # --- Logging and Notifications --- + # Logging và Notifications (giữ nguyên) + # ... (code logging và notification giữ nguyên) # Log Activity log_message = f"Assigned dietitian {assigned_dietitian.full_name} to patient {patient.full_name} ({assignment_type} assignment)." activity = ActivityLog( @@ -2293,3 +2380,4 @@ def download_ml_results(patient_id, encounter_id): return response # --- KẾT THÚC ROUTE TẢI KẾT QUẢ ML --- + \ No newline at end of file diff --git a/app/templates/encounter_measurements.html b/app/templates/encounter_measurements.html index c3ea3ec4ca627f36614c7e810b12879301551047..e9007b96fabc7567553c95dfe671cbe8c14d3b4b 100644 --- a/app/templates/encounter_measurements.html +++ b/app/templates/encounter_measurements.html @@ -5,7 +5,7 @@ {% block title %}Encounter #{{ display_encounter_id }} Measurements - {{ patient.full_name }}{% endblock %} -{% block header %}Encounter #{{ display_encounter_id }} Measurements - {{ patient.full_name }}{% endblock %} +{% block header %}Encounter {{ display_encounter_id }} Details{% endblock %} {% block content %} <div class="animate-slide-in container mx-auto px-4 py-8"> @@ -53,7 +53,7 @@ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm"> <div><strong>Start Time:</strong> {{ encounter.start_time.strftime('%d/%m/%Y %H:%M') if encounter.start_time else 'N/A' }}</div> {# Sửa cách hiển thị status #} - <div><strong>Status:</strong> <span class="px-2 py-0.5 text-xs font-semibold rounded-full bg-{{ status.color if status and status.color else 'gray' }}-100 text-{{ status.color if status and status.color else 'gray' }}-800">{{ status.text if status and status.text else 'Unknown' }}</span></div> + <div><strong>Status:</strong> <span class="px-2 py-0.5 text-xs font-semibold rounded-full bg-{{ encounter_status.color if encounter_status and encounter_status.color else 'gray' }}-100 text-{{ encounter_status.color if encounter_status and encounter_status.color else 'gray' }}-800">{{ encounter_status.text if encounter_status and encounter_status.text else 'Unknown' }}</span></div> {# Chỉ hiển thị Dietitian ở đây #} <div><strong>Dietitian:</strong> {{ encounter.assigned_dietitian.full_name if encounter.assigned_dietitian else 'None' }}</div> @@ -108,7 +108,7 @@ <a href="{{ url_for('patients.patient_detail', patient_id=patient.id) }}#encounters" class="inline-flex items-center px-3 py-1.5 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 transition-all duration-200"> <i class="fas fa-arrow-left mr-1.5"></i> - Quay lại danh sách lượt khám + Return to Encounter List </a> <div class="flex items-center space-x-3"> {# Wrap remaining buttons #} diff --git a/app/templates/new_patient.html b/app/templates/new_patient.html index 69e49abf56eedf0cc73cc24c2a2efb23584337aa..90bab75bc9bdf9ed9daca3d2c06b561cff61f31d 100644 --- a/app/templates/new_patient.html +++ b/app/templates/new_patient.html @@ -1,6 +1,7 @@ {% extends "base.html" %} {% block title %}Add New Patient{% endblock %} +{% block header %}Add Patient{% endblock %} {% block content %} <div class="container mx-auto px-4 py-6"> diff --git a/app/templates/patient_detail.html b/app/templates/patient_detail.html index cac6f961eb9cbc4e85204a63d7414718ade15f00..ff7c39c7233a55a38785dcdbeaa4a9f32bf5bd86 100644 --- a/app/templates/patient_detail.html +++ b/app/templates/patient_detail.html @@ -208,7 +208,7 @@ <div class="flex justify-between"> <dt class="text-sm font-medium text-gray-500">In-charge dietitian</dt> {# Cập nhật để dùng assigned_dietitian từ Patient #} - <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">{{ patient.assigned_dietitian.full_name if patient.assigned_dietitian else 'Chưa gán' }}</dd> + <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">{{ patient.assigned_dietitian.full_name if patient.assigned_dietitian else 'Not yet assigned' }}</dd> </div> </dl> </div> @@ -393,7 +393,7 @@ <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">Time</th> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Dietitian</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Critical Indicators (Max 3)</th> + <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Critical Indicators</th> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th> <th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">ACTIONS</th> </tr> @@ -1535,6 +1535,13 @@ // Add event listeners for delete procedure buttons (if on this page) // ... (existing delete procedure logic) ... + // Scroll to top after initial tab activation to prevent jumping due to hash + // Use setTimeout to ensure scrolling happens after potential browser hash scroll + setTimeout(() => { + window.scrollTo(0, 0); + console.log("[DOMContentLoaded] Scrolled window to top (after timeout)."); + }, 100); // Delay of 100ms + }); </script> diff --git a/app/templates/patients.html b/app/templates/patients.html index e2c91c773aabd0f97d5f1d4dcf896bd38f183a22..f613aafc442133569125144f9527a7b937ae1356 100644 --- a/app/templates/patients.html +++ b/app/templates/patients.html @@ -133,17 +133,17 @@ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ patient.admission_date.strftime('%Y-%m-%d') if patient.admission_date else 'N/A' }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm font-medium"> <a href="{{ url_for('patients.patient_detail', patient_id=patient.patient_id) }}" class="text-blue-600 hover:text-blue-900 mr-3" title="View Details"> - <i class="fas fa-eye text-2xl"></i> + <i class="fas fa-eye text-xl"></i> </a> {% if patient.status and patient.status.value != 'COMPLETED' %} <a href="{{ url_for('patients.edit_patient', patient_id=patient.patient_id) }}" class="text-indigo-600 hover:text-indigo-900 mr-3" title="Edit Patient"> - <i class="fas fa-edit text-2xl"></i> + <i class="fas fa-edit text-xl"></i> </a> {% endif %} {# Add admin check for delete button #} {% if current_user.is_admin %} <button type="button" data-patient-id="{{ patient.patient_id }}" data-patient-name="{{ patient.full_name }}" class="text-red-600 hover:text-red-900 delete-patient" title="Delete Patient"> - <i class="fas fa-trash text-2xl"></i> + <i class="fas fa-trash text-xl"></i> </button> {% endif %} </td> diff --git a/app/templates/report_form.html b/app/templates/report_form.html index 8fad6037228fcafdc638d8b79e5f230f47e8f1e2..0e1de6e715e1f49515d994afdd40b12a158ad0c5 100644 --- a/app/templates/report_form.html +++ b/app/templates/report_form.html @@ -402,7 +402,7 @@ e.preventDefault(); // Ngăn form submit ngay lập tức // Lấy thông báo xác nhận từ thuộc tính data - const confirmMessage = this.getAttribute('data-confirmation-message') || 'Bạn có chắc chắn muốn thực hiện?'; + const confirmMessage = this.getAttribute('data-confirmation-message') || 'Are you certain the report has been fully completed?'; // Hiển thị hộp thoại xác nhận if (confirm(confirmMessage)) { diff --git a/migrations/versions/8116b7d4aede_initital.py b/migrations/versions/8116b7d4aede_initital.py deleted file mode 100644 index f61f1161e3acc787855f8a8c75743c2745a0ae97..0000000000000000000000000000000000000000 --- a/migrations/versions/8116b7d4aede_initital.py +++ /dev/null @@ -1,103 +0,0 @@ -"""initital - -Revision ID: 8116b7d4aede -Revises: -Create Date: 2025-04-21 13:04:43.442726 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = '8116b7d4aede' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('support_message_read_status', schema=None) as batch_op: - # Drop constraints FIRST - try: # Use try-except in case constraints don't exist or have different names - batch_op.drop_constraint('support_message_read_status_ibfk_1', type_='foreignkey') - except Exception as e: - print(f"Info: Could not drop FK support_message_read_status_ibfk_1 (might not exist): {e}") - try: - batch_op.drop_constraint('support_message_read_status_ibfk_2', type_='foreignkey') - except Exception as e: - print(f"Info: Could not drop FK support_message_read_status_ibfk_2 (might not exist): {e}") - - # Now drop the index - try: - batch_op.drop_index('uq_user_message_read') - except Exception as e: - print(f"Info: Could not drop Index uq_user_message_read (might not exist): {e}") - - # Drop the table after constraints and indexes are gone - try: - op.drop_table('support_message_read_status') - except Exception as e: - print(f"Info: Could not drop table support_message_read_status (might not exist): {e}") - with op.batch_alter_table('reports', schema=None) as batch_op: - batch_op.alter_column('status', - existing_type=mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=20), - type_=sa.Enum('DRAFT', 'PENDING', 'COMPLETED', 'CANCELLED', name='reportstatus'), - nullable=False) - - with op.batch_alter_table('support_messages', schema=None) as batch_op: - batch_op.alter_column('timestamp', - existing_type=mysql.DATETIME(), - nullable=False) - - with op.batch_alter_table('uploadedfiles', schema=None) as batch_op: - batch_op.alter_column('error_details', - existing_type=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'), - type_=sa.Text(length=16777215), - existing_nullable=True) - - with op.batch_alter_table('users', schema=None) as batch_op: - batch_op.add_column(sa.Column('last_support_visit', sa.DateTime(timezone=True), nullable=True)) - - # ### 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.drop_column('last_support_visit') - - 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(collation='utf8mb4_unicode_ci'), - existing_nullable=True) - - with op.batch_alter_table('support_messages', schema=None) as batch_op: - batch_op.alter_column('timestamp', - existing_type=mysql.DATETIME(), - nullable=True) - - with op.batch_alter_table('reports', schema=None) as batch_op: - batch_op.alter_column('status', - existing_type=sa.Enum('DRAFT', 'PENDING', 'COMPLETED', 'CANCELLED', name='reportstatus'), - type_=mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=20), - nullable=True) - - op.create_table('support_message_read_status', - sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False), - sa.Column('user_id', mysql.INTEGER(), autoincrement=False, nullable=False), - sa.Column('message_id', mysql.INTEGER(), autoincrement=False, nullable=False), - sa.Column('read_at', mysql.DATETIME(), nullable=True), - sa.ForeignKeyConstraint(['message_id'], ['support_messages.id'], name='support_message_read_status_ibfk_1'), - sa.ForeignKeyConstraint(['user_id'], ['users.userID'], name='support_message_read_status_ibfk_2'), - sa.PrimaryKeyConstraint('id'), - mysql_collate='utf8mb4_unicode_ci', - mysql_default_charset='utf8mb4', - mysql_engine='InnoDB' - ) - with op.batch_alter_table('support_message_read_status', schema=None) as batch_op: - batch_op.create_index('uq_user_message_read', ['user_id', 'message_id'], unique=True) - - # ### end Alembic commands ### diff --git a/migrations/versions/fb934f468320_add_ml_pred.py b/migrations/versions/fb934f468320_add_ml_pred.py deleted file mode 100644 index 7404e7c1cdda189235d29896fc98092b3df87d6f..0000000000000000000000000000000000000000 --- a/migrations/versions/fb934f468320_add_ml_pred.py +++ /dev/null @@ -1,50 +0,0 @@ -"""add ml pred - -Revision ID: fb934f468320 -Revises: fe0cbf650625 -Create Date: 2025-04-22 01:02:07.781555 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = 'fb934f468320' -down_revision = 'fe0cbf650625' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('encounters', schema=None) as batch_op: - batch_op.add_column(sa.Column('ml_needs_intervention', sa.Boolean(), nullable=True, comment='Result of ML prediction (True=Needs Intervention)')) - batch_op.add_column(sa.Column('ml_prediction_time', sa.DateTime(), nullable=True, comment='Timestamp of the last ML prediction run')) - batch_op.add_column(sa.Column('ml_top_features_json', sa.JSON(), nullable=True, comment='JSON array of top influential features from SHAP')) - batch_op.add_column(sa.Column('ml_limit_breaches_json', sa.JSON(), nullable=True, comment='JSON array of features breaching physiological limits')) - - with op.batch_alter_table('uploadedfiles', schema=None) as batch_op: - batch_op.alter_column('error_details', - existing_type=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'), - 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(collation='utf8mb4_unicode_ci'), - existing_nullable=True) - - with op.batch_alter_table('encounters', schema=None) as batch_op: - batch_op.drop_column('ml_limit_breaches_json') - batch_op.drop_column('ml_top_features_json') - batch_op.drop_column('ml_prediction_time') - batch_op.drop_column('ml_needs_intervention') - - # ### end Alembic commands ### diff --git a/migrations/versions/fe0cbf650625_add.py b/migrations/versions/fe0cbf650625_add.py deleted file mode 100644 index a517bcc835710accca34ac7e11909f3d237afef9..0000000000000000000000000000000000000000 --- a/migrations/versions/fe0cbf650625_add.py +++ /dev/null @@ -1,64 +0,0 @@ -"""add - -Revision ID: fe0cbf650625 -Revises: 8116b7d4aede -Create Date: 2025-04-21 20:14:07.900955 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = 'fe0cbf650625' -down_revision = '8116b7d4aede' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('patient_dietitian_assignments', - sa.Column('id', mysql.INTEGER(unsigned=True), nullable=False), - sa.Column('patient_id', sa.String(length=50), nullable=False), - sa.Column('dietitian_id', sa.Integer(), nullable=False), - sa.Column('assignment_date', sa.DateTime(), nullable=False), - sa.Column('end_date', sa.DateTime(), nullable=True), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.Column('notes', sa.Text(), nullable=True), - sa.ForeignKeyConstraint(['dietitian_id'], ['users.userID'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['patient_id'], ['patients.patientID'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('patient_id', 'dietitian_id', name='uq_patient_dietitian_assignment') - ) - with op.batch_alter_table('patient_dietitian_assignments', schema=None) as batch_op: - batch_op.create_index(batch_op.f('ix_patient_dietitian_assignments_assignment_date'), ['assignment_date'], unique=False) - batch_op.create_index(batch_op.f('ix_patient_dietitian_assignments_dietitian_id'), ['dietitian_id'], unique=False) - batch_op.create_index(batch_op.f('ix_patient_dietitian_assignments_is_active'), ['is_active'], unique=False) - batch_op.create_index(batch_op.f('ix_patient_dietitian_assignments_patient_id'), ['patient_id'], unique=False) - - with op.batch_alter_table('uploadedfiles', schema=None) as batch_op: - batch_op.alter_column('error_details', - existing_type=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'), - 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(collation='utf8mb4_unicode_ci'), - existing_nullable=True) - - with op.batch_alter_table('patient_dietitian_assignments', schema=None) as batch_op: - batch_op.drop_index(batch_op.f('ix_patient_dietitian_assignments_patient_id')) - batch_op.drop_index(batch_op.f('ix_patient_dietitian_assignments_is_active')) - batch_op.drop_index(batch_op.f('ix_patient_dietitian_assignments_dietitian_id')) - batch_op.drop_index(batch_op.f('ix_patient_dietitian_assignments_assignment_date')) - - op.drop_table('patient_dietitian_assignments') - # ### end Alembic commands ### diff --git a/requirements.txt b/requirements.txt index 9fe069ec95febee3cd67ed36de2e4e5dd2468719..43852f9a197652224e64a4d6b7d93be7d0f44d64 100644 Binary files a/requirements.txt and b/requirements.txt differ