@llm-dev-ops/agentics-cli 2.7.12 → 2.7.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -380,7 +380,7 @@ function computeDomainAwareConfidence(query, originalProb) {
380
380
  const rounded = Math.round(score * 100) / 100;
381
381
  return Math.abs(rounded - originalProb) > 0.03 ? rounded : originalProb;
382
382
  }
383
- export function synthesizeFinancials(simData, platformResults, query) {
383
+ export function synthesizeFinancials(simData, platformResults, query, runDir) {
384
384
  const fm = (safeGet(simData, 'financial_model') ?? safeGet(simData, 'cost_model') ?? safeGet(simData, 'financials'));
385
385
  // Collect from all possible agent sources — not just costops
386
386
  const financialFields = {};
@@ -405,6 +405,14 @@ export function synthesizeFinancials(simData, platformResults, query) {
405
405
  // contribute to D3 part 2's triangulation surface.
406
406
  const triangulationMap = {};
407
407
  resolveCanonicalClaims(platformResults, financialFields, financialSources, triangulationMap);
408
+ // ── Source 0.5: ADR-096 D3+ (2.7.13) — narrative extraction ────────────
409
+ // When platform/decision-memo or platform/executive-summary return
410
+ // narrative text rather than structured JSON, scan that text for explicit
411
+ // financial figures ($X investment, Y% ROI, etc.) and use them with
412
+ // src=domain/agent:extracted. Conservative regex — only catches anchored
413
+ // mentions, never invents values. Cheaper than an LLM call but bridges
414
+ // the gap when the agent narrative happens to include real numbers.
415
+ resolveNarrativeClaims(platformResults, financialFields, financialSources);
408
416
  // Source 1: simData direct fields
409
417
  for (const [key, ...aliases] of financialKeys) {
410
418
  if (!key)
@@ -463,6 +471,47 @@ export function synthesizeFinancials(simData, platformResults, query) {
463
471
  }
464
472
  }
465
473
  }
474
+ // ── Source 0.7: ADR-PIPELINE-096 D3++ (2.7.14) — unit-economics-derived ─
475
+ // When a unit-economics.json manifest exists for this run AND any of the
476
+ // headline metrics are still empty, derive them from the bottom-up sector
477
+ // baseline (sqft × $/sqft for CRE, rooms × $/orn for hospitality, etc.)
478
+ // via buildFinancialModelFromUnitEconomics. This unifies the methodology:
479
+ // savings AND investment AND NPV AND payback all trace to one model
480
+ // instead of having savings from sqft baseline but investment from
481
+ // per-employee. McKinsey/Accenture engagements would flag the methodology
482
+ // mismatch — this fixes it.
483
+ if (runDir) {
484
+ const manifestResult = loadUnitEconomics(runDir);
485
+ if (manifestResult.manifest) {
486
+ const m = buildFinancialModelFromUnitEconomics(manifestResult.manifest);
487
+ const sector = manifestResult.manifest.sector;
488
+ const sourceTag = `unit-economics:${sector}`;
489
+ if (!financialFields['budget']) {
490
+ financialFields['budget'] = m.budget;
491
+ financialSources['budget'] = sourceTag;
492
+ }
493
+ if (!financialFields['roi']) {
494
+ financialFields['roi'] = m.roi;
495
+ financialSources['roi'] = sourceTag;
496
+ }
497
+ if (!financialFields['npv']) {
498
+ financialFields['npv'] = m.npv;
499
+ financialSources['npv'] = sourceTag;
500
+ }
501
+ if (!financialFields['payback']) {
502
+ financialFields['payback'] = m.payback;
503
+ financialSources['payback'] = sourceTag;
504
+ }
505
+ if (!financialFields['revenue']) {
506
+ financialFields['revenue'] = m.revenue;
507
+ financialSources['revenue'] = sourceTag;
508
+ }
509
+ if (!financialFields['costSavings']) {
510
+ financialFields['costSavings'] = m.costSavings;
511
+ financialSources['costSavings'] = sourceTag;
512
+ }
513
+ }
514
+ }
466
515
  const hasRealData = !!(financialFields['budget'] || financialFields['roi'] || financialFields['npv'] || financialFields['payback'] || financialFields['revenue'] || financialFields['costSavings']);
467
516
  // Source 3: Estimate from query context when nothing else available
468
517
  if (!hasRealData) {
@@ -594,6 +643,76 @@ function resolveCanonicalClaims(platformResults, financialFields, financialSourc
594
643
  captureTriangulation('revenue', 'savings');
595
644
  captureTriangulation('costSavings', 'savings');
596
645
  }
646
+ /**
647
+ * ADR-PIPELINE-096 D3+ (2.7.13) — Source 0.5: extract financial figures
648
+ * from agent narrative responses (platform/decision-memo,
649
+ * platform/executive-summary). Used when no canonical agent supplied a
650
+ * structured value but a narrative mention exists.
651
+ */
652
+ function resolveNarrativeClaims(platformResults, financialFields, financialSources) {
653
+ // Collect narrative agents — currently platform/decision-memo and
654
+ // platform/executive-summary are the only two that return structured
655
+ // narrative blocks per the 2.7.10 calibration diagnostic.
656
+ const narrativeAgents = [];
657
+ for (const r of platformResults) {
658
+ if (r.status < 200 || r.status >= 300)
659
+ continue;
660
+ const agent = r;
661
+ if (agent.domain !== 'platform')
662
+ continue;
663
+ if (agent.agent !== 'decision-memo' && agent.agent !== 'executive-summary')
664
+ continue;
665
+ const sp = extractSignalPayload(r.response);
666
+ const data = sp.data ?? {};
667
+ // Concatenate the narrative-bearing fields. decision-memo has
668
+ // background, analysis, recommendation, risks; executive-summary has
669
+ // executive_summary, key_findings, recommendation. Catch all relevant.
670
+ const parts = [];
671
+ for (const k of ['executive_summary', 'background', 'analysis', 'recommendation', 'risks', 'key_findings', 'next_steps', 'options']) {
672
+ const v = data[k];
673
+ if (typeof v === 'string')
674
+ parts.push(v);
675
+ else if (Array.isArray(v))
676
+ parts.push(v.filter(x => typeof x === 'string').join(' '));
677
+ }
678
+ const narrative = parts.join(' ');
679
+ if (narrative.length > 20) {
680
+ narrativeAgents.push({ domain: agent.domain, agent: agent.agent, narrative });
681
+ }
682
+ }
683
+ if (narrativeAgents.length === 0)
684
+ return;
685
+ const extracted = agentClaimRegistry.extractFinancialClaimsFromNarrative(narrativeAgents);
686
+ // Map extracted claims onto financialFields keys based on the source suffix.
687
+ for (const claim of extracted) {
688
+ const value = typeof claim.value === 'string' ? claim.value : String(claim.value);
689
+ if (claim.source.endsWith(':extracted')) {
690
+ // Plain investment extraction
691
+ if (!financialFields['budget']) {
692
+ financialFields['budget'] = `${value} (${claim.methodology})`;
693
+ financialSources['budget'] = claim.source;
694
+ }
695
+ }
696
+ else if (claim.source.endsWith(':extracted-roi')) {
697
+ if (!financialFields['roi']) {
698
+ financialFields['roi'] = `${value} projected (${claim.methodology})`;
699
+ financialSources['roi'] = claim.source;
700
+ }
701
+ }
702
+ else if (claim.source.endsWith(':extracted-npv')) {
703
+ if (!financialFields['npv']) {
704
+ financialFields['npv'] = `${value} 5-year NPV (${claim.methodology})`;
705
+ financialSources['npv'] = claim.source;
706
+ }
707
+ }
708
+ else if (claim.source.endsWith(':extracted-payback')) {
709
+ if (!financialFields['payback']) {
710
+ financialFields['payback'] = `${value} (${claim.methodology})`;
711
+ financialSources['payback'] = claim.source;
712
+ }
713
+ }
714
+ }
715
+ }
597
716
  /** Format a registry-resolved value into the renderer's expected string shape. */
598
717
  function formatClaimValue(key, value, methodology) {
599
718
  if (typeof value === 'string')
@@ -1663,7 +1782,7 @@ export async function synthesizeExecutiveDocument(skeleton, query, documentType,
1663
1782
  // ============================================================================
1664
1783
  // Executive Summary Renderer (ADR-PIPELINE-024: Pyramid Principle)
1665
1784
  // ============================================================================
1666
- export function renderExecutiveSummary(query, simulationResult, platformResults) {
1785
+ export function renderExecutiveSummary(query, simulationResult, platformResults, runDir) {
1667
1786
  const now = new Date().toISOString();
1668
1787
  const simPayload = extractSignalPayload(simulationResult);
1669
1788
  const simData = simPayload.data ?? {};
@@ -1672,7 +1791,10 @@ export function renderExecutiveSummary(query, simulationResult, platformResults)
1672
1791
  const timeline = safeString(simData, 'timeline_estimate') || safeString(simData, 'timeline') || '12-18 weeks';
1673
1792
  const problemStatement = distillProblemStatement(query);
1674
1793
  const extracted = extractScenarioFromQuery(query);
1675
- const fin = synthesizeFinancials(simData, platformResults, query);
1794
+ // ADR-PIPELINE-096 D3++ (2.7.14) pass runDir so synthesizeFinancials
1795
+ // can apply the unit-economics-derived bottom-up methodology when
1796
+ // available. The same call now serves all three documents.
1797
+ const fin = synthesizeFinancials(simData, platformResults, query, runDir);
1676
1798
  const risks = generateDomainRisks(query, extracted);
1677
1799
  const primarySystem = extracted.systems[0] ?? extractSystemFromQuery(query) ?? 'the enterprise platform';
1678
1800
  let recommendation = 'DEFER';
@@ -1786,21 +1908,33 @@ export function renderExecutiveSummary(query, simulationResult, platformResults)
1786
1908
  // carry scope=full-program.
1787
1909
  if (fin.hasData) {
1788
1910
  lines.push('## Financial Impact', '');
1789
- // ADR-PIPELINE-096 D3 calibration banner (2.7.11/2.7.12): when ALL
1790
- // sources are heuristic (no canonical agent supplied a value), surface
1791
- // the fallback explicitly. 2.7.12 attributes heuristic values as
1792
- // src=heuristic:... so the test below distinguishes "any agent
1793
- // attribution" from "heuristic attribution". Calibration diagnostic in
1794
- // agent-responses.json shows that the current Cloud Run fleet does not
1795
- // include a project-level financial agent — the costops/* domain is
1796
- // about AI/LLM cost ops, not enterprise project financials.
1911
+ // ADR-PIPELINE-096 D3 calibration banner (2.7.112.7.14): banner shape
1912
+ // depends on which source filled the cells:
1913
+ // src=domain/agent → canonical agent (no banner; just the data)
1914
+ // src=unit-economics:X → bottom-up sector model (no banner this
1915
+ // IS the canonical methodology when no
1916
+ // project-level agent exists; tag this
1917
+ // explicitly as the engagement methodology)
1918
+ // src=heuristic:X → per-employee fallback (banner with full
1919
+ // remediation guidance)
1797
1920
  const allSources = [fin.sources?.budget, fin.sources?.roi, fin.sources?.npv, fin.sources?.payback, fin.sources?.revenue, fin.sources?.costSavings].filter(Boolean);
1798
- const hasAnyCanonicalSource = allSources.some(s => !s.startsWith('heuristic:'));
1799
- const allHeuristic = allSources.length > 0 && !hasAnyCanonicalSource;
1800
- if (allHeuristic || allSources.length === 0) {
1801
- lines.push('> **⚠️ Heuristic fallback in use.** No agent in the current fleet returned project-level financial figures. The numbers below are derived from the sector-baseline per-employee benchmark (ADR-PIPELINE-095) and are tagged `src=heuristic:...` for transparency. For consultancy-grade attribution, either provision a project-financial agent that returns `{investment, roi, npv, payback}` at predictable paths, or add an LLM-extraction layer over `platform/decision-memo`\'s narrative output (ADR-PIPELINE-096 task #24).');
1921
+ const hasAnyAgentCanonical = allSources.some(s => !s.startsWith('heuristic:') && !s.startsWith('unit-economics:'));
1922
+ const allUnitEconomics = allSources.length > 0 && allSources.every(s => s.startsWith('unit-economics:'));
1923
+ const allHeuristic = allSources.length > 0 && allSources.every(s => s.startsWith('heuristic:'));
1924
+ if (allHeuristic) {
1925
+ lines.push('> **⚠️ Heuristic fallback in use.** No agent in the current fleet returned project-level financial figures, and unit-economics manifest was unavailable. The numbers below are derived from the sector-baseline per-employee benchmark (ADR-PIPELINE-095) and are tagged `src=heuristic:...` for transparency. For consultancy-grade attribution, either provision a project-financial agent that returns `{investment, roi, npv, payback}` at predictable paths, or add an LLM-extraction layer over `platform/decision-memo`\'s narrative output (ADR-PIPELINE-096 task #24).');
1926
+ lines.push('');
1927
+ }
1928
+ else if (allUnitEconomics) {
1929
+ lines.push('> **📊 Bottom-up sector methodology.** All headline figures derive from the unit-economics manifest (ADR-PIPELINE-066 §6 sector baseline) — savings AND investment AND NPV AND payback all trace to one model, McKinsey/Accenture-grade methodology consistency. Source attribution: `src=unit-economics:<sector>`.');
1930
+ lines.push('');
1931
+ }
1932
+ else if (!hasAnyAgentCanonical && allSources.length > 0) {
1933
+ // Mixed unit-econ + heuristic — partial bottom-up, partial fallback.
1934
+ lines.push('> **📊 Mixed methodology.** Some figures are unit-economics-derived (sector bottom-up); others are heuristic per-employee fallback. Source attribution carried in each cell\'s `src=` tag.');
1802
1935
  lines.push('');
1803
1936
  }
1937
+ // If hasAnyAgentCanonical: agents supplied at least one value — no banner needed (the data IS the deliverable).
1804
1938
  lines.push('| Metric | Value |', '|--------|-------|');
1805
1939
  // ADR-PIPELINE-096 D3 — pass `fin.sources?.X` to fcvTag so the rendered
1806
1940
  // cell carries `src=domain/agent` (canonical) or omits the attribute
@@ -1919,7 +2053,7 @@ export function renderExecutiveSummary(query, simulationResult, platformResults)
1919
2053
  // ============================================================================
1920
2054
  // Decision Memo Renderer
1921
2055
  // ============================================================================
1922
- export function renderDecisionMemo(query, simulationResult, platformResults) {
2056
+ export function renderDecisionMemo(query, simulationResult, platformResults, runDir) {
1923
2057
  const now = new Date().toISOString();
1924
2058
  const lineage = extractSimulationLineage(simulationResult);
1925
2059
  const simPayload = extractSignalPayload(simulationResult);
@@ -1927,7 +2061,8 @@ export function renderDecisionMemo(query, simulationResult, platformResults) {
1927
2061
  const successProb = extractSuccessProbability(simData);
1928
2062
  const extracted = extractScenarioFromQuery(query);
1929
2063
  const problemStatement = distillProblemStatement(query);
1930
- const fin = synthesizeFinancials(simData, platformResults, query);
2064
+ // ADR-PIPELINE-096 D3++ (2.7.14) runDir threaded for unit-economics-derived methodology.
2065
+ const fin = synthesizeFinancials(simData, platformResults, query, runDir);
1931
2066
  const risks = generateDomainRisks(query, extracted);
1932
2067
  const primarySystem = extracted.systems[0] ?? extractSystemFromQuery(query) ?? 'the enterprise platform';
1933
2068
  const plannerData = extractAgentData(platformResults, 'copilot', 'planner');