From ccf790d13006fdf199d664d7faafedaa22a6e2f8 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 17 Aug 2026 17:11:10 +0300 Subject: [PATCH] DEV-000066 natural response target resolution --- modules/continuity/domain/human_response.go | 30 +++++++++++++++++++ .../continuity/domain/human_response_test.go | 7 +++++ 2 files changed, 37 insertions(+) diff --git a/modules/continuity/domain/human_response.go b/modules/continuity/domain/human_response.go index e2ae04c..3660042 100644 --- a/modules/continuity/domain/human_response.go +++ b/modules/continuity/domain/human_response.go @@ -50,6 +50,12 @@ func InterpretHumanResponse(request contract.HumanResponseRequest, owner identit response.Clarification = "Я не вижу активного ожидания, к которому это относится." return contract.HumanResponseResult{Response: response, Handled: true, Reply: response.Clarification}, nil } + if len(candidates) > 1 { + matched := matchingCandidates(message, candidates) + if len(matched) == 1 { + candidates = matched + } + } if len(candidates) > 1 { response.ResolutionState = contract.ResponseAmbiguous response.Outcome = contract.ResponseOutcomeClarificationNeeded @@ -153,6 +159,30 @@ func candidateSummary(candidates []ResponseCandidate) string { return strings.Join(parts, "; ") } +func matchingCandidates(message string, candidates []ResponseCandidate) []ResponseCandidate { + lower := strings.ToLower(message) + var matched []ResponseCandidate + for _, candidate := range candidates { + if summaryMatches(lower, candidate.Attention.Summary) { + matched = append(matched, candidate) + } + } + return matched +} + +func summaryMatches(lowerMessage string, summary string) bool { + for _, token := range strings.Fields(strings.ToLower(summary)) { + token = strings.Trim(token, ".,!?:;()[]«»\"'") + if len([]rune(token)) < 4 { + continue + } + if strings.Contains(lowerMessage, token) { + return true + } + } + return false +} + func humanResponseTime(value time.Time) string { if value.IsZero() { return "позже" diff --git a/modules/continuity/domain/human_response_test.go b/modules/continuity/domain/human_response_test.go index f22b2c3..fb820ad 100644 --- a/modules/continuity/domain/human_response_test.go +++ b/modules/continuity/domain/human_response_test.go @@ -41,6 +41,13 @@ func TestInterpretHumanResponseAmbiguousTargetAsksClarificationWithoutResolvedMu } } +func TestInterpretHumanResponseResolvesAmbiguousTargetByNaturalSummaryReference(t *testing.T) { + result := interpretResponseForTest(t, "Готово, проверить базу сделано", []ResponseCandidate{responseCandidate("a1", "p1", "позвонить врачу"), responseCandidate("a2", "p2", "проверить базу")}) + if !result.Handled || result.Response.ResolutionState != continuity.ResponseResolved || result.Response.TargetAttentionID != "a2" { + t.Fatalf("expected natural summary target resolution, got %#v", result) + } +} + func TestInterpretHumanResponseNoTargetAsksClarification(t *testing.T) { result := interpretResponseForTest(t, "Готово", nil) if !result.Handled || result.Response.ResolutionState != continuity.ResponseNoTarget || result.Response.Outcome != continuity.ResponseOutcomeClarificationNeeded { -- 2.54.0