fix: isolate payment amount evidence

This commit is contained in:
2026-08-21 22:07:45 +08:00
parent 979684a06e
commit 294a7de61d
10 changed files with 673 additions and 72 deletions
@@ -3,8 +3,9 @@ using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Reflection;
using System.Text.Json;
using MiaoJiZhang.Api.Services;
using System.Text.Json;
using MiaoJiZhang.Api.Controllers;
using MiaoJiZhang.Api.Services;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
@@ -859,6 +860,29 @@ public sealed class RecognitionBatchActionParserTests
Assert.Equal(1, action.Confidence);
}
}
public sealed class RecognitionAmountUpdateEvidenceTests
{
private static readonly IReadOnlyDictionary<string, string?> EvidenceOwners =
new Dictionary<string, string?>
{
["evidence-a"] = "candidate-a",
["evidence-b"] = "candidate-b"
};
[Fact]
public void AmountUpdateRequiresLinkedEvidenceAndHighConfidence()
{
Assert.False(ParseController.CanApplyAmountUpdate(
"candidate-a", null, 0.99, EvidenceOwners));
Assert.False(ParseController.CanApplyAmountUpdate(
"candidate-a", "evidence-a", 0.89, EvidenceOwners));
Assert.False(ParseController.CanApplyAmountUpdate(
"candidate-a", "evidence-b", 0.99, EvidenceOwners));
Assert.True(ParseController.CanApplyAmountUpdate(
"candidate-a", "evidence-a", 0.9, EvidenceOwners));
}
}
public sealed class BudgetRecommendationValidationTests
{
@@ -364,6 +364,9 @@ public class ParseController(AppDbContext db, ILlmClient llm, AgentService agent
{
var candidateIds = manifest.Candidates.Select(item => item.CandidateId).ToHashSet();
var evidenceById = manifest.Evidence.ToDictionary(item => item.EvidenceId);
var evidenceOwners = evidenceById.ToDictionary(
entry => entry.Key,
entry => entry.Value.CandidateId);
var selected = modelActions
.Where(action => action.CandidateId != null && candidateIds.Contains(action.CandidateId))
.GroupBy(action => action.CandidateId!)
@@ -401,7 +404,19 @@ public class ParseController(AppDbContext db, ILlmClient llm, AgentService agent
transferDirection = candidate.TransferDirection;
reason = "转账方向不明确,已保留本地结果";
}
var amount = model?.Amount is > 0 ? model.Amount.Value : candidate.Amount;
var proposedAmount = model?.Amount is > 0 ? model.Amount.Value : candidate.Amount;
var amountChanged = proposedAmount != candidate.Amount;
var amountUpdateAllowed = !amountChanged ||
model?.Action == "update" && CanApplyAmountUpdate(
candidate.CandidateId,
model.EvidenceId,
model.Confidence,
evidenceOwners);
var amount = amountUpdateAllowed ? proposedAmount : candidate.Amount;
if (amountChanged && !amountUpdateAllowed)
{
reason = "金额修改证据不足,已保留本地金额";
}
var category = FindCategory(
categories,
type == "income" || transferDirection == "in"
@@ -464,6 +479,18 @@ public class ParseController(AppDbContext db, ILlmClient llm, AgentService agent
}
return result.Take(20).ToList();
}
internal static bool CanApplyAmountUpdate(
string candidateId,
string? evidenceId,
double confidence,
IReadOnlyDictionary<string, string?> evidenceOwners)
{
return confidence >= 0.9 &&
!string.IsNullOrWhiteSpace(evidenceId) &&
evidenceOwners.TryGetValue(evidenceId, out var owner) &&
owner == candidateId;
}
private async Task<bool> FeatureEnabled(string key)
{
@@ -200,6 +200,7 @@ public partial class OpenAiVisionClient : ILlmClient
flowSessionId drop
evidenceId create evidenceId
update/create type expenseincome transferamount 0transfer transferDirection=in|out
update amount candidateId evidenceId confidence 0.9
沿reason 40
""";
var messages = new List<object>();