@paro.io/expert-shared-components 1.14.69 → 1.14.71

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.
@@ -155,8 +155,12 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
155
155
  react_1.default.useEffect(() => { setQboCompanyNameState(qboCompanyName !== null && qboCompanyName !== void 0 ? qboCompanyName : null); }, [qboCompanyName]);
156
156
  // Derive live strategies from engineOutput so CLIENT_REPORT and PREPARER_WORKPAPER
157
157
  // render real engine data instead of the static STRATEGIES catalog.
158
- const engineOutput = (0, react_1.useMemo)(() => { var _a; return (_a = llmResult === null || llmResult === void 0 ? void 0 : llmResult.engineOutput) !== null && _a !== void 0 ? _a : null; }, [llmResult]);
159
- const adapted = (0, useEngineOutput_1.useEngineOutput)(engineOutput);
158
+ // preComputed comes from the formula engine only strategies with status=COMPLETE
159
+ // should appear in reports. Without preComputed, the hook falls back to
160
+ // _reconciledPreComputed flags on strategy_analysis entries.
161
+ const engineOutput = (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]);
162
+ const preComputed = (0, react_1.useMemo)(() => { var _a, _b; return (_b = (_a = llmResult === null || llmResult === void 0 ? void 0 : llmResult.meta) === null || _a === void 0 ? void 0 : _a.preComputed) !== null && _b !== void 0 ? _b : null; }, [llmResult]);
163
+ const adapted = (0, useEngineOutput_1.useEngineOutput)(engineOutput, preComputed);
160
164
  const fetchAndSetParsedDocuments = (0, react_1.useCallback)((sid) => __awaiter(void 0, void 0, void 0, function* () {
161
165
  try {
162
166
  const docs = yield taxAxisApi.getDocuments(sid);
@@ -8,13 +8,15 @@ const react_1 = __importDefault(require("react"));
8
8
  const compute_1 = require("../../lib/compute");
9
9
  const SectionOpener_1 = require("./SectionOpener");
10
10
  const ETRChart_1 = require("./ETRChart");
11
+ // Format K values: >= 1000K -> "$X.XM", otherwise "$XK"
12
+ const fmtKRange = (k) => k >= 1000 ? "$" + (k / 1000).toFixed(1) + "M" : "$" + k + "K";
11
13
  function ExecutiveSummary({ profile, eligible, computed, totalLo, totalHi, nowCount, top3, palette, effectiveTaxRate, confidenceTier, dataYears }) {
12
14
  const bizName = profile.bizName || "Client";
13
15
  const rev = parseInt((profile.revenue || "0").replace(/,/g, "")) || 500000;
14
16
  const dataYearsLabel = dataYears ? `${dataYears} year${dataYears > 1 ? "s" : ""}` : (profile.taxDataYears || "1 year");
15
17
  return (react_1.default.createElement(react_1.default.Fragment, null,
16
18
  react_1.default.createElement(SectionOpener_1.SectionOpener, { number: "01", eyebrow: "EXECUTIVE SUMMARY", headline: "Where " + bizName + "'s tax dollars are going \u2014 and where they don't have to.", bullets: [
17
- eligible.length + " strategies apply to your current profile, with combined estimated savings of $" + totalLo + "K\u2013$" + totalHi + "K annually.",
19
+ eligible.length + " strategies apply to your current profile, with combined estimated savings of " + fmtKRange(totalLo) + "\u2013" + fmtKRange(totalHi) + " annually.",
18
20
  nowCount > 0
19
21
  ? nowCount + " can be initiated this week without any structural changes to your business."
20
22
  : "Implementation timeline spans the current tax year with no structural changes required.",
@@ -24,7 +26,7 @@ function ExecutiveSummary({ profile, eligible, computed, totalLo, totalHi, nowCo
24
26
  react_1.default.createElement("div", { style: { background: palette.gray50, border: "1px solid " + palette.gray200, borderRadius: 10, padding: "24px 28px", marginBottom: 20 } },
25
27
  react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.15em", color: palette.gray400, marginBottom: 16, fontFamily: palette.mono } }, "KEY METRICS"),
26
28
  react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 16 } }, [
27
- { v: "$" + totalLo + "K\u2013$" + totalHi + "K", l: "Est. Annual Savings" },
29
+ { v: fmtKRange(totalLo) + "\u2013" + fmtKRange(totalHi), l: "Est. Annual Savings" },
28
30
  { v: String(eligible.length), l: "Strategies Identified" },
29
31
  { v: String(nowCount), l: "Immediate Actions" },
30
32
  { v: String(top3.length), l: "Priority Recommendations" },
@@ -39,21 +41,23 @@ function ExecutiveSummary({ profile, eligible, computed, totalLo, totalHi, nowCo
39
41
  eligible.length,
40
42
  " applicable strategies"),
41
43
  " with combined estimated savings of ",
42
- react_1.default.createElement("strong", { style: { color: palette.teal } }, "$" + totalLo + "K\u2013$" + totalHi + "K annually"),
44
+ react_1.default.createElement("strong", { style: { color: palette.teal } }, fmtKRange(totalLo) + "\u2013" + fmtKRange(totalHi) + " annually"),
43
45
  "."),
44
46
  react_1.default.createElement("div", { style: { fontSize: 14, color: palette.gray700, lineHeight: 1.8, fontFamily: palette.body, marginBottom: 16 } },
45
47
  top3.length,
46
48
  " strategies represent the highest-impact opportunities and are detailed below. ",
47
49
  nowCount > 0 ? "Of these and the broader eligible set, " + nowCount + " can be initiated this week with no structural changes to your business." : "",
48
50
  " Savings estimates reflect your current revenue of ",
49
- "$" + Math.round(rev / 1000) + "K",
51
+ rev >= 1000000 ? "$" + (rev / 1000000).toFixed(1) + "M" : "$" + Math.round(rev / 1000) + "K",
50
52
  ", an effective tax rate of ",
51
- effectiveTaxRate != null ? Math.round((effectiveTaxRate <= 1 ? effectiveTaxRate * 100 : effectiveTaxRate) * 10) / 10 : (parseFloat(profile.federalRate || "24") + parseFloat(profile.stateRate || "4.95")),
53
+ effectiveTaxRate != null ? Math.round((effectiveTaxRate <= 1 ? effectiveTaxRate * 100 : effectiveTaxRate) * 10) / 10 : ((profile.entity === "C-Corporation" ? 21 : parseFloat(profile.federalRate || "24")) + parseFloat(profile.stateRate || "4.95")),
52
54
  "%, and ",
53
55
  dataYearsLabel,
54
56
  " of financial data."),
55
57
  (() => {
56
- const fedRate = parseFloat(profile.federalRate || "24");
58
+ // C-Corps have a flat 21% federal rate
59
+ const isCCorp = profile.entity === "C-Corporation";
60
+ const fedRate = isCCorp ? 21 : parseFloat(profile.federalRate || "24");
57
61
  const stateRate = parseFloat(profile.stateRate || "4.95");
58
62
  // If engine provided effective_tax_rate (as a decimal 0-1), convert to percentage
59
63
  const currentRate = effectiveTaxRate != null
@@ -21,6 +21,7 @@ interface ImplementationRoadmapProps {
21
21
  bucketTotals: Record<string, BucketTotal>;
22
22
  activeBuckets: BucketDef[];
23
23
  palette: Palette;
24
+ taxYear?: string;
24
25
  }
25
- export declare function ImplementationRoadmap({ eligible, computed, top3, nowCount, nowMidK, bucketDefs, bucketTotals, activeBuckets, palette, }: ImplementationRoadmapProps): React.JSX.Element;
26
+ export declare function ImplementationRoadmap({ eligible, computed, top3, nowCount, nowMidK, bucketDefs, bucketTotals, activeBuckets, palette, taxYear, }: ImplementationRoadmapProps): React.JSX.Element;
26
27
  export {};
@@ -9,7 +9,7 @@ const data_1 = require("../../lib/data");
9
9
  const SectionOpener_1 = require("./SectionOpener");
10
10
  const QuarterlyCashChart_1 = require("./QuarterlyCashChart");
11
11
  const ImplementationTimelineChart_1 = require("./ImplementationTimelineChart");
12
- function ImplementationRoadmap({ eligible, computed, top3, nowCount, nowMidK, bucketDefs, bucketTotals, activeBuckets, palette, }) {
12
+ function ImplementationRoadmap({ eligible, computed, top3, nowCount, nowMidK, bucketDefs, bucketTotals, activeBuckets, palette, taxYear, }) {
13
13
  const card = {
14
14
  background: palette.white,
15
15
  border: "1px solid " + palette.gray200,
@@ -31,8 +31,8 @@ function ImplementationRoadmap({ eligible, computed, top3, nowCount, nowMidK, bu
31
31
  "Time-sensitive elections and filings have firm deadlines noted in each strategy detail above.",
32
32
  "Year-end planning items will be reviewed in a coordinated session before December 31.",
33
33
  ], palette: palette }),
34
- react_1.default.createElement(QuarterlyCashChart_1.QuarterlyCashChart, { eligible: eligible, computed: computed, palette: palette }),
35
- react_1.default.createElement(ImplementationTimelineChart_1.ImplementationTimelineChart, { top3: top3, computed: computed, palette: palette }),
34
+ react_1.default.createElement(QuarterlyCashChart_1.QuarterlyCashChart, { eligible: eligible, computed: computed, palette: palette, taxYear: taxYear }),
35
+ react_1.default.createElement(ImplementationTimelineChart_1.ImplementationTimelineChart, { top3: top3, computed: computed, palette: palette, taxYear: taxYear }),
36
36
  react_1.default.createElement("div", { style: { marginBottom: 28 } }, activeBuckets.map(b => {
37
37
  const bt = bucketTotals[b.key];
38
38
  const midK = Math.round((bt.lo + bt.hi) / 2 / 1000);
@@ -42,10 +42,13 @@ function ImplementationRoadmap({ eligible, computed, top3, nowCount, nowMidK, bu
42
42
  react_1.default.createElement("div", { style: { fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.1em", color: b.color, fontFamily: palette.mono } }, b.label),
43
43
  react_1.default.createElement("div", { style: { fontSize: 11, color: palette.gray400, fontFamily: palette.mono } }, bt.strategies.length + " " + (bt.strategies.length === 1 ? "strategy" : "strategies"))),
44
44
  react_1.default.createElement("div", { style: { fontSize: 13, fontWeight: 700, color: palette.teal, fontFamily: palette.mono } }, "~$" + midK + "K midpoint")),
45
- bt.strategies.map(s => (react_1.default.createElement("div", { key: s.rank, style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", padding: "10px 0", borderBottom: "1px solid " + palette.gray100 } },
46
- react_1.default.createElement("div", { style: { flex: 1 } },
47
- react_1.default.createElement("div", { style: { fontSize: 14, fontWeight: 600, color: palette.gray800, fontFamily: palette.head } }, s.name),
48
- react_1.default.createElement("div", { style: { fontSize: 12, color: palette.gray500, marginTop: 2, fontFamily: palette.body } }, data_1.NEXT_STEPS[s.rank] || "Discuss with your preparer.")),
49
- react_1.default.createElement("div", { style: { fontSize: 13, fontWeight: 700, color: palette.teal, fontFamily: palette.mono, flexShrink: 0, marginLeft: 12 } }, stSavings(s)))))));
45
+ bt.strategies.map(s => {
46
+ var _a;
47
+ return (react_1.default.createElement("div", { key: s.rank, style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", padding: "10px 0", borderBottom: "1px solid " + palette.gray100 } },
48
+ react_1.default.createElement("div", { style: { flex: 1 } },
49
+ react_1.default.createElement("div", { style: { fontSize: 14, fontWeight: 600, color: palette.gray800, fontFamily: palette.head } }, s.name),
50
+ react_1.default.createElement("div", { style: { fontSize: 12, color: palette.gray500, marginTop: 2, fontFamily: palette.body } }, data_1.NEXT_STEPS[(_a = s.strategyNumber) !== null && _a !== void 0 ? _a : s.rank] || "Discuss with your preparer.")),
51
+ react_1.default.createElement("div", { style: { fontSize: 13, fontWeight: 700, color: palette.teal, fontFamily: palette.mono, flexShrink: 0, marginLeft: 12 } }, stSavings(s))));
52
+ })));
50
53
  }))));
51
54
  }
@@ -5,6 +5,7 @@ interface ImplementationTimelineChartProps {
5
5
  top3: Strategy[];
6
6
  computed: ComputedMap;
7
7
  palette: Palette;
8
+ taxYear?: string;
8
9
  }
9
- export declare function ImplementationTimelineChart({ top3, computed, palette }: ImplementationTimelineChartProps): React.JSX.Element;
10
+ export declare function ImplementationTimelineChart({ top3, computed, palette, taxYear }: ImplementationTimelineChartProps): React.JSX.Element;
10
11
  export {};
@@ -5,17 +5,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ImplementationTimelineChart = ImplementationTimelineChart;
7
7
  const react_1 = __importDefault(require("react"));
8
- function ImplementationTimelineChart({ top3, computed, palette }) {
8
+ function ImplementationTimelineChart({ top3, computed, palette, taxYear }) {
9
9
  const W = 640, H = Math.max(180, 60 + top3.length * 44);
10
10
  const padL = 180, padR = 80, padT = 44, padB = 24;
11
11
  const plotW = W - padL - padR, plotH = H - padT - padB;
12
12
  const BUCKET_RANGE = { now: [0, 2], "30d": [0, 4], "90d": [2, 12], filing: [36, 52] };
13
13
  const weekScale = (w) => padL + (w / 52) * plotW;
14
14
  const qDividers = [13, 26, 39];
15
+ const currentYear = new Date().getFullYear();
16
+ const year = taxYear || String(currentYear);
17
+ const yearNum = parseInt(year, 10);
18
+ const isHistorical = yearNum < currentYear;
19
+ const chartTitle = isHistorical ? "IMPLEMENTATION SEQUENCE" : "IMPLEMENTATION TIMELINE";
15
20
  const qLabels = ["Q1", "Q2", "Q3", "Q4"];
16
21
  return (react_1.default.createElement("div", { className: "chart-block", style: { background: palette.white, border: "1px solid " + palette.gray200, borderRadius: 10, padding: "24px 28px", marginBottom: 24 } },
17
22
  react_1.default.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 18 } },
18
- react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.15em", color: palette.gray400, fontFamily: palette.mono } }, "IMPLEMENTATION TIMELINE"),
23
+ react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.15em", color: palette.gray400, fontFamily: palette.mono } }, chartTitle),
19
24
  react_1.default.createElement("div", { style: { fontSize: 11, fontWeight: 700, color: palette.teal, fontFamily: palette.mono } }, "top " + top3.length + " priorities")),
20
25
  react_1.default.createElement("svg", { viewBox: "0 0 " + W + " " + H, style: { width: "100%", height: "auto", display: "block" } },
21
26
  [0, 1, 2, 3].map(qi => {
@@ -27,7 +32,8 @@ function ImplementationTimelineChart({ top3, computed, palette }) {
27
32
  const cx = weekScale(qi * 13 + 6.5);
28
33
  return react_1.default.createElement("text", { key: q, x: cx, y: padT - 18, fontSize: "10", fontWeight: "700", fill: palette.gray400, fontFamily: palette.mono, textAnchor: "middle" },
29
34
  q,
30
- " 2026");
35
+ " ",
36
+ year);
31
37
  }),
32
38
  qDividers.map(w => (react_1.default.createElement("line", { key: w, x1: weekScale(w), y1: padT - 12, x2: weekScale(w), y2: padT + plotH + 4, stroke: palette.gray200, strokeWidth: "1", strokeDasharray: "2,3" }))),
33
39
  top3.map((s, i) => {
@@ -5,6 +5,7 @@ interface QuarterlyCashChartProps {
5
5
  eligible: Strategy[];
6
6
  computed: ComputedMap;
7
7
  palette: Palette;
8
+ taxYear?: string;
8
9
  }
9
- export declare function QuarterlyCashChart({ eligible, computed, palette }: QuarterlyCashChartProps): React.JSX.Element;
10
+ export declare function QuarterlyCashChart({ eligible, computed, palette, taxYear }: QuarterlyCashChartProps): React.JSX.Element;
10
11
  export {};
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.QuarterlyCashChart = QuarterlyCashChart;
7
7
  const react_1 = __importDefault(require("react"));
8
- function QuarterlyCashChart({ eligible, computed, palette }) {
8
+ function QuarterlyCashChart({ eligible, computed, palette, taxYear }) {
9
9
  const W = 600, H = 240;
10
10
  const padL = 56, padR = 24, padT = 32, padB = 52;
11
11
  const plotW = W - padL - padR, plotH = H - padT - padB;
@@ -25,8 +25,14 @@ function QuarterlyCashChart({ eligible, computed, palette }) {
25
25
  const yScale = (v) => padT + plotH - (v / maxVal) * plotH;
26
26
  const barW = Math.min(72, (plotW - 60) / 4);
27
27
  const slot = plotW / 4;
28
- const labels = ["Q1 2026", "Q2 2026", "Q3 2026", "Q4 2026"];
29
- const sublabels = ["Now / 30 days", "Within 90 days", "Mid-year", "At filing"];
28
+ const currentYear = new Date().getFullYear();
29
+ const year = taxYear || String(currentYear);
30
+ const yearNum = parseInt(year, 10);
31
+ const isHistorical = yearNum < currentYear;
32
+ const labels = [`Q1 ${year}`, `Q2 ${year}`, `Q3 ${year}`, `Q4 ${year}`];
33
+ const sublabels = isHistorical
34
+ ? ["Q1 actions", "Q2 actions", "Q3 actions", "Q4 actions"]
35
+ : ["Now / 30 days", "Within 90 days", "Mid-year", "At filing"];
30
36
  const tickStep = maxVal > 200000 ? 100000 : maxVal > 100000 ? 50000 : 25000;
31
37
  const yTicks = [];
32
38
  for (let v = 0; v <= maxVal * 1.05; v += tickStep)
@@ -55,7 +55,14 @@ function TaxAxisClientReport({ profile, onBack, onNavigatePreparer, liveStrategi
55
55
  ];
56
56
  const bucketTotals = {};
57
57
  bucketDefs.forEach(b => {
58
- const strategies = eligible.filter(s => s.timelineBucket === b.key).sort((a, c) => c.score - a.score);
58
+ const strategies = eligible.filter(s => {
59
+ var _a, _b, _c, _d;
60
+ if (s.timelineBucket !== b.key)
61
+ return false;
62
+ const clo = (_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.lo) !== null && _b !== void 0 ? _b : s.lo;
63
+ const chi = (_d = (_c = computed.get(s.rank)) === null || _c === void 0 ? void 0 : _c.hi) !== null && _d !== void 0 ? _d : s.hi;
64
+ return clo > 0 || chi > 0;
65
+ }).sort((a, c) => c.score - a.score);
59
66
  const lo = strategies.reduce((a, s) => { var _a, _b; return a + ((_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.lo) !== null && _b !== void 0 ? _b : s.lo); }, 0);
60
67
  const hi = strategies.reduce((a, s) => { var _a, _b; return a + ((_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.hi) !== null && _b !== void 0 ? _b : s.hi); }, 0);
61
68
  bucketTotals[b.key] = { strategies, lo, hi };
@@ -115,7 +122,7 @@ function TaxAxisClientReport({ profile, onBack, onNavigatePreparer, liveStrategi
115
122
  "This summary was prepared using TaxAxis, Paro's AI-powered tax analysis engine. Savings estimates are based on financial data provided for tax year " + profile.year + " (" + (profile.taxDataYears || "1 year") + " of data). All figures are estimates. Your tax preparer has reviewed these recommendations.",
116
123
  hasOBBBA && " Some strategies reference provisions from the One Big Beautiful Bill Act (OBBBA), signed July 4, 2025, with limited IRS guidance."),
117
124
  react_1.default.createElement(RecommendedStrategies_1.RecommendedStrategies, { profile: profile, eligible: eligible, computed: computed, top3: top3, hasOBBBA: hasOBBBA, palette: palette_1.P, interactionWarnings: engineRawOutput === null || engineRawOutput === void 0 ? void 0 : engineRawOutput.strategy_interaction_warnings }),
118
- react_1.default.createElement(ImplementationRoadmap_1.ImplementationRoadmap, { eligible: eligible, computed: computed, top3: top3, nowCount: nowCount, nowMidK: nowMidK, bucketDefs: bucketDefs, bucketTotals: bucketTotals, activeBuckets: activeBuckets, palette: palette_1.P }),
125
+ react_1.default.createElement(ImplementationRoadmap_1.ImplementationRoadmap, { eligible: eligible, computed: computed, top3: top3, nowCount: nowCount, nowMidK: nowMidK, bucketDefs: bucketDefs, bucketTotals: bucketTotals, activeBuckets: activeBuckets, palette: palette_1.P, taxYear: profile.year }),
119
126
  react_1.default.createElement(Methodology_1.Methodology, { profile: profile, eligible: eligible, palette: palette_1.P }),
120
127
  react_1.default.createElement("div", { style: { padding: "16px 20px", marginBottom: 16, fontSize: 10, color: palette_1.P.gray400, lineHeight: 1.7, fontFamily: palette_1.P.body } }, "This summary was generated by TaxAxis, an AI-powered analysis tool by Paro. Recommendations are based on financial data provided and current tax law as of April 2026, including OBBBA provisions. These are estimates, not guarantees. Consult your tax preparer before implementing any strategy. This is not legal or financial advice."),
121
128
  react_1.default.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "12px 0", borderTop: "1px solid " + palette_1.P.gray200, fontSize: 10, color: palette_1.P.gray400, fontFamily: palette_1.P.mono } },
@@ -11,7 +11,9 @@ function Sec({ title, children, titleColor }) {
11
11
  react_1.default.createElement("div", { className: "text-[11px] font-bold uppercase tracking-widest mb-2.5 font-tax-axis-mono", style: { color: titleColor || "#A1E5E6" } }, title),
12
12
  children));
13
13
  }
14
- const fmtK = (n) => `$${(n / 1000).toFixed(n % 1000 ? 1 : 0)}K`;
14
+ const fmtK = (n) => n >= 1000000
15
+ ? `$${(n / 1000000).toFixed(1)}M`
16
+ : `$${(n / 1000).toFixed(n % 1000 ? 1 : 0)}K`;
15
17
  function positionStrengthLabel(positionStrength) {
16
18
  if (!positionStrength)
17
19
  return { label: "Position Under Review", color: "#9498B8", dots: 2 };
@@ -235,7 +235,9 @@ function buildLlmComputed(strategies) {
235
235
  return map;
236
236
  }
237
237
  // ─── Formatting helper ──────────────────────────────────────────
238
- const fmtK = (n) => `$${(n / 1000).toFixed(n % 1000 ? 1 : 0)}K`;
238
+ const fmtK = (n) => n >= 1000000
239
+ ? `$${(n / 1000000).toFixed(1)}M`
240
+ : `$${(n / 1000).toFixed(n % 1000 ? 1 : 0)}K`;
239
241
  // ─── Timeline buckets for Client Summary ────────────────────────
240
242
  const BUCKETS = [
241
243
  { key: "now", label: "Act Now", desc: "Claim on your return or adjust this pay period" },
@@ -264,7 +266,8 @@ function TaxAxisDashboard({ profile, llmResult, parsedDocuments, onDownloadClien
264
266
  // clientBrief from why_it_applies, etc.) over the thin mapLlmToStrategies fallback.
265
267
  // engineOutput is at llmResult.engineOutput or llmResult.rawOutput (both present).
266
268
  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]);
267
- const adapted = (0, useEngineOutput_1.useEngineOutput)(engineOutputForAdapter);
269
+ const preComputedForAdapter = (0, react_1.useMemo)(() => { var _a, _b; return (_b = (_a = llmResult === null || llmResult === void 0 ? void 0 : llmResult.meta) === null || _a === void 0 ? void 0 : _a.preComputed) !== null && _b !== void 0 ? _b : null; }, [llmResult]);
270
+ const adapted = (0, useEngineOutput_1.useEngineOutput)(engineOutputForAdapter, preComputedForAdapter);
268
271
  const llmStrategies = (0, react_1.useMemo)(() => {
269
272
  var _a;
270
273
  if (!hasLlm)
@@ -66,10 +66,20 @@ const INDUSTRY_OPTIONS = [
66
66
  "Real Estate",
67
67
  "Other",
68
68
  ];
69
- const PERIOD_OPTIONS = ["Full Year", "YTD"];
70
- const YEAR_OPTIONS = ["2024", "2025", "2026"];
69
+ const CURRENT_YEAR = new Date().getFullYear();
70
+ const YEAR_OPTIONS = ["2023", "2024", "2025", "2026"];
71
71
  function ClientParametersSection({ userContext = "expert", }) {
72
- const { control, formState: { errors }, } = (0, react_hook_form_1.useFormContext)();
72
+ const { control, formState: { errors }, watch, setValue, } = (0, react_hook_form_1.useFormContext)();
73
+ const selectedYear = watch("year");
74
+ const yearNum = parseInt(selectedYear || "2026", 10);
75
+ const isCurrentOrFuture = yearNum >= CURRENT_YEAR;
76
+ const periodOptions = isCurrentOrFuture ? ["YTD"] : ["Full Year", "YTD"];
77
+ // Auto-switch period to "YTD" when user selects a current/future tax year
78
+ react_1.default.useEffect(() => {
79
+ if (isCurrentOrFuture) {
80
+ setValue("period", "YTD");
81
+ }
82
+ }, [isCurrentOrFuture, setValue]);
73
83
  const [expanded, setExpanded] = (0, react_1.useState)(true);
74
84
  return (react_1.default.createElement(TaxAxisCard_1.TaxAxisCard, null,
75
85
  react_1.default.createElement("div", { onClick: () => setExpanded((prev) => !prev), className: "flex items-center justify-between cursor-pointer", style: { marginBottom: expanded ? 14 : 0 } },
@@ -103,7 +113,7 @@ function ClientParametersSection({ userContext = "expert", }) {
103
113
  react_1.default.createElement(react_hook_form_1.Controller, { name: "period", control: control, render: ({ field }) => {
104
114
  var _a;
105
115
  return (react_1.default.createElement("div", { className: "relative" },
106
- react_1.default.createElement("select", Object.assign({}, field, { value: (_a = field.value) !== null && _a !== void 0 ? _a : "Full Year", className: selectCls }), PERIOD_OPTIONS.map(o => (react_1.default.createElement("option", { key: o, value: o, className: "bg-tax-axis-surface-2" }, o)))),
116
+ react_1.default.createElement("select", Object.assign({}, field, { value: (_a = field.value) !== null && _a !== void 0 ? _a : (isCurrentOrFuture ? "YTD" : "Full Year"), className: selectCls }), periodOptions.map(o => (react_1.default.createElement("option", { key: o, value: o, className: "bg-tax-axis-surface-2" }, o)))),
107
117
  react_1.default.createElement(Chevron, null)));
108
118
  } })),
109
119
  react_1.default.createElement("div", null,
@@ -89,5 +89,5 @@ exports.intakeDefaultValues = {
89
89
  hsaContributions: "0",
90
90
  wotcHires: "No",
91
91
  familyEmployed: "No",
92
- period: "Full Year",
92
+ period: "YTD",
93
93
  };
@@ -18,14 +18,18 @@ function EngagementHeader({ profile, palette }) {
18
18
  marginBottom: 16,
19
19
  boxShadow: "0 1px 3px rgba(0,0,0,0.04)",
20
20
  };
21
+ // C-Corps have a flat 21% federal rate and no filing status
22
+ const isCCorp = profile.entity === "C-Corporation";
23
+ const fedRateDisplay = isCCorp ? "21%" : (profile.federalRate || "24%");
24
+ const filingDisplay = isCCorp ? "\u2014" : (profile.filingStatus || "MFJ");
21
25
  const rows = [
22
26
  ["Entity", profile.entity],
23
27
  ["Industry", profile.industry],
24
28
  ["States", states.join(", ") || "\u2014"],
25
29
  ["Tax Year", profile.year],
26
- ["Filing", profile.filingStatus || "MFJ"],
30
+ ["Filing", filingDisplay],
27
31
  ["SSTB", profile.sstb || "No"],
28
- ["Fed Rate", profile.federalRate || "24%"],
32
+ ["Fed Rate", fedRateDisplay],
29
33
  ["State Rate", (profile.stateRate || "4.95") + "%"],
30
34
  ["Revenue", "$" + (rev || 0).toLocaleString()],
31
35
  ["Net Income", "$" + (0, compute_1.parseNum)(profile.netIncome).toLocaleString()],
@@ -50,9 +50,9 @@ function TaxAxisPreparerWorkpaper({ profile, onBack, onToggleToClient, liveStrat
50
50
  const grouped = priorityOrder
51
51
  .map(p => ({ priority: p, items: eligible.filter(s => s.priority === p).sort((a, b) => b.score - a.score) }))
52
52
  .filter(g => g.items.length > 0);
53
- // Interaction pairs
54
- const eligibleRanks = new Set(eligible.map(s => s.rank));
55
- const activeInteractions = data_1.WORKPAPER_INTERACTION_PAIRS.filter(([a, b]) => eligibleRanks.has(a) && eligibleRanks.has(b));
53
+ // Interaction pairs — match on original catalog strategy number, not re-indexed rank
54
+ const eligibleStrategyNumbers = new Set(eligible.map(s => { var _a; return (_a = s.strategyNumber) !== null && _a !== void 0 ? _a : s.rank; }));
55
+ const activeInteractions = data_1.WORKPAPER_INTERACTION_PAIRS.filter(([a, b]) => eligibleStrategyNumbers.has(a) && eligibleStrategyNumbers.has(b));
56
56
  // Savings summary
57
57
  const sortedByMid = [...eligible].sort((a, b) => {
58
58
  var _a, _b, _c, _d;
@@ -1,6 +1,27 @@
1
1
  import type { Strategy, ComputedMap } from "../types";
2
2
  /** Raw engine output blob from the LLM run. */
3
3
  export type EngineOutput = Record<string, unknown> | null;
4
+ /** Pre-computed formula engine results from outputPayload.meta.preComputed. */
5
+ export interface PreComputedResults {
6
+ results: Record<string, PreComputedStrategyResult>;
7
+ completedCount?: number;
8
+ excludedCount?: number;
9
+ failedCount?: number;
10
+ totalSavingsLow?: number;
11
+ totalSavingsHigh?: number;
12
+ computedAt?: string;
13
+ }
14
+ export interface PreComputedStrategyResult {
15
+ strategy_id: string;
16
+ status: "COMPLETE" | "EXCLUDED" | "FAILED";
17
+ result_gross: number | null;
18
+ confidence_interval?: {
19
+ low: number;
20
+ high: number;
21
+ basis?: string;
22
+ } | null;
23
+ trace?: Record<string, unknown>;
24
+ }
4
25
  /** Adapter result passed to client-report / preparer-workpaper components. */
5
26
  export interface EngineOutputAdapterResult {
6
27
  strategies: Strategy[];
@@ -14,7 +35,9 @@ export interface EngineStrategyAnalysis {
14
35
  weighted_score: number;
15
36
  quick_win: boolean;
16
37
  engagement_recommendation: string;
38
+ risk_disclosure?: string | null;
17
39
  calculation_trace?: Record<string, unknown>;
40
+ _reconciledPreComputed?: boolean;
18
41
  [key: string]: unknown;
19
42
  }
20
43
  /** CPA workflow section from the engine. */
@@ -23,4 +46,4 @@ export interface EngineCpaWorkflow {
23
46
  engagement_recommendations: string[];
24
47
  [key: string]: unknown;
25
48
  }
26
- export declare function useEngineOutput(_engineOutput: EngineOutput): EngineOutputAdapterResult | null;
49
+ export declare function useEngineOutput(engineOutput: EngineOutput, preComputed?: PreComputedResults | null): EngineOutputAdapterResult | null;
@@ -1,12 +1,120 @@
1
1
  "use strict";
2
- // useEngineOutput — minimal stub to unblock builds.
3
- // TODO: centralise narrative-field mapping (why_it_applies, source_documents, specialistNote) here.
2
+ // useEngineOutput — maps raw engine output + formula pre-computed results
3
+ // into the canonical Strategy[] + ComputedMap consumed by report components.
4
+ //
5
+ // Only strategies validated by the deterministic formula engine are included.
6
+ // This prevents LLM-hallucinated strategies and dollar amounts from reaching
7
+ // the Client Report or Preparer Workpaper.
4
8
  Object.defineProperty(exports, "__esModule", { value: true });
5
9
  exports.useEngineOutput = useEngineOutput;
6
10
  const react_1 = require("react");
11
+ const strategyNarrative_1 = require("../data/strategyNarrative");
12
+ const data_1 = require("../data");
7
13
  // ---------------------------------------------------------------------------
8
- // Hook — pass-through stub (returns null, no mapping yet)
14
+ // Helpers
9
15
  // ---------------------------------------------------------------------------
10
- function useEngineOutput(_engineOutput) {
11
- return (0, react_1.useMemo)(() => null, [_engineOutput]);
16
+ /** Parse "S10" -> 10. Returns null if strategyId doesn't match the SN pattern. */
17
+ function parseStrategyNumber(strategyId) {
18
+ const match = strategyId.match(/^S(\d+)$/);
19
+ return match ? parseInt(match[1], 10) : null;
20
+ }
21
+ // Static catalog lookups keyed by original strategy rank (1-25).
22
+ const CATEGORY_BY_RANK = new Map();
23
+ const CODE_BY_RANK = new Map();
24
+ for (const s of data_1.STRATEGIES) {
25
+ CATEGORY_BY_RANK.set(s.rank, s.cat);
26
+ CODE_BY_RANK.set(s.rank, s.code);
27
+ }
28
+ // ---------------------------------------------------------------------------
29
+ // Core mapping — engine strategy_analysis entry -> canonical Strategy
30
+ // ---------------------------------------------------------------------------
31
+ function mapAnalysisToStrategy(sa, idx, pc) {
32
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
33
+ const ct = ((_a = sa.calculation_trace) !== null && _a !== void 0 ? _a : {});
34
+ const ci = pc === null || pc === void 0 ? void 0 : pc.confidence_interval;
35
+ // Dollar amounts: prefer preComputed CI, then calculation_trace CI
36
+ const lo = ci ? Math.round(ci.low) : Math.round((_c = (_b = ct.confidence_interval) === null || _b === void 0 ? void 0 : _b.low) !== null && _c !== void 0 ? _c : 0);
37
+ const hi = ci ? Math.round(ci.high) : Math.round((_e = (_d = ct.confidence_interval) === null || _d === void 0 ? void 0 : _d.high) !== null && _e !== void 0 ? _e : 0);
38
+ const strategyNumber = parseStrategyNumber(sa.strategy_id);
39
+ const cat = strategyNumber !== null ? ((_f = CATEGORY_BY_RANK.get(strategyNumber)) !== null && _f !== void 0 ? _f : "income") : "income";
40
+ const staticNarrative = strategyNumber !== null ? strategyNarrative_1.STRATEGY_NARRATIVE[strategyNumber] : undefined;
41
+ // Source documents from calculation_trace
42
+ const sourceDocuments = Array.isArray(ct.source_documents) ? ct.source_documents : undefined;
43
+ const specialistNote = typeof ct.specialist_note === "string" && ct.specialist_note.trim() !== ""
44
+ ? ct.specialist_note
45
+ : ((_g = staticNarrative === null || staticNarrative === void 0 ? void 0 : staticNarrative.whySpecialist) !== null && _g !== void 0 ? _g : undefined);
46
+ const positionStrength = typeof ct.position_strength === "string" ? ct.position_strength : undefined;
47
+ const authority = typeof ct.irs_cite === "string" ? ct.irs_cite : undefined;
48
+ const formsFromTrace = Array.isArray(ct.forms_required)
49
+ ? ct.forms_required.join(", ")
50
+ : undefined;
51
+ // Timeline bucket: derive from priority as a reasonable default
52
+ const priority = (sa.priority_tier || "MEDIUM").toUpperCase();
53
+ const timelineBucket = sa.quick_win ? "now" : priority === "HIGH" ? "now" : priority === "MEDIUM" ? "30d" : "90d";
54
+ return {
55
+ rank: idx + 1,
56
+ strategyNumber: strategyNumber !== null && strategyNumber !== void 0 ? strategyNumber : undefined,
57
+ code: (_h = (strategyNumber !== null ? CODE_BY_RANK.get(strategyNumber) : undefined)) !== null && _h !== void 0 ? _h : sa.strategy_id,
58
+ name: sa.strategy_name,
59
+ cat,
60
+ priority,
61
+ score: sa.weighted_score != null
62
+ ? Math.min(Math.round((sa.weighted_score / 25) * 100), 100)
63
+ : (priority === "HIGH" ? 85 : priority === "MEDIUM" ? 70 : 55),
64
+ entities: [],
65
+ lo,
66
+ hi,
67
+ timeline: timelineBucket === "now" ? "Act now" : timelineBucket === "30d" ? "Within 30 days" : "Within 90 days",
68
+ timelineBucket,
69
+ abstract: sa.engagement_recommendation || "",
70
+ warning: (_j = sa.risk_disclosure) !== null && _j !== void 0 ? _j : null,
71
+ forms: formsFromTrace,
72
+ authority,
73
+ sourceDocuments,
74
+ specialistNote,
75
+ positionStrength,
76
+ quickWin: sa.quick_win,
77
+ };
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Hook
81
+ // ---------------------------------------------------------------------------
82
+ function useEngineOutput(engineOutput, preComputed) {
83
+ return (0, react_1.useMemo)(() => {
84
+ var _a;
85
+ if (!engineOutput)
86
+ return null;
87
+ const strategyAnalysis = engineOutput.strategy_analysis;
88
+ if (!Array.isArray(strategyAnalysis) || strategyAnalysis.length === 0)
89
+ return null;
90
+ const pcResults = (_a = preComputed === null || preComputed === void 0 ? void 0 : preComputed.results) !== null && _a !== void 0 ? _a : {};
91
+ // Filter: only include strategies where the formula engine returned COMPLETE
92
+ // OR the reconciler tagged _reconciledPreComputed: true.
93
+ const validated = strategyAnalysis.filter((sa) => {
94
+ const pc = pcResults[sa.strategy_id];
95
+ if ((pc === null || pc === void 0 ? void 0 : pc.status) === "COMPLETE")
96
+ return true;
97
+ if (sa._reconciledPreComputed === true)
98
+ return true;
99
+ return false;
100
+ });
101
+ if (validated.length === 0)
102
+ return null;
103
+ // Map to canonical Strategy[]
104
+ const strategies = validated.map((sa, idx) => {
105
+ const pc = pcResults[sa.strategy_id];
106
+ return mapAnalysisToStrategy(sa, idx, pc);
107
+ });
108
+ // Build ComputedMap keyed by rank (1-based index matching strategies array)
109
+ const computedMap = new Map();
110
+ for (const s of strategies) {
111
+ computedMap.set(s.rank, {
112
+ lo: s.lo,
113
+ hi: s.hi,
114
+ sources: s.sources,
115
+ trace: s.trace,
116
+ });
117
+ }
118
+ return { strategies, computedMap };
119
+ }, [engineOutput, preComputed]);
12
120
  }
@@ -16,20 +16,20 @@ exports.NEXT_STEPS = {
16
16
  8: "Provide your property appraisal or purchase records. A cost segregation study will identify reclassifiable assets.",
17
17
  9: "Increase your payroll HSA deduction this pay period. Your preparer will confirm your remaining contribution room.",
18
18
  10: "Start logging business meals with date, attendees, and business purpose. 50% of qualifying meals are deductible.",
19
- 11: "Review your current professional service expenses with your preparer to ensure all are properly categorized.",
20
- 12: "Photograph your dedicated workspace and collect mortgage and utility bills for the year.",
21
- 13: "Your preparer will evaluate your current employee health insurance setup for credit eligibility.",
22
- 14: "Consider bunching two years of charitable contributions into one year for maximum deduction impact.",
23
- 15: "Your preparer will file a PTE election with your state. Deadline varies by state.",
24
- 16: "Your preparer will review your business interest expense for §163(j) optimization.",
25
- 17: "Discuss year-end timing of income and expenses with your preparer before December.",
26
- 18: "Identify qualifying capital gains and potential Opportunity Zone investments with your advisor.",
27
- 19: "Have your commercial property evaluated for energy efficiency certification.",
28
- 20: "Your preparer will review your current accounting method for optimization opportunities.",
29
- 21: "Provide your POS tip reporting data. Your preparer will calculate the deduction.",
30
- 22: "Provide your payroll overtime summary. Your preparer will calculate the OBBBA deduction.",
19
+ 11: "Photograph your dedicated workspace and collect mortgage, insurance, and utility bills for the year. Your preparer will compare actual vs. simplified methods.",
20
+ 12: "Your preparer will draft an accountable plan document and board resolution for tax-free reimbursement of qualifying employee business expenses.",
21
+ 13: "Document fair market rental rates for your home and schedule qualifying business use days. Your preparer will prepare the rental agreement and FMV documentation.",
22
+ 14: "Document bona fide job duties and market-rate wages for family members working in the business. Your preparer will set up proper payroll documentation.",
23
+ 15: "Your preparer will evaluate your state's pass-through entity election and calculate the federal deduction benefit versus your current SALT position.",
24
+ 16: "Your preparer will assess qualifying energy-efficient commercial building improvements and determine applicable deduction or credit tiers.",
25
+ 17: "Your preparer will compare standard mileage rate versus actual expense methods and evaluate heavy SUV §179 eligibility for your business vehicles.",
26
+ 18: "Your preparer will review group-term life insurance, disability, and other employer-provided coverage for tax-efficient structuring under IRC employer benefit rules.",
27
+ 19: "Your preparer will evaluate qualifying real property exchanges and coordinate identification and exchange period deadlines with a qualified intermediary.",
28
+ 20: "Your preparer will verify qualified small business stock eligibility, holding period requirements, and the $10M or 10x-basis exclusion limit for capital gains.",
29
+ 21: "Your preparer will calculate the OBBBA qualified tip income deduction for eligible tipped employees and verify documentation requirements.",
30
+ 22: "Your preparer will evaluate OBBBA overtime pay deduction eligibility and calculate the incremental tax deduction for qualifying overtime wages.",
31
31
  23: "Your preparer will review prior-year R&E amortization for catch-up eligibility.",
32
- 24: "Document your employer-provided childcare facilities or referral services.",
32
+ 24: "Your preparer will evaluate employer-provided childcare facility or referral service expenditures and calculate the 25% qualified expenditure credit.",
33
33
  25: "Your preparer will evaluate Trump Account eligibility and optimal contribution levels.",
34
34
  };
35
35
  exports.INTERACTION_PAIRS = [[1, 4], [1, 7], [2, 3]];
@@ -17,7 +17,10 @@ exports.SOURCE_DESC = {
17
17
  "§223": "Health Savings Account contributions for HDHP-covered taxpayers",
18
18
  "§274": "Business meal deductions, generally limited to 50% of qualifying expenses",
19
19
  "§162": "Ordinary and necessary business expenses",
20
+ "§62": "Accountable plan reimbursements for qualifying employee business expenses under an employer-maintained plan",
20
21
  "§280A": "Home office deduction for the business use of a residence",
22
+ "§280A(g)": "Augusta Rule: exclusion of rental income for personal residence rented 14 days or fewer per year",
23
+ "§73": "Employment of family members; wage deduction and FICA/FUTA savings for qualifying family employees",
21
24
  "§45R": "Small employer health insurance credit",
22
25
  "§170": "Charitable contribution deductions",
23
26
  "OBBBA §70109": "Pass-through entity tax election workaround for the SALT cap",
@@ -26,6 +29,9 @@ exports.SOURCE_DESC = {
26
29
  "§1400Z": "Qualified Opportunity Zone capital gains deferral",
27
30
  "§179D": "Energy efficient commercial buildings deduction",
28
31
  "§446": "Permissible accounting methods and changes",
32
+ "§79, §264": "Group-term life insurance exclusion and employer-owned life insurance tax treatment",
33
+ "§1031": "Like-kind exchange deferral of recognized gain on qualifying real property dispositions",
34
+ "§1202": "Qualified Small Business Stock gain exclusion: up to $10M or 10x adjusted basis for qualifying C-Corp stock held 5+ years",
29
35
  "OBBBA": "OBBBA-introduced deductions and credits (Qualified Tips, Overtime Pay, Trump Accounts)",
30
36
  "§174A": "OBBBA-restored full expensing for domestic research and experimental expenditures",
31
37
  "§45F": "Employer-provided childcare facilities and services credit",
@@ -26,6 +26,8 @@ export interface StrategyTraceStep {
26
26
  }
27
27
  export interface Strategy {
28
28
  rank: number;
29
+ /** Original catalog strategy number (1-25). Stable across re-indexing. */
30
+ strategyNumber?: number;
29
31
  code: string;
30
32
  name: string;
31
33
  cat: StrategyCategory;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paro.io/expert-shared-components",
3
- "version": "1.14.69",
3
+ "version": "1.14.71",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {