From 53ce19e8c3850e3792c7461b1db3d9fe1ab90166 Mon Sep 17 00:00:00 2001
From: MingNian <1281442923@qq.com>
Date: Wed, 24 Jun 2026 22:11:21 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=81=A5=E5=BA=B7=E5=BD=95=E5=85=A5?=
=?UTF-8?q?=E6=8F=90=E9=86=92=20+=20=E5=A4=9A=E6=8C=87=E6=A0=87=E5=BC=82?=
=?UTF-8?q?=E5=B8=B8=20+=20=E7=94=A8=E8=8D=AF=E6=BC=8F=E6=9C=8D=20+=20UI?=
=?UTF-8?q?=20=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
后端
- NotificationPreference 增加健康录入提醒总开关和 5 子项(血压/心率/血糖/血氧/体重)
- EF 迁移 AddHealthRecordReminder
- 新增 GET/PUT /api/notification-prefs
- 新增 HealthRecordReminderService 每天 9 点/12 点扫描未录入用户推送提醒
前端
- 通知偏好对接真实后端 API
- 设置页新增「健康录入提醒」总开关+5 子项
- 通知中心右上角加齿轮跳设置页
- 今日健康卡片:4 项异常检测+多项合并;用药只显漏服并按时间窗口区分文案
- 侧边栏头像改圆形白底灰图标,去掉紫色品牌渐变
- 整体背景统一浅灰白 #F6F8FC,去掉浅紫渐变
- 设置页简化(去掉分组/彩色图标方块)
- 健康档案保存按钮移到右上角,TextButton 全局默认黑色
- API baseUrl 跟随网络环境
---
.../Entities/support_entities.cs | 7 +
...063234_AddHealthRecordReminder.Designer.cs | 1461 +++++++++++++++++
.../20260624063234_AddHealthRecordReminder.cs | 84 +
.../Migrations/AppDbContextModelSnapshot.cs | 18 +
.../health_record_reminder_service.cs | 107 ++
.../Endpoints/notification_endpoints.cs | 69 +
backend/src/Health.WebApi/Program.cs | 2 +
health_app/lib/core/api_client.dart | 4 +-
health_app/lib/core/app_theme.dart | 31 +-
.../lib/pages/diet/diet_capture_page.dart | 7 +-
.../home/widgets/chat_messages_view.dart | 167 +-
.../medication/medication_list_page.dart | 20 +-
.../notification_center_page.dart | 436 ++---
health_app/lib/pages/remaining_pages.dart | 22 +-
health_app/lib/pages/report/report_pages.dart | 10 +-
.../settings/notification_prefs_page.dart | 148 +-
.../lib/pages/settings/settings_pages.dart | 326 ++--
health_app/lib/widgets/health_drawer.dart | 25 +-
18 files changed, 2379 insertions(+), 565 deletions(-)
create mode 100644 backend/src/Health.Infrastructure/Data/Migrations/20260624063234_AddHealthRecordReminder.Designer.cs
create mode 100644 backend/src/Health.Infrastructure/Data/Migrations/20260624063234_AddHealthRecordReminder.cs
create mode 100644 backend/src/Health.WebApi/BackgroundServices/health_record_reminder_service.cs
diff --git a/backend/src/Health.Domain/Entities/support_entities.cs b/backend/src/Health.Domain/Entities/support_entities.cs
index 3c32d25..293e98b 100644
--- a/backend/src/Health.Domain/Entities/support_entities.cs
+++ b/backend/src/Health.Domain/Entities/support_entities.cs
@@ -89,6 +89,13 @@ public sealed class NotificationPreference
public bool FollowUpReminder { get; set; } = true;
public bool DoctorReply { get; set; } = true;
public bool AbnormalAlert { get; set; } = true;
+ // 健康录入提醒——总开关 + 5 子项
+ public bool HealthRecordReminder { get; set; } = true;
+ public bool HealthRecordReminderBloodPressure { get; set; } = true;
+ public bool HealthRecordReminderHeartRate { get; set; } = true;
+ public bool HealthRecordReminderGlucose { get; set; } = true;
+ public bool HealthRecordReminderSpO2 { get; set; } = true;
+ public bool HealthRecordReminderWeight { get; set; } = true;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public User User { get; set; } = null!;
diff --git a/backend/src/Health.Infrastructure/Data/Migrations/20260624063234_AddHealthRecordReminder.Designer.cs b/backend/src/Health.Infrastructure/Data/Migrations/20260624063234_AddHealthRecordReminder.Designer.cs
new file mode 100644
index 0000000..08f22f1
--- /dev/null
+++ b/backend/src/Health.Infrastructure/Data/Migrations/20260624063234_AddHealthRecordReminder.Designer.cs
@@ -0,0 +1,1461 @@
+//
+using System;
+using System.Collections.Generic;
+using Health.Infrastructure.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Health.Infrastructure.Data.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ [Migration("20260624063234_AddHealthRecordReminder")]
+ partial class AddHealthRecordReminder
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Health.Domain.Entities.Consultation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ClosedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DoctorId")
+ .HasColumnType("uuid");
+
+ b.Property("Month")
+ .HasColumnType("integer");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DoctorId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Consultations");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.ConsultationMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ConsultationId")
+ .HasColumnType("uuid");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SenderName")
+ .HasColumnType("text");
+
+ b.Property("SenderType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ConsultationId", "CreatedAt")
+ .IsDescending(false, true);
+
+ b.ToTable("ConsultationMessages");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.Conversation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AgentType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("MessageCount")
+ .HasColumnType("integer");
+
+ b.Property("Summary")
+ .HasColumnType("text");
+
+ b.Property("Title")
+ .HasColumnType("text");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "UpdatedAt")
+ .IsDescending(false, true);
+
+ b.ToTable("Conversations");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.ConversationMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ConversationId")
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Intent")
+ .HasColumnType("text");
+
+ b.Property("MetadataJson")
+ .HasColumnType("jsonb");
+
+ b.Property("Role")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ConversationId", "CreatedAt");
+
+ b.ToTable("ConversationMessages");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.DeviceToken", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean");
+
+ b.Property("Platform")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("PushToken")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("DeviceTokens");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.DietFoodItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Calories")
+ .HasColumnType("integer");
+
+ b.Property("CarbsGrams")
+ .HasColumnType("numeric");
+
+ b.Property("DietRecordId")
+ .HasColumnType("uuid");
+
+ b.Property("FatGrams")
+ .HasColumnType("numeric");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Portion")
+ .HasColumnType("text");
+
+ b.Property("ProteinGrams")
+ .HasColumnType("numeric");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.Property("Warning")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DietRecordId");
+
+ b.ToTable("DietFoodItems");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.DietRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("HealthScore")
+ .HasColumnType("integer");
+
+ b.Property("MealType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("RecordedAt")
+ .HasColumnType("date");
+
+ b.Property("TotalCalories")
+ .HasColumnType("integer");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "RecordedAt")
+ .IsDescending(false, true);
+
+ b.ToTable("DietRecords");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.Doctor", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AvatarUrl")
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Department")
+ .HasColumnType("text");
+
+ b.Property("Introduction")
+ .HasColumnType("text");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Phone")
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)");
+
+ b.Property("ProfessionalDirection")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("Title")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Doctors");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.DoctorProfile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AvatarUrl")
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Department")
+ .HasColumnType("text");
+
+ b.Property("DoctorId")
+ .HasColumnType("uuid");
+
+ b.Property("Hospital")
+ .HasColumnType("text");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean");
+
+ b.Property("IsOnline")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("Title")
+ .HasColumnType("text");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DoctorId");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("DoctorProfiles");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.ExercisePlan", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("EndDate")
+ .HasColumnType("date");
+
+ b.Property("ReminderTime")
+ .HasColumnType("time without time zone");
+
+ b.Property("StartDate")
+ .HasColumnType("date");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "StartDate", "EndDate");
+
+ b.ToTable("ExercisePlans");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.ExercisePlanItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CompletedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DurationMinutes")
+ .HasColumnType("integer");
+
+ b.Property("ExerciseType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsCompleted")
+ .HasColumnType("boolean");
+
+ b.Property("IsRestDay")
+ .HasColumnType("boolean");
+
+ b.Property("PlanId")
+ .HasColumnType("uuid");
+
+ b.Property("ScheduledDate")
+ .HasColumnType("date");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PlanId", "ScheduledDate")
+ .IsUnique();
+
+ b.ToTable("ExercisePlanItems");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.FollowUp", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Department")
+ .HasColumnType("text");
+
+ b.Property("DoctorName")
+ .HasColumnType("text");
+
+ b.Property("Notes")
+ .HasColumnType("text");
+
+ b.Property("ScheduledAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("FollowUps");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.HealthArchive", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.PrimitiveCollection>("Allergies")
+ .IsRequired()
+ .HasColumnType("text[]");
+
+ b.PrimitiveCollection>("ChronicDiseases")
+ .IsRequired()
+ .HasColumnType("text[]");
+
+ b.Property("Diagnosis")
+ .HasColumnType("text");
+
+ b.PrimitiveCollection>("DietRestrictions")
+ .IsRequired()
+ .HasColumnType("text[]");
+
+ b.Property("FamilyHistory")
+ .HasColumnType("text");
+
+ b.Property("SurgeryDate")
+ .HasColumnType("date");
+
+ b.Property("SurgeryType")
+ .HasColumnType("text");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("HealthArchives");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.HealthArchiveSurgery", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Date")
+ .HasColumnType("date");
+
+ b.Property("HealthArchiveId")
+ .HasColumnType("uuid");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("HealthArchiveId", "SortOrder");
+
+ b.ToTable("HealthArchiveSurgeries", (string)null);
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.HealthRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Diastolic")
+ .HasColumnType("integer");
+
+ b.Property("IsAbnormal")
+ .HasColumnType("boolean");
+
+ b.Property("MetricType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("RecordedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Source")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Systolic")
+ .HasColumnType("integer");
+
+ b.Property("Unit")
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.Property("Value")
+ .HasColumnType("numeric");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "MetricType");
+
+ b.HasIndex("UserId", "RecordedAt")
+ .IsDescending(false, true);
+
+ b.ToTable("HealthRecords");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.Medication", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Dosage")
+ .HasColumnType("text");
+
+ b.Property("EndDate")
+ .HasColumnType("date");
+
+ b.Property("Frequency")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Notes")
+ .HasColumnType("text");
+
+ b.Property("Source")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("StartDate")
+ .HasColumnType("date");
+
+ b.PrimitiveCollection>("TimeOfDay")
+ .IsRequired()
+ .HasColumnType("time[]");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "IsActive");
+
+ b.ToTable("Medications");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.MedicationLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ConfirmedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("MedicationId")
+ .HasColumnType("uuid");
+
+ b.Property("ScheduledTime")
+ .HasColumnType("time without time zone");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MedicationId", "CreatedAt")
+ .IsDescending(false, true);
+
+ b.ToTable("MedicationLogs");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.NotificationPreference", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AbnormalAlert")
+ .HasColumnType("boolean");
+
+ b.Property("DoctorReply")
+ .HasColumnType("boolean");
+
+ b.Property("FollowUpReminder")
+ .HasColumnType("boolean");
+
+ b.Property("HealthRecordReminder")
+ .HasColumnType("boolean");
+
+ b.Property("HealthRecordReminderBloodPressure")
+ .HasColumnType("boolean");
+
+ b.Property("HealthRecordReminderGlucose")
+ .HasColumnType("boolean");
+
+ b.Property("HealthRecordReminderHeartRate")
+ .HasColumnType("boolean");
+
+ b.Property("HealthRecordReminderSpO2")
+ .HasColumnType("boolean");
+
+ b.Property("HealthRecordReminderWeight")
+ .HasColumnType("boolean");
+
+ b.Property("MedicationReminder")
+ .HasColumnType("boolean");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("NotificationPreferences");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.RefreshToken", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsRevoked")
+ .HasColumnType("boolean");
+
+ b.Property("Token")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.ToTable("RefreshTokens");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.Report", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AiIndicators")
+ .HasColumnType("jsonb");
+
+ b.Property("AiSummary")
+ .HasColumnType("text");
+
+ b.Property("Category")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DoctorComment")
+ .HasColumnType("text");
+
+ b.Property("DoctorName")
+ .HasColumnType("text");
+
+ b.Property("DoctorRecommendation")
+ .HasColumnType("text");
+
+ b.Property("FileType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("FileUrl")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ReviewedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Severity")
+ .HasColumnType("text");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Reports");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AvatarUrl")
+ .HasColumnType("text");
+
+ b.Property("BirthDate")
+ .HasColumnType("date");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DoctorId")
+ .HasColumnType("uuid");
+
+ b.Property("Gender")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("Phone")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Role")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasDefaultValue("User");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DoctorId");
+
+ b.HasIndex("Phone")
+ .IsUnique();
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.UserNotification", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ActionTargetId")
+ .HasColumnType("text");
+
+ b.Property("ActionType")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean");
+
+ b.Property("IsRead")
+ .HasColumnType("boolean");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ReadAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Severity")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("character varying(16)");
+
+ b.Property("SourceId")
+ .HasColumnType("uuid");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SourceId")
+ .IsUnique();
+
+ b.HasIndex("UserId", "IsDeleted", "IsRead", "CreatedAt");
+
+ b.ToTable("UserNotifications", (string)null);
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.VerificationCode", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsUsed")
+ .HasColumnType("boolean");
+
+ b.Property("Phone")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Phone", "CreatedAt")
+ .IsDescending(false, true);
+
+ b.ToTable("VerificationCodes");
+ });
+
+ modelBuilder.Entity("Health.Infrastructure.Data.Records.AiWriteCommandRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Arguments")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("ToolName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "Status", "ExpiresAt");
+
+ b.ToTable("AiWriteCommands", (string)null);
+ });
+
+ modelBuilder.Entity("Health.Infrastructure.Data.Records.DietImageAnalysisTaskRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Attempts")
+ .HasColumnType("integer");
+
+ b.Property("AvailableAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("FilePaths")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("LastError")
+ .HasColumnType("text");
+
+ b.Property("ResultCode")
+ .HasColumnType("integer");
+
+ b.Property("ResultData")
+ .HasColumnType("text");
+
+ b.Property("ResultMessage")
+ .HasColumnType("text");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Status", "AvailableAt");
+
+ b.ToTable("DietImageAnalysisTasks", (string)null);
+ });
+
+ modelBuilder.Entity("Health.Infrastructure.Data.Records.MedicationReminderTaskRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Attempts")
+ .HasColumnType("integer");
+
+ b.Property("AvailableAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Dosage")
+ .HasColumnType("text");
+
+ b.Property("LastError")
+ .HasColumnType("text");
+
+ b.Property("MedicationId")
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ScheduledDate")
+ .HasColumnType("date");
+
+ b.Property("ScheduledTime")
+ .HasColumnType("time without time zone");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Status", "AvailableAt");
+
+ b.HasIndex("MedicationId", "ScheduledDate", "ScheduledTime")
+ .IsUnique();
+
+ b.ToTable("MedicationReminderTasks", (string)null);
+ });
+
+ modelBuilder.Entity("Health.Infrastructure.Data.Records.NotificationOutboxRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Attempts")
+ .HasColumnType("integer");
+
+ b.Property("AvailableAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("LastError")
+ .HasColumnType("text");
+
+ b.Property("Payload")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("SourceTaskId")
+ .HasColumnType("uuid");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SourceTaskId")
+ .IsUnique();
+
+ b.HasIndex("Status", "AvailableAt");
+
+ b.ToTable("NotificationOutbox", (string)null);
+ });
+
+ modelBuilder.Entity("Health.Infrastructure.Data.Records.ReportAnalysisTaskRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Attempts")
+ .HasColumnType("integer");
+
+ b.Property("AvailableAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("FilePath")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property