@paro.io/expert-shared-components 1.14.56 → 1.14.57
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.
|
@@ -10,13 +10,13 @@ const SectionOpener_1 = require("./SectionOpener");
|
|
|
10
10
|
function Methodology({ profile, eligible, palette }) {
|
|
11
11
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
12
12
|
react_1.default.createElement(SectionOpener_1.SectionOpener, { number: "04", eyebrow: "METHODOLOGY", headline: "How TaxAxis arrives at these numbers.", bullets: [
|
|
13
|
-
"25 federal strategies evaluated against your profile
|
|
13
|
+
"25 federal strategies evaluated against your profile through a two-stage AI and deterministic pipeline \u2014 every recommendation traces to a specific IRC section, revenue ruling, or OBBBA provision.",
|
|
14
14
|
"Confidence bands reflect data quality: \u00B130% on single-year data, narrowing to \u00B115% with multi-year history.",
|
|
15
15
|
"Aggregate savings on the cover chart are adjusted by an interaction-discount factor reflecting strategy stacking, marginal-rate exhaustion, and \u00A76694 reasonableness.",
|
|
16
16
|
], palette: palette }),
|
|
17
17
|
react_1.default.createElement("div", { style: { marginBottom: 28 } },
|
|
18
18
|
react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", color: palette.gray400, fontFamily: palette.mono, marginBottom: 8 } }, "EVAL PIPELINE"),
|
|
19
|
-
react_1.default.createElement("div", { style: { fontSize: 13, color: palette.gray700, lineHeight: 1.8, fontFamily: palette.body } }, "TaxAxis evaluates 25 federal tax strategies against each client profile.
|
|
19
|
+
react_1.default.createElement("div", { style: { fontSize: 13, color: palette.gray700, lineHeight: 1.8, fontFamily: palette.body } }, "TaxAxis evaluates 25 federal tax strategies against each client profile through a two-stage pipeline. An AI model (Claude Sonnet 4.5 via AWS Bedrock) reads your financial documents, identifies applicable strategies, and drafts narrative content. A deterministic post-processing engine then recomputes savings ranges against IRC-cited rules, corrects confidence bands based on data quality, and validates every output through an 8-gate compliance check \u2014 covering schema integrity, strategy coverage, profile consistency, mathematical accuracy, business rules, \u00A76694 compliance, narrative quality, and entity eligibility. Every recommendation is reviewed by a qualified tax professional before the report is issued.")),
|
|
20
20
|
react_1.default.createElement("div", { style: { marginBottom: 28 } },
|
|
21
21
|
react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", color: palette.gray400, fontFamily: palette.mono, marginBottom: 8 } }, "CONFIDENCE BANDS"),
|
|
22
22
|
react_1.default.createElement("div", { style: { fontSize: 13, color: palette.gray700, lineHeight: 1.8, fontFamily: palette.body, marginBottom: 12 } }, "Savings ranges reflect data quality. Clients who provide multiple years of financial history receive tighter bands of \u00B115%, because multi-year data reveals trends and reduces estimation variance. Single-year data widens the bands to \u00B130%. Within each band, the low estimate uses conservative inputs at the minimum applicable rate, while the high estimate uses full applicable inputs at the client's marginal rate."),
|
|
@@ -26,6 +26,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
26
26
|
exports.TaxAxisDashboard = TaxAxisDashboard;
|
|
27
27
|
const react_1 = __importStar(require("react"));
|
|
28
28
|
const data_1 = require("../../lib/data");
|
|
29
|
+
const strategyNarrative_1 = require("../../lib/data/strategyNarrative");
|
|
29
30
|
const compute_1 = require("../../lib/compute");
|
|
30
31
|
const useEngineOutput_1 = require("../../lib/adapters/useEngineOutput");
|
|
31
32
|
const TaxAxisButton_1 = require("../shared/TaxAxisButton");
|
|
@@ -97,18 +98,109 @@ function buildRoadmapBucketLookup(llm) {
|
|
|
97
98
|
}
|
|
98
99
|
return map;
|
|
99
100
|
}
|
|
100
|
-
|
|
101
|
+
// LLM sometimes emits these literal placeholder strings as why_it_applies / next_step
|
|
102
|
+
// content. When detected, we fall back to STRATEGY_NARRATIVE static content.
|
|
103
|
+
const PLACEHOLDER_WHY = "This strategy may apply based on your financial profile.";
|
|
104
|
+
const PLACEHOLDER_NEXT = "Discuss with your CPA to evaluate and implement this strategy.";
|
|
105
|
+
function isMissingOrPlaceholder(value, knownPlaceholder) {
|
|
106
|
+
if (!value)
|
|
107
|
+
return true;
|
|
108
|
+
const trimmed = value.trim();
|
|
109
|
+
if (trimmed === "")
|
|
110
|
+
return true;
|
|
111
|
+
if (trimmed === knownPlaceholder)
|
|
112
|
+
return true;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
// Parse "S10" -> 10. Returns null if strategyId doesn't match the SN pattern.
|
|
116
|
+
function parseStrategyNumber(strategyId) {
|
|
117
|
+
if (!strategyId)
|
|
118
|
+
return null;
|
|
119
|
+
const match = strategyId.match(/^S(\d+)$/);
|
|
120
|
+
if (!match)
|
|
121
|
+
return null;
|
|
122
|
+
return parseInt(match[1], 10);
|
|
123
|
+
}
|
|
124
|
+
// Build a strategy_id -> client_summary entry lookup using strategy_analysis as the join table.
|
|
125
|
+
function buildClientSummaryLookup(llm) {
|
|
126
|
+
var _a, _b, _c, _d;
|
|
127
|
+
const lookup = new Map();
|
|
128
|
+
const engineOutput = (_a = llm.engineOutput) !== null && _a !== void 0 ? _a : llm.rawOutput;
|
|
129
|
+
if (!engineOutput)
|
|
130
|
+
return lookup;
|
|
131
|
+
const strategyAnalysis = (_b = engineOutput.strategy_analysis) !== null && _b !== void 0 ? _b : [];
|
|
132
|
+
const clientSummaryStrategies = (_d = (_c = engineOutput.client_summary) === null || _c === void 0 ? void 0 : _c.strategies) !== null && _d !== void 0 ? _d : [];
|
|
133
|
+
// strategy_name -> client_summary entry
|
|
134
|
+
const byName = new Map();
|
|
135
|
+
for (const cs of clientSummaryStrategies) {
|
|
136
|
+
if (cs.strategy_name) {
|
|
137
|
+
byName.set(cs.strategy_name, cs);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// strategy_id -> client_summary entry (via strategy_analysis name lookup)
|
|
141
|
+
for (const sa of strategyAnalysis) {
|
|
142
|
+
if (sa.strategy_id && sa.strategy_name) {
|
|
143
|
+
const cs = byName.get(sa.strategy_name);
|
|
144
|
+
if (cs) {
|
|
145
|
+
lookup.set(sa.strategy_id, cs);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return lookup;
|
|
150
|
+
}
|
|
151
|
+
function mapLlmToStrategies(llm, profile) {
|
|
101
152
|
const nameLookup = buildEngineNameLookup(llm);
|
|
102
153
|
const bucketLookup = buildRoadmapBucketLookup(llm);
|
|
154
|
+
const clientSummaryLookup = buildClientSummaryLookup(llm);
|
|
103
155
|
return llm.strategies
|
|
104
156
|
.filter((s) => s.applicable)
|
|
105
157
|
.map((s, idx) => {
|
|
106
|
-
var _a, _b, _c, _d, _e;
|
|
158
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
107
159
|
const id = (_a = s.strategyId) !== null && _a !== void 0 ? _a : s.strategyType;
|
|
108
|
-
// Prefer the real strategy_name from engineOutput, fall back to humanized strategyType
|
|
109
160
|
const name = (_b = nameLookup.get(id)) !== null && _b !== void 0 ? _b : humanizeStrategyType(s.strategyType);
|
|
110
161
|
const timelineBucket = (_c = bucketLookup.get(id)) !== null && _c !== void 0 ? _c : (s.priority === "HIGH" ? "now" : "90d");
|
|
111
162
|
const weightedScore = (_d = s.weightedScore) !== null && _d !== void 0 ? _d : (s.priority === "HIGH" ? 85 : s.priority === "MEDIUM" ? 70 : 55);
|
|
163
|
+
// Lookup the client_summary entry for this strategy (top-3 strategies only)
|
|
164
|
+
const csEntry = clientSummaryLookup.get(id);
|
|
165
|
+
const llmWhy = csEntry === null || csEntry === void 0 ? void 0 : csEntry.why_it_applies;
|
|
166
|
+
const llmNext = csEntry === null || csEntry === void 0 ? void 0 : csEntry.next_step;
|
|
167
|
+
// Strategy number for STRATEGY_NARRATIVE static lookup (e.g. "S10" -> 10)
|
|
168
|
+
const strategyNumber = parseStrategyNumber(id);
|
|
169
|
+
const staticNarrative = strategyNumber !== null ? strategyNarrative_1.STRATEGY_NARRATIVE[strategyNumber] : undefined;
|
|
170
|
+
// clientBrief: prefer LLM why_it_applies, fall back to static, then neutral default
|
|
171
|
+
let clientBrief;
|
|
172
|
+
if (!isMissingOrPlaceholder(llmWhy, PLACEHOLDER_WHY)) {
|
|
173
|
+
clientBrief = llmWhy;
|
|
174
|
+
}
|
|
175
|
+
else if (staticNarrative === null || staticNarrative === void 0 ? void 0 : staticNarrative.whyMatters) {
|
|
176
|
+
clientBrief = staticNarrative.whyMatters(profile);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
clientBrief = "Your preparer will review this strategy and include the analysis in your engagement report.";
|
|
180
|
+
}
|
|
181
|
+
// action: prefer LLM next_step, fall back to static nextSteps, then implementationSteps[0]
|
|
182
|
+
let action;
|
|
183
|
+
if (!isMissingOrPlaceholder(llmNext, PLACEHOLDER_NEXT)) {
|
|
184
|
+
action = llmNext;
|
|
185
|
+
}
|
|
186
|
+
else if (staticNarrative === null || staticNarrative === void 0 ? void 0 : staticNarrative.nextSteps) {
|
|
187
|
+
action = staticNarrative.nextSteps;
|
|
188
|
+
}
|
|
189
|
+
else if ((_e = s.implementationSteps) === null || _e === void 0 ? void 0 : _e[0]) {
|
|
190
|
+
action = s.implementationSteps[0];
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
action = "Work with your CPA to implement this strategy.";
|
|
194
|
+
}
|
|
195
|
+
// Pull calculation_trace fields. Defensive about shape.
|
|
196
|
+
const ct = ((_f = s.calculationTrace) !== null && _f !== void 0 ? _f : {});
|
|
197
|
+
const sourceDocuments = Array.isArray(ct.source_documents) ? ct.source_documents : undefined;
|
|
198
|
+
const specialistNote = typeof ct.specialist_note === "string" && ct.specialist_note.trim() !== ""
|
|
199
|
+
? ct.specialist_note
|
|
200
|
+
: ((_g = staticNarrative === null || staticNarrative === void 0 ? void 0 : staticNarrative.whySpecialist) !== null && _g !== void 0 ? _g : undefined);
|
|
201
|
+
const positionStrength = typeof ct.position_strength === "string" ? ct.position_strength : undefined;
|
|
202
|
+
const authority = typeof ct.irs_cite === "string" ? ct.irs_cite : undefined;
|
|
203
|
+
const formsFromTrace = Array.isArray(ct.forms_required) ? ct.forms_required.join(", ") : undefined;
|
|
112
204
|
return {
|
|
113
205
|
rank: idx + 1,
|
|
114
206
|
code: id,
|
|
@@ -121,13 +213,18 @@ function mapLlmToStrategies(llm) {
|
|
|
121
213
|
hi: s.estimatedSavings.max,
|
|
122
214
|
timeline: timelineBucket === "now" ? "Act now" : timelineBucket === "30d" ? "Within 30 days" : "Within 90 days",
|
|
123
215
|
timelineBucket,
|
|
124
|
-
clientBrief
|
|
125
|
-
action
|
|
126
|
-
forms: s.requiredForms.join(", "),
|
|
216
|
+
clientBrief,
|
|
217
|
+
action,
|
|
218
|
+
forms: formsFromTrace !== null && formsFromTrace !== void 0 ? formsFromTrace : s.requiredForms.join(", "),
|
|
127
219
|
abstract: s.summary,
|
|
128
220
|
sources: [],
|
|
129
221
|
trace: [],
|
|
130
222
|
cost: undefined,
|
|
223
|
+
sourceDocuments,
|
|
224
|
+
specialistNote,
|
|
225
|
+
positionStrength,
|
|
226
|
+
authority,
|
|
227
|
+
quickWin: s.quickWin,
|
|
131
228
|
};
|
|
132
229
|
});
|
|
133
230
|
}
|
|
@@ -177,8 +274,8 @@ function TaxAxisDashboard({ profile, llmResult, parsedDocuments, onDownloadClien
|
|
|
177
274
|
if ((_a = adapted === null || adapted === void 0 ? void 0 : adapted.strategies) === null || _a === void 0 ? void 0 : _a.length)
|
|
178
275
|
return adapted.strategies;
|
|
179
276
|
// Fallback to thin mapping for old payload shapes without engineOutput.
|
|
180
|
-
return mapLlmToStrategies(llmResult);
|
|
181
|
-
}, [hasLlm, adapted, llmResult]);
|
|
277
|
+
return mapLlmToStrategies(llmResult, profile);
|
|
278
|
+
}, [hasLlm, adapted, llmResult, profile]);
|
|
182
279
|
const llmComputed = (0, react_1.useMemo)(() => {
|
|
183
280
|
var _a;
|
|
184
281
|
if (!hasLlm)
|
|
@@ -26,10 +26,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
26
26
|
exports.TaxAxisProcessing = TaxAxisProcessing;
|
|
27
27
|
const react_1 = __importStar(require("react"));
|
|
28
28
|
const ProcessingStages_1 = require("./ProcessingStages");
|
|
29
|
-
const CRAWL_CEILING =
|
|
30
|
-
const CRAWL_TICK_MS =
|
|
31
|
-
const CRAWL_STEP = 0.
|
|
32
|
-
const FINISH_MS =
|
|
29
|
+
const CRAWL_CEILING = 99; // pause here until reportReady
|
|
30
|
+
const CRAWL_TICK_MS = 300; // interval between crawl ticks
|
|
31
|
+
const CRAWL_STEP = 0.27; // % added each tick → 0.27 / 0.3s = 0.9%/s
|
|
32
|
+
const FINISH_MS = 400; // ms to animate from 99% → 100% once ready
|
|
33
33
|
const STAGE_ADVANCE_MS = 3200; // how often the displayed stage advances
|
|
34
34
|
function buildStages(profile) {
|
|
35
35
|
var _a;
|
|
@@ -69,6 +69,30 @@ function TaxAxisProcessing({ onComplete, profile, reportReady = false, userConte
|
|
|
69
69
|
// Track current progress in a ref so the finish animation can read it synchronously
|
|
70
70
|
// without relying on the async functional-updater pattern.
|
|
71
71
|
const progressRef = (0, react_1.useRef)(0);
|
|
72
|
+
// ── Stall detection: show patience message after 35s of no progress change ──
|
|
73
|
+
const [showPatience, setShowPatience] = (0, react_1.useState)(false);
|
|
74
|
+
const lastProgressRef = (0, react_1.useRef)(0);
|
|
75
|
+
const stallTimerRef = (0, react_1.useRef)(null);
|
|
76
|
+
(0, react_1.useEffect)(() => {
|
|
77
|
+
if (finishingRef.current) {
|
|
78
|
+
setShowPatience(false);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (progress !== lastProgressRef.current) {
|
|
82
|
+
lastProgressRef.current = progress;
|
|
83
|
+
setShowPatience(false);
|
|
84
|
+
if (stallTimerRef.current)
|
|
85
|
+
clearTimeout(stallTimerRef.current);
|
|
86
|
+
stallTimerRef.current = setTimeout(() => {
|
|
87
|
+
if (!finishingRef.current)
|
|
88
|
+
setShowPatience(true);
|
|
89
|
+
}, 8000);
|
|
90
|
+
}
|
|
91
|
+
return () => {
|
|
92
|
+
if (stallTimerRef.current)
|
|
93
|
+
clearTimeout(stallTimerRef.current);
|
|
94
|
+
};
|
|
95
|
+
}, [progress]);
|
|
72
96
|
const setProgressSync = (v) => {
|
|
73
97
|
progressRef.current = v;
|
|
74
98
|
setProgress(v);
|
|
@@ -147,5 +171,6 @@ function TaxAxisProcessing({ onComplete, profile, reportReady = false, userConte
|
|
|
147
171
|
transition: isFinishing ? "none" : "width 0.18s linear",
|
|
148
172
|
} })),
|
|
149
173
|
react_1.default.createElement(ProcessingStages_1.ProcessingStages, { stages: stages, progress: stageIdx + 1 }),
|
|
150
|
-
pct >= CRAWL_CEILING && !isFinishing && (react_1.default.createElement("p", { className: "text-[11px] text-tax-axis-text-4 font-tax-axis-mono mt-4 animate-pulse" }, "Finalizing analysis\u2026"))
|
|
174
|
+
pct >= CRAWL_CEILING && !isFinishing && (react_1.default.createElement("p", { className: "text-[11px] text-tax-axis-text-4 font-tax-axis-mono mt-4 animate-pulse" }, "Finalizing analysis\u2026")),
|
|
175
|
+
showPatience && !isFinishing && (react_1.default.createElement("p", { className: "text-[13px] text-tax-axis-text-3 font-tax-axis-body mt-4 text-center" }, "Still running \u2014 document analysis typically takes 2\u20133 minutes. Do not navigate away."))));
|
|
151
176
|
}
|