Stage 3: улучшена логика жизненного цикла и очищены ответы ассистента

This commit is contained in:
2026-03-26 20:21:51 +03:00
parent d0b842adb0
commit 914843a8ba
81 changed files with 18051 additions and 654 deletions
+47 -23
View File
@@ -917,10 +917,10 @@ class AssistantDataLayer {
result = this.executeRisk(fragmentText, data);
}
else if (route === "batch_refresh_then_store") {
result = this.executeBatch(data);
result = this.executeBatch(fragmentText, data);
}
else if (route === "store_canonical") {
result = this.executeCanonical(data);
result = this.executeCanonical(fragmentText, data);
}
else if (route === "live_mcp_drilldown") {
result = this.executeDrilldown(fragmentText, data);
@@ -1207,7 +1207,9 @@ class AssistantDataLayer {
errors: []
};
}
executeRisk(_fragmentText, data) {
executeRisk(fragmentText, data) {
const semanticProfile = buildSemanticRetrievalProfile(fragmentText);
const profileRiskFactors = semanticProfile.anomaly_patterns;
const records = [...data.problemCases, ...data.ndsRegisters];
const scored = records
.map((record) => {
@@ -1258,12 +1260,15 @@ class AssistantDataLayer {
items: [],
summary: {
checked_records: records.length,
risky_records: 0
risky_records: 0,
query_subject: semanticProfile.query_subject,
semantic_profile: semanticProfile,
ranking_basis: semanticProfile.ranking_basis
},
evidence: [],
why_included: [],
selection_reason: ["Риск-оценка выполнялась по техническим признакам, но записи выше порога не найдены."],
risk_factors: [],
risk_factors: profileRiskFactors,
business_interpretation: ["По текущему срезу явные риск-признаки не обнаружены."],
confidence: "medium",
limitations: ["Оценка основана на snapshot-данных и эвристическом risk score."],
@@ -1271,6 +1276,13 @@ class AssistantDataLayer {
};
}
const averageScore = items.reduce((acc, item) => acc + item.risk_score, 0) / items.length;
const normalizedRiskFactors = uniqueStrings([
...profileRiskFactors,
"unknown_link_count",
"zero_guid_values",
"navigation_links",
"missing_counterparty_link"
]);
return {
status: "ok",
result_type: "list",
@@ -1278,7 +1290,10 @@ class AssistantDataLayer {
summary: {
checked_records: records.length,
risky_records: items.length,
average_risk_score: Number(averageScore.toFixed(2))
average_risk_score: Number(averageScore.toFixed(2)),
query_subject: semanticProfile.query_subject,
semantic_profile: semanticProfile,
ranking_basis: semanticProfile.ranking_basis
},
evidence: items.slice(0, 10).map((item) => ({
source_entity: item.source_entity,
@@ -1287,21 +1302,18 @@ class AssistantDataLayer {
})),
why_included: ["В ответ включены записи с risk_score >= 2."],
selection_reason: [
"score растет при unknown links, zero GUID, навигационных ссылках и отсутствии явного контрагента."
],
risk_factors: [
"unknown_link_count",
"zero_guid_values",
"navigation_links",
"missing_counterparty_link"
"score растет при unknown links, zero GUID, навигационных ссылках и отсутствии явного контрагента.",
`Semantic profile subject: ${semanticProfile.query_subject}.`
],
risk_factors: normalizedRiskFactors,
business_interpretation: ["Эти записи требуют первичной бухгалтерской проверки как потенциальные аномалии."],
confidence: "high",
limitations: ["Риск-факторы определяются эвристикой, а не полным набором бизнес-правил 1С."],
errors: []
};
}
executeBatch(data) {
executeBatch(fragmentText, data) {
const semanticProfile = buildSemanticRetrievalProfile(fragmentText);
const source = [...data.problemCases, ...data.keyFields, ...data.docs];
const byEntity = new Map();
for (const record of source) {
@@ -1321,7 +1333,10 @@ class AssistantDataLayer {
items,
summary: {
checked_records: source.length,
ranked_entities: items.length
ranked_entities: items.length,
query_subject: semanticProfile.query_subject,
semantic_profile: semanticProfile,
ranking_basis: semanticProfile.ranking_basis
},
evidence: items.slice(0, 5).map((item) => ({
entity: item.entity,
@@ -1329,17 +1344,20 @@ class AssistantDataLayer {
})),
why_included: items.length > 0 ? ["Показаны сущности с максимальным количеством записей."] : [],
selection_reason: ["Ранжирование выполнено по records_count по убыванию."],
risk_factors: ["Высокий объем записей по сущности повышает приоритет проверки."],
risk_factors: uniqueStrings(["entity_volume_spike", ...semanticProfile.anomaly_patterns]),
business_interpretation: [
"Сущности в топе ранга чаще дают наибольший вклад в проблемный объем и требуют приоритетного аудита."
"Top entities by volume highlight where lifecycle-focused review should start first."
],
confidence: "medium",
limitations: ["Ранжирование по объему не всегда эквивалентно бизнес-риску."],
errors: []
};
}
executeCanonical(data) {
const items = data.docs
executeCanonical(fragmentText, data) {
const semanticProfile = buildSemanticRetrievalProfile(fragmentText);
const useVatSource = semanticProfile.domain_scope.includes("vat") || semanticProfile.domain_scope.includes("taxes");
const sourceRecords = useVatSource ? [...data.ndsRegisters, ...data.keyFields] : data.docs;
const items = sourceRecords
.map((record) => {
const period = extractDate(record);
return {
@@ -1360,8 +1378,11 @@ class AssistantDataLayer {
result_type: "list",
items,
summary: {
checked_records: data.docs.length,
returned_records: items.length
checked_records: sourceRecords.length,
returned_records: items.length,
query_subject: semanticProfile.query_subject,
semantic_profile: semanticProfile,
ranking_basis: semanticProfile.ranking_basis
},
evidence: items.slice(0, 6).map((item) => ({
source_entity: item.source_entity,
@@ -1369,8 +1390,11 @@ class AssistantDataLayer {
period: item.period
})),
why_included: items.length > 0 ? ["Показаны последние по дате записи канонического документного слоя."] : [],
selection_reason: ["Отбор по максимальной дате документа в пределах snapshot."],
risk_factors: [],
selection_reason: [
"Отбор по максимальной дате документа в пределах snapshot.",
`Semantic profile subject: ${semanticProfile.query_subject}.`
],
risk_factors: semanticProfile.anomaly_patterns,
business_interpretation: ["Слой отражает базовый factual-срез документов для оперативной сверки."],
confidence: "high",
limitations: ["Это read-only snapshot, а не онлайн-состояние 1С."],