feat: AI 提示词模块化 + AI 同意门控 + AI 草稿存储 + 意图路由 + 医疗引用知识库 + 多页面 UI 优化
- AI 提示词拆分为 markdown 模块(Prompts/global + modules + rag + router) - 新增 AI 同意门控(用户需同意后才能使用 AI 功能) - 新增 AI 录入草稿存储(AiEntryDraftContracts/EfAiEntryDraftStore/AiEntryDraftRecord) - 新增 AI 意图路由(ai_intent_router) - 新增医疗引用知识库(MedicalCitationKnowledge) - 重构 prompt_manager 和 ai_chat_endpoints - 优化聊天/趋势/档案/抽屉/医生端等多个页面 UI - 新增 agent 插画和趋势指标图标资源 - 删除 HANDOFF-2026-07-17.md
This commit is contained in:
@@ -97,6 +97,66 @@ public sealed class ApplicationServiceTests
|
||||
Assert.Equal(1, repository.SaveCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HealthEntryBatch_ExecutesAllMetricsWithOneSave()
|
||||
{
|
||||
var repository = new CapturingHealthRecordRepository();
|
||||
var service = new HealthRecordService(repository);
|
||||
using var arguments = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"metrics": [
|
||||
{ "type": "blood_pressure", "systolic": 113, "diastolic": 86 },
|
||||
{ "type": "heart_rate", "heart_rate": 80 },
|
||||
{ "type": "glucose", "glucose": 5.6 }
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
var result = await HealthDataAgentHandler.Execute(
|
||||
"record_health_data_batch",
|
||||
arguments.RootElement,
|
||||
Guid.NewGuid(),
|
||||
service,
|
||||
CancellationToken.None);
|
||||
using var resultJson = JsonDocument.Parse(JsonSerializer.Serialize(result));
|
||||
|
||||
Assert.True(resultJson.RootElement.GetProperty("success").GetBoolean());
|
||||
Assert.Equal(3, resultJson.RootElement.GetProperty("record_ids").GetArrayLength());
|
||||
Assert.Equal(3, resultJson.RootElement.GetProperty("items").GetArrayLength());
|
||||
Assert.Equal(3, repository.Added.Count);
|
||||
Assert.Equal(1, repository.SaveCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HealthEntryBatch_IncompleteMetricBlocksWholeBatch()
|
||||
{
|
||||
var repository = new CapturingHealthRecordRepository();
|
||||
var service = new HealthRecordService(repository);
|
||||
using var arguments = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"metrics": [
|
||||
{ "type": "blood_pressure", "systolic": 113 },
|
||||
{ "type": "heart_rate", "heart_rate": 80 }
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
var result = await HealthDataAgentHandler.Execute(
|
||||
"record_health_data_batch",
|
||||
arguments.RootElement,
|
||||
Guid.NewGuid(),
|
||||
service,
|
||||
CancellationToken.None);
|
||||
using var resultJson = JsonDocument.Parse(JsonSerializer.Serialize(result));
|
||||
|
||||
Assert.False(resultJson.RootElement.GetProperty("success").GetBoolean());
|
||||
Assert.Contains("舒张压", resultJson.RootElement.GetProperty("message").GetString());
|
||||
Assert.Empty(repository.Added);
|
||||
Assert.Equal(0, repository.SaveCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HealthArchive_AiSurgeryUpdatePreservesLegacySurgery()
|
||||
{
|
||||
@@ -311,6 +371,62 @@ public sealed class ApplicationServiceTests
|
||||
Assert.Equal(0, todayJson.RootElement.GetProperty("count").GetInt32());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AiExerciseQuery_AuthoritativeAnswerPreservesStoredExerciseType()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var today = DateOnly.FromDateTime(DateTime.UtcNow.AddHours(8));
|
||||
var repository = new FakeExerciseRepository();
|
||||
var plan = new ExercisePlan
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = userId,
|
||||
StartDate = today,
|
||||
EndDate = today.AddDays(6),
|
||||
ReminderTime = new TimeOnly(15, 0),
|
||||
};
|
||||
plan.Items.Add(new ExercisePlanItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ScheduledDate = today,
|
||||
ExerciseType = "打篮球",
|
||||
DurationMinutes = 30,
|
||||
});
|
||||
repository.SetPlan(plan);
|
||||
var service = new ExerciseService(repository);
|
||||
using var arguments = JsonDocument.Parse("""{"action":"query","scope":"current_plans"}""");
|
||||
|
||||
var result = await ExerciseAgentHandler.Execute(
|
||||
"manage_exercise", arguments.RootElement, userId, service, CancellationToken.None);
|
||||
using var json = JsonDocument.Parse(JsonSerializer.Serialize(result));
|
||||
var answer = json.RootElement.GetProperty("authoritative_answer").GetString();
|
||||
|
||||
Assert.Contains("打篮球", answer);
|
||||
Assert.DoesNotContain("打毽子", answer);
|
||||
Assert.Contains("15:00", answer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MedicationEntry_AcceptsPillCountAsDosage()
|
||||
{
|
||||
using var arguments = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"action": "create",
|
||||
"name": "维生素D",
|
||||
"dosage": "1粒",
|
||||
"frequency": "Daily",
|
||||
"time_of_day": ["12:00"],
|
||||
"start_date": "2026-07-27",
|
||||
"duration_days": 3
|
||||
}
|
||||
""");
|
||||
|
||||
var validationError = MedicationAgentHandler.ValidateWriteArguments(arguments.RootElement);
|
||||
|
||||
Assert.Null(validationError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AiExerciseQuery_RequiresExplicitScopeInsteadOfAssumingToday()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user