Open-World: усилить аналитический синтез бизнес-обзора
This commit is contained in:
+111
-17
@@ -822,6 +822,20 @@ function businessOverviewNetDirectionRu(direction) {
|
||||
}
|
||||
return "входящий и исходящий денежный поток в проверенном срезе примерно сбалансированы";
|
||||
}
|
||||
function amountHumanRu(value) {
|
||||
const rounded = Math.round(Math.abs(value) * 100) / 100;
|
||||
return `${new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 2 }).format(rounded)} руб.`;
|
||||
}
|
||||
function percentOfTotal(part, total) {
|
||||
if (!Number.isFinite(part) || !Number.isFinite(total) || total <= 0) {
|
||||
return null;
|
||||
}
|
||||
return Math.round((part / total) * 10_000) / 100;
|
||||
}
|
||||
function percentText(part, total) {
|
||||
const pct = percentOfTotal(part, total);
|
||||
return pct === null ? null : `${pct}%`;
|
||||
}
|
||||
function derivedBusinessOverviewConfirmedLines(pilot) {
|
||||
const overview = pilot.derived_business_overview;
|
||||
if (!overview) {
|
||||
@@ -888,19 +902,94 @@ function derivedBusinessOverviewConfirmedLines(pilot) {
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
function derivedBusinessOverviewInferenceLine(pilot) {
|
||||
function businessOverviewCashSynthesisLine(overview) {
|
||||
const incoming = overview.incoming_customer_revenue;
|
||||
const outgoing = overview.outgoing_supplier_payout;
|
||||
if (incoming.rows_with_amount <= 0 && outgoing.rows_with_amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
const checkedOperationalScale = Math.abs(incoming.total_amount) + Math.abs(outgoing.total_amount);
|
||||
return [
|
||||
`Аналитический вывод по оборотам: проверенный операционный размах ${amountHumanRu(checkedOperationalScale)}; входящий поток ${incoming.total_amount_human_ru}, исходящий ${outgoing.total_amount_human_ru}.`,
|
||||
`${businessOverviewNetDirectionRu(overview.net_direction)}; расчетное нетто ${overview.net_amount_human_ru}.`
|
||||
].join(" ");
|
||||
}
|
||||
function businessOverviewCustomerConcentrationLine(overview) {
|
||||
const leader = overview.top_customers[0];
|
||||
if (!leader || overview.incoming_customer_revenue.total_amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
const share = percentText(leader.total_amount, overview.incoming_customer_revenue.total_amount);
|
||||
return share
|
||||
? `Концентрация входящего потока: крупнейший подтвержденный клиент ${leader.axis_value} дает около ${share} проверенных входящих поступлений (${leader.total_amount_human_ru}). Это сигнал зависимости от клиента, а не полный customer-risk аудит.`
|
||||
: `Крупнейший подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`;
|
||||
}
|
||||
function businessOverviewRiskSynthesisLine(overview) {
|
||||
const signals = [];
|
||||
if (overview.tax_position) {
|
||||
const taxDirection = overview.tax_position.net_vat_direction === "vat_to_pay"
|
||||
? `НДС к уплате ${overview.tax_position.net_vat_amount_human_ru}`
|
||||
: overview.tax_position.net_vat_direction === "vat_to_recover_or_offset"
|
||||
? `НДС к вычету/зачету ${overview.tax_position.net_vat_amount_human_ru}`
|
||||
: "НДС-позиция сбалансирована";
|
||||
signals.push(taxDirection);
|
||||
}
|
||||
if (overview.debt_position) {
|
||||
const debtDirection = overview.debt_position.net_debt_position_direction === "net_receivable"
|
||||
? `дебиторка больше кредиторки на ${overview.debt_position.net_debt_position_amount_human_ru}`
|
||||
: overview.debt_position.net_debt_position_direction === "net_payable"
|
||||
? `кредиторка больше дебиторки на ${overview.debt_position.net_debt_position_amount_human_ru}`
|
||||
: "дебиторка и кредиторка сбалансированы";
|
||||
signals.push(debtDirection);
|
||||
}
|
||||
if (overview.debt_open_settlement_quality?.concentration_top_contract_pct !== null && overview.debt_open_settlement_quality?.top_contracts[0]) {
|
||||
const topContract = overview.debt_open_settlement_quality.top_contracts[0];
|
||||
signals.push(`крупнейший открытый договор держит ${overview.debt_open_settlement_quality.concentration_top_contract_pct}% открытых остатков (${topContract.total_amount_human_ru})`);
|
||||
}
|
||||
if (overview.debt_open_settlement_quality?.age_signal?.max_age_days !== null && overview.debt_open_settlement_quality?.age_signal?.max_age_days !== undefined) {
|
||||
signals.push(`самый старый договорный возрастной сигнал ${overview.debt_open_settlement_quality.age_signal.max_age_days} дн.`);
|
||||
}
|
||||
if (overview.inventory_position) {
|
||||
signals.push(`складской остаток на дату ${overview.inventory_position.total_amount_human_ru}`);
|
||||
if (overview.inventory_position.aging_signal?.max_age_days !== null && overview.inventory_position.aging_signal?.max_age_days !== undefined) {
|
||||
signals.push(`самый старый складской purchase-date сигнал ${overview.inventory_position.aging_signal.max_age_days} дн.`);
|
||||
}
|
||||
}
|
||||
return signals.length > 0
|
||||
? `Риски и контуры внимания по подтвержденным данным: ${signals.join("; ")}.`
|
||||
: null;
|
||||
}
|
||||
function businessOverviewExecutiveVerdictLine(overview) {
|
||||
const hasCash = overview.incoming_customer_revenue.rows_with_amount > 0 || overview.outgoing_supplier_payout.rows_with_amount > 0;
|
||||
const hasExtraSignals = Boolean(overview.tax_position ||
|
||||
overview.debt_position ||
|
||||
overview.debt_open_settlement_quality ||
|
||||
overview.inventory_position);
|
||||
if (!hasCash && !hasExtraSignals) {
|
||||
return null;
|
||||
}
|
||||
const cashTone = overview.net_direction === "net_incoming"
|
||||
? "операционно входящий поток сильнее исходящего"
|
||||
: overview.net_direction === "net_outgoing"
|
||||
? "операционно исходящий поток сильнее входящего, это зона внимания к расходам/закупкам"
|
||||
: "операционный поток выглядит сбалансированным";
|
||||
const evidenceTone = hasExtraSignals
|
||||
? "часть налоговых, долговых или складских контуров уже отдельно проверена"
|
||||
: "налоги, долги и склад еще не дают проверенного управленческого контекста";
|
||||
return `Сводный LLM-аудит по подтвержденному: ${cashTone}; ${evidenceTone}. Это полезный управленческий срез по найденным строкам 1С, но не финальный вывод о прибыльности, марже или здоровье компании.`;
|
||||
}
|
||||
function derivedBusinessOverviewInferenceLines(pilot) {
|
||||
const overview = pilot.derived_business_overview;
|
||||
if (!overview) {
|
||||
return null;
|
||||
}
|
||||
if (overview.incoming_customer_revenue.rows_with_amount <= 0 &&
|
||||
overview.outgoing_supplier_payout.rows_with_amount <= 0) {
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
`Расчетное нетто по найденным строкам: ${overview.net_amount_human_ru}; ${businessOverviewNetDirectionRu(overview.net_direction)}.`,
|
||||
"Это нормальный операционный сигнал, но не прибыль и не маржа: для управленческого вывода нужны отдельные расходы, себестоимость, долги, налоги и склад."
|
||||
].join(" ");
|
||||
businessOverviewCashSynthesisLine(overview),
|
||||
businessOverviewCustomerConcentrationLine(overview),
|
||||
businessOverviewRiskSynthesisLine(overview),
|
||||
businessOverviewExecutiveVerdictLine(overview),
|
||||
"Это аналитическая интерпретация подтвержденных строк, а не прибыль и не маржа: для финального управленческого вывода нужны отдельные расходы, себестоимость, закрывающие документы, долги, налоги и складская оборачиваемость."
|
||||
].filter((line) => Boolean(line));
|
||||
}
|
||||
function businessOverviewUnknownLines(pilot) {
|
||||
if (!pilot.derived_business_overview) {
|
||||
@@ -915,17 +1004,22 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
||||
if (pilot.evidence.unknown_facts.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_unknown_fact_boundary");
|
||||
}
|
||||
if (pilot.evidence.inferred_facts.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_bounded_inference");
|
||||
}
|
||||
const derivedInferenceLine = derivedBusinessOverviewInferenceLine(pilot) ??
|
||||
derivedActivityInferenceLine(pilot) ??
|
||||
const businessOverviewInferenceLines = derivedBusinessOverviewInferenceLines(pilot);
|
||||
const derivedInferenceLine = derivedActivityInferenceLine(pilot) ??
|
||||
derivedMetadataInferenceLine(pilot) ??
|
||||
derivedRankedValueFlowInferenceLine(pilot) ??
|
||||
derivedEntityResolutionInferenceLine(pilot);
|
||||
const inferenceLines = derivedInferenceLine
|
||||
? [derivedInferenceLine]
|
||||
: pilot.evidence.inferred_facts;
|
||||
const inferenceLines = businessOverviewInferenceLines.length > 0
|
||||
? businessOverviewInferenceLines
|
||||
: derivedInferenceLine
|
||||
? [derivedInferenceLine]
|
||||
: pilot.evidence.inferred_facts;
|
||||
if (inferenceLines.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_bounded_inference");
|
||||
}
|
||||
if (businessOverviewInferenceLines.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_analyst_synthesis");
|
||||
}
|
||||
const derivedMetadataLine = derivedMetadataConfirmedLine(pilot);
|
||||
const derivedEntityResolutionLine = derivedEntityResolutionConfirmedLine(pilot);
|
||||
const derivedValueLine = derivedBidirectionalValueFlowConfirmedLine(pilot) ??
|
||||
|
||||
Reference in New Issue
Block a user