@paro.io/expert-shared-components 1.14.55 → 1.14.56

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.
@@ -25,49 +25,46 @@ function positionStrengthLabel(positionStrength) {
25
25
  return { label: positionStrength, color: "#9498B8", dots: 2 };
26
26
  }
27
27
  /**
28
- * Split engagement_recommendation into two parts:
29
- * - savingsSection: sentences/paragraphs that contain dollar figures or formula references
30
- * → shown as "HOW SAVINGS BREAK DOWN"
31
- * - cpaSection: the remaining implementation/CPA-action paragraphs
32
- * → shown as "CPA ENGAGEMENT NOTES"
28
+ * Split engagement_recommendation at the implementation/action boundary.
33
29
  *
34
- * Strategy: split on double-newlines or sentence boundaries. Sentences with "$" or
35
- * formulas go to savings; implementation/deadline/CPA sentences go to cpa.
30
+ * The LLM writes engagement_recommendation as a single paragraph covering both
31
+ * the savings math AND the CPA implementation steps. We split at the first
32
+ * implementation keyword so the savings math shows under "HOW SAVINGS BREAK DOWN"
33
+ * and the CPA action steps show under "CPA ENGAGEMENT NOTES".
34
+ *
35
+ * Heuristic: split at "Implementation:", "CPA should", "CPA must", or numbered
36
+ * action lists "(1)". If no split point found, entire text goes to savings section.
36
37
  */
37
38
  function splitEngagementText(text) {
38
39
  if (!text)
39
40
  return { savingsSection: "", cpaSection: "" };
40
- // Split into paragraphs first, then sentences within paragraphs
41
- const rawParas = text.split(/\n{2,}/).map((p) => p.trim()).filter(Boolean);
42
- const savingsParas = [];
43
- const cpaParas = [];
44
- for (const para of rawParas) {
45
- const hasDollar = /\$[\d,]+/.test(para);
46
- const hasFormula = /SAVINGS\s*=|CREDIT\s*=|DEDUCTION\s*=|ADDITIONAL_CONTRIBUTION/i.test(para);
47
- const hasPercent = /\d+\.?\d*%/.test(para);
48
- const hasCpaKeyword = /\bCPA\b|actuar|implement|deadline|establish|file|form\s+\d|verify|engage|model|recommend/i.test(para);
49
- if ((hasDollar || hasFormula || hasPercent) && !hasCpaKeyword) {
50
- savingsParas.push(para);
51
- }
52
- else if (savingsParas.length === 0) {
53
- // First para with dollar amounts seeds savings section even if it has CPA keywords
54
- if (hasDollar || hasFormula)
55
- savingsParas.push(para);
56
- else
57
- cpaParas.push(para);
58
- }
59
- else {
60
- cpaParas.push(para);
61
- }
41
+ // Try splitting on double-newline first (well-structured LLM output)
42
+ const paraBreak = text.indexOf("\n\n");
43
+ if (paraBreak !== -1) {
44
+ return {
45
+ savingsSection: text.slice(0, paraBreak).trim(),
46
+ cpaSection: text.slice(paraBreak).trim(),
47
+ };
62
48
  }
63
- // If everything ended up in one bucket, put first para in savings, rest in cpa
64
- if (!savingsParas.length && cpaParas.length) {
65
- savingsParas.push(cpaParas.shift());
49
+ // Single-paragraph: find the first implementation boundary
50
+ const splitPatterns = [
51
+ /\s+Implementation:\s+/i,
52
+ /\s+CPA should\s+/i,
53
+ /\s+CPA must\s+/i,
54
+ /\s+To implement:\s+/i,
55
+ /\s+\(1\)\s+/, // numbered list start
56
+ ];
57
+ for (const pattern of splitPatterns) {
58
+ const match = text.search(pattern);
59
+ if (match > 80) { // only split if there's meaningful content before it
60
+ return {
61
+ savingsSection: text.slice(0, match).trim(),
62
+ cpaSection: text.slice(match).trim(),
63
+ };
64
+ }
66
65
  }
67
- return {
68
- savingsSection: savingsParas.join("\n\n"),
69
- cpaSection: cpaParas.join("\n\n"),
70
- };
66
+ // No clean split point — show everything as savings breakdown, nothing as CPA notes
67
+ return { savingsSection: text, cpaSection: "" };
71
68
  }
72
69
  function SourceDocRows({ docs, trace }) {
73
70
  if (docs && docs.length > 0) {
@@ -27,6 +27,7 @@ exports.TaxAxisDashboard = TaxAxisDashboard;
27
27
  const react_1 = __importStar(require("react"));
28
28
  const data_1 = require("../../lib/data");
29
29
  const compute_1 = require("../../lib/compute");
30
+ const useEngineOutput_1 = require("../../lib/adapters/useEngineOutput");
30
31
  const TaxAxisButton_1 = require("../shared/TaxAxisButton");
31
32
  const TaxAxisBadge_1 = require("../shared/TaxAxisBadge");
32
33
  const DashboardSummary_1 = require("./DashboardSummary");
@@ -163,8 +164,29 @@ function TaxAxisDashboard({ profile, llmResult, parsedDocuments, onDownloadClien
163
164
  // Extracted documents: live from session if provided, otherwise empty (no mock fallback)
164
165
  const extractionDocs = parsedDocuments !== null && parsedDocuments !== void 0 ? parsedDocuments : [];
165
166
  // ─── Derived data ──────────────────────────────────────────────
166
- const llmStrategies = (0, react_1.useMemo)(() => (hasLlm ? mapLlmToStrategies(llmResult) : []), [hasLlm, llmResult]);
167
- const llmComputed = (0, react_1.useMemo)(() => (hasLlm ? buildLlmComputed(llmStrategies) : new Map()), [hasLlm, llmStrategies]);
167
+ // Prefer useEngineOutput (full rich data: sourceDocuments, positionStrength,
168
+ // clientBrief from why_it_applies, etc.) over the thin mapLlmToStrategies fallback.
169
+ // engineOutput is at llmResult.engineOutput or llmResult.rawOutput (both present).
170
+ const engineOutputForAdapter = (0, react_1.useMemo)(() => { var _a, _b; return (_b = (_a = llmResult === null || llmResult === void 0 ? void 0 : llmResult.engineOutput) !== null && _a !== void 0 ? _a : llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) !== null && _b !== void 0 ? _b : null; }, [llmResult]);
171
+ const adapted = (0, useEngineOutput_1.useEngineOutput)(engineOutputForAdapter);
172
+ const llmStrategies = (0, react_1.useMemo)(() => {
173
+ var _a;
174
+ if (!hasLlm)
175
+ return [];
176
+ // Use adapted strategies when available — they carry the full engine data.
177
+ if ((_a = adapted === null || adapted === void 0 ? void 0 : adapted.strategies) === null || _a === void 0 ? void 0 : _a.length)
178
+ return adapted.strategies;
179
+ // Fallback to thin mapping for old payload shapes without engineOutput.
180
+ return mapLlmToStrategies(llmResult);
181
+ }, [hasLlm, adapted, llmResult]);
182
+ const llmComputed = (0, react_1.useMemo)(() => {
183
+ var _a;
184
+ if (!hasLlm)
185
+ return new Map();
186
+ if ((_a = adapted === null || adapted === void 0 ? void 0 : adapted.computedMap) === null || _a === void 0 ? void 0 : _a.size)
187
+ return adapted.computedMap;
188
+ return buildLlmComputed(llmStrategies);
189
+ }, [hasLlm, adapted, llmStrategies]);
168
190
  const computed = (0, react_1.useMemo)(() => (hasLlm ? llmComputed : (0, compute_1.computeAllStrategies)(profile)), [hasLlm, llmComputed, profile]);
169
191
  const dashEligible = (0, react_1.useMemo)(() => (hasLlm ? llmStrategies : (0, compute_1.filterEligibleStrategies)(profile)), [hasLlm, llmStrategies, profile]);
170
192
  const maxSavings = Math.max(...dashEligible.map((s) => { var _a, _b; return (_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.hi) !== null && _b !== void 0 ? _b : s.hi; }), 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paro.io/expert-shared-components",
3
- "version": "1.14.55",
3
+ "version": "1.14.56",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {