Files
AI-Health/backend/src/Health.Application/Calendars/CalendarService.cs
MingNian 4d213b5a44 feat: 后端架构重构 — Endpoint→Service→Repository分层 + AI确认机制 + 异步任务持久化
- 核心业务拆分为 Endpoint → Application Service → Repository 三层
- AI写入操作必须用户确认后才写库(确认卡片机制)
- 报告/饮食/用药分析改为持久化任务队列(原子领取/重试/重启恢复)
- 运动计划修复: 连续真实日期替代周模板
- 用药提醒去重 + 通知Outbox预留
- 认证收拢到AuthService, 管理员收拢到AdminService
- AI会话加用户归属校验防串号
- 提示词调整为患者视角
- 开发假数据已关闭
- 21/21测试通过, 0警告0错误
2026-06-20 20:41:42 +08:00

93 lines
3.8 KiB
C#

namespace Health.Application.Calendars;
public sealed class CalendarService(ICalendarRepository calendar) : ICalendarService
{
private readonly ICalendarRepository _calendar = calendar;
public async Task<IReadOnlyList<object>> GetMonthAsync(Guid userId, int year, int month, CancellationToken ct)
{
var start = new DateOnly(year, month, 1);
var end = start.AddMonths(1);
var snapshot = await _calendar.GetSnapshotAsync(userId, start, end, ct);
var result = new List<object>();
for (var date = start; date < end; date = date.AddDays(1))
{
var entries = BuildEntries(snapshot, date);
if (entries.Count == 0) continue;
result.Add(new
{
date = date.ToString("yyyy-MM-dd"),
events = entries.Select(entry => entry.Type).Distinct().ToList(),
details = entries.Select(entry => entry.Value).ToList()
});
}
return result;
}
public async Task<object> GetDayAsync(Guid userId, DateOnly date, CancellationToken ct)
{
var snapshot = await _calendar.GetSnapshotAsync(userId, date, date.AddDays(1), ct);
var medications = snapshot.Medications
.Where(m => IsMedicationActiveOn(m, date))
.Select(m => new { m.Name, m.Dosage, timeOfDay = m.TimeOfDay.Select(t => t.ToString(@"hh\:mm")).ToList() })
.ToList();
var exercises = snapshot.ExercisePlans
.SelectMany(p => p.Items)
.Where(i => i.ScheduledDate == date && !i.IsRestDay)
.Select(i => new { type = i.ExerciseType, duration = i.DurationMinutes, isCompleted = i.IsCompleted, scheduledDate = i.ScheduledDate })
.ToList();
var followUps = snapshot.FollowUps
.Where(f => DateOnly.FromDateTime(f.ScheduledAt) == date)
.Select(f => new { f.Title, f.DoctorName, f.Department, status = f.Status.ToString() })
.ToList();
return new { medications, exercises, followUps };
}
private static List<CalendarEntry> BuildEntries(CalendarDataSnapshot snapshot, DateOnly date)
{
var entries = new List<CalendarEntry>();
foreach (var medication in snapshot.Medications.Where(m => IsMedicationActiveOn(m, date)))
{
entries.Add(new CalendarEntry("medication", new
{
type = "medication",
name = medication.Name,
dosage = medication.Dosage,
timeOfDay = medication.TimeOfDay.Select(t => t.ToString(@"hh\:mm")).ToList()
}));
}
foreach (var plan in snapshot.ExercisePlans)
{
foreach (var item in plan.Items.Where(i => i.ScheduledDate == date && !i.IsRestDay))
entries.Add(new CalendarEntry("exercise", new { type = "exercise", name = item.ExerciseType, duration = item.DurationMinutes, isCompleted = item.IsCompleted, scheduledDate = item.ScheduledDate }));
}
foreach (var followUp in snapshot.FollowUps.Where(f => DateOnly.FromDateTime(f.ScheduledAt) == date))
{
entries.Add(new CalendarEntry("followup", new
{
type = "followup",
title = followUp.Title,
doctorName = followUp.DoctorName,
department = followUp.Department,
status = followUp.Status.ToString()
}));
}
return entries;
}
private static bool IsMedicationActiveOn(Health.Domain.Entities.Medication medication, DateOnly date) =>
medication.IsActive
&& medication.TimeOfDay.Count > 0
&& (medication.StartDate == null || medication.StartDate <= date)
&& (medication.EndDate == null || medication.EndDate >= date);
private sealed record CalendarEntry(string Type, object Value);
}