@paro.io/expert-shared-components 1.14.71 → 1.14.73
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.
- package/lib/components/DocumentCenter/ClientDocumentsTable.js +20 -23
- package/lib/components/TaxAxis/TaxAxisApi.d.ts +8 -0
- package/lib/components/TaxAxis/TaxAxisShell.js +27 -7
- package/lib/tax-axis/components/clientReport/ExecutiveSummary.d.ts +4 -2
- package/lib/tax-axis/components/clientReport/ExecutiveSummary.js +8 -6
- package/lib/tax-axis/components/clientReport/ImplementationRoadmap.d.ts +4 -3
- package/lib/tax-axis/components/clientReport/ImplementationRoadmap.js +2 -2
- package/lib/tax-axis/components/clientReport/TaxAxisClientReport.js +14 -5
- package/lib/tax-axis/components/intake/IntakeCtaCards.d.ts +8 -2
- package/lib/tax-axis/components/intake/IntakeCtaCards.js +70 -7
- package/lib/tax-axis/components/intake/TaxAxisIntake.js +1 -1
- package/lib/tax-axis/components/processing/TaxAxisProcessing.d.ts +5 -1
- package/lib/tax-axis/components/processing/TaxAxisProcessing.js +27 -11
- package/lib/tax-axis/components/prospectReport/ProspectNextSteps.js +3 -1
- package/lib/tax-axis/components/prospectReport/ProspectReportSkeleton.d.ts +2 -0
- package/lib/tax-axis/components/prospectReport/ProspectReportSkeleton.js +78 -0
- package/lib/tax-axis/components/prospectReport/ProspectStrategyCard.d.ts +7 -1
- package/lib/tax-axis/components/prospectReport/ProspectStrategyCard.js +8 -6
- package/lib/tax-axis/components/prospectReport/TaxAxisProspectReport.d.ts +33 -1
- package/lib/tax-axis/components/prospectReport/TaxAxisProspectReport.js +57 -56
- package/lib/tax-axis/lib/adapters/useEngineOutput.js +39 -3
- package/lib/tax-axis/lib/documentFieldCatalog.d.ts +5 -10
- package/lib/tax-axis/lib/documentFieldCatalog.js +115 -329
- package/package.json +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ProspectReportSkeleton = ProspectReportSkeleton;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const theme_1 = require("./theme");
|
|
9
|
+
// Dark-themed skeleton shown while generateProspectReport mutation is in-flight
|
|
10
|
+
function Bone({ w = "100%", h = 16, radius = 6, mb = 0 }) {
|
|
11
|
+
return (react_1.default.createElement("div", { style: {
|
|
12
|
+
width: w,
|
|
13
|
+
height: h,
|
|
14
|
+
borderRadius: radius,
|
|
15
|
+
background: `linear-gradient(90deg, ${theme_1.T.surface2} 25%, rgba(255,255,255,0.04) 50%, ${theme_1.T.surface2} 75%)`,
|
|
16
|
+
backgroundSize: "400% 100%",
|
|
17
|
+
animation: "skeletonShimmer 1.6s infinite linear",
|
|
18
|
+
marginBottom: mb,
|
|
19
|
+
flexShrink: 0,
|
|
20
|
+
} }));
|
|
21
|
+
}
|
|
22
|
+
function ProspectReportSkeleton() {
|
|
23
|
+
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
24
|
+
react_1.default.createElement("style", null, `
|
|
25
|
+
@keyframes skeletonShimmer {
|
|
26
|
+
0% { background-position: 100% 0 }
|
|
27
|
+
100% { background-position: -100% 0 }
|
|
28
|
+
}
|
|
29
|
+
`),
|
|
30
|
+
react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 16, padding: "36px 32px 28px", marginBottom: 24, boxShadow: theme_1.T.shadowLg, overflow: "hidden" } },
|
|
31
|
+
react_1.default.createElement("div", { style: { height: 4, background: `linear-gradient(90deg,${theme_1.T.accent},${theme_1.T.orange})`, marginBottom: 24, borderRadius: 2 } }),
|
|
32
|
+
react_1.default.createElement("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 20 } },
|
|
33
|
+
react_1.default.createElement(Bone, { w: 120, h: 12 }),
|
|
34
|
+
react_1.default.createElement(Bone, { w: 80, h: 12 })),
|
|
35
|
+
react_1.default.createElement(Bone, { w: "45%", h: 10, mb: 12 }),
|
|
36
|
+
react_1.default.createElement(Bone, { w: "70%", h: 36, mb: 12 }),
|
|
37
|
+
react_1.default.createElement(Bone, { w: "50%", h: 14, mb: 24 }),
|
|
38
|
+
react_1.default.createElement("div", { style: { height: 1, background: theme_1.T.border, marginBottom: 20 } }),
|
|
39
|
+
react_1.default.createElement(Bone, { w: "55%", h: 44, mb: 12 }),
|
|
40
|
+
react_1.default.createElement(Bone, { w: "30%", h: 12 })),
|
|
41
|
+
react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", marginBottom: 24, boxShadow: theme_1.T.shadow } },
|
|
42
|
+
react_1.default.createElement(Bone, { w: "40%", h: 10, mb: 16 }),
|
|
43
|
+
react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 16 } }, [1, 2, 3].map(i => (react_1.default.createElement("div", { key: i },
|
|
44
|
+
react_1.default.createElement(Bone, { w: "60%", h: 28, mb: 8 }),
|
|
45
|
+
react_1.default.createElement(Bone, { w: "80%", h: 10 })))))),
|
|
46
|
+
react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", marginBottom: 24, boxShadow: theme_1.T.shadow } },
|
|
47
|
+
react_1.default.createElement(Bone, { w: "25%", h: 10, mb: 16 }),
|
|
48
|
+
react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 16 } }, [1, 2, 3, 4].map(i => (react_1.default.createElement("div", { key: i },
|
|
49
|
+
react_1.default.createElement(Bone, { w: "70%", h: 22, mb: 8 }),
|
|
50
|
+
react_1.default.createElement(Bone, { w: "80%", h: 10 })))))),
|
|
51
|
+
react_1.default.createElement("div", { style: { marginBottom: 24 } },
|
|
52
|
+
react_1.default.createElement(Bone, { w: "30%", h: 10, mb: 12 }),
|
|
53
|
+
react_1.default.createElement(Bone, { w: "100%", h: 2, mb: 16 }),
|
|
54
|
+
react_1.default.createElement(Bone, { w: "100%", h: 14, mb: 8 }),
|
|
55
|
+
react_1.default.createElement(Bone, { w: "95%", h: 14, mb: 8 }),
|
|
56
|
+
react_1.default.createElement(Bone, { w: "80%", h: 14 })),
|
|
57
|
+
[1, 2, 3].map(i => (react_1.default.createElement("div", { key: i, style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", marginBottom: 14, boxShadow: theme_1.T.shadow } },
|
|
58
|
+
react_1.default.createElement("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 20 } },
|
|
59
|
+
react_1.default.createElement("div", { style: { display: "flex", gap: 14, alignItems: "center" } },
|
|
60
|
+
react_1.default.createElement("div", { style: { width: 34, height: 34, borderRadius: "50%", background: theme_1.T.surface2 } }),
|
|
61
|
+
react_1.default.createElement("div", null,
|
|
62
|
+
react_1.default.createElement(Bone, { w: 160, h: 16, mb: 8 }),
|
|
63
|
+
react_1.default.createElement(Bone, { w: 100, h: 10 }))),
|
|
64
|
+
react_1.default.createElement("div", { style: { textAlign: "right" } },
|
|
65
|
+
react_1.default.createElement(Bone, { w: 90, h: 22, mb: 6 }),
|
|
66
|
+
react_1.default.createElement(Bone, { w: 80, h: 10 }))),
|
|
67
|
+
react_1.default.createElement(Bone, { w: "100%", h: 12, mb: 6 }),
|
|
68
|
+
react_1.default.createElement(Bone, { w: "90%", h: 12, mb: 6 }),
|
|
69
|
+
react_1.default.createElement(Bone, { w: "75%", h: 12, mb: 16 }),
|
|
70
|
+
react_1.default.createElement(Bone, { w: "100%", h: 12, mb: 6 }),
|
|
71
|
+
react_1.default.createElement(Bone, { w: "85%", h: 12 })))),
|
|
72
|
+
react_1.default.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 10, padding: "20px 0", color: theme_1.T.text4, fontFamily: theme_1.T.body, fontSize: 12 } },
|
|
73
|
+
react_1.default.createElement("div", { style: { width: 6, height: 6, borderRadius: "50%", background: theme_1.T.accent, animation: "pulse 1s infinite" } }),
|
|
74
|
+
"Generating your prospect report\u2026"),
|
|
75
|
+
react_1.default.createElement("style", null, `
|
|
76
|
+
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.3} }
|
|
77
|
+
`)));
|
|
78
|
+
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { Strategy, ClientProfile, ComputedMap } from "../../lib/types";
|
|
3
|
+
export interface LlmStrategyNarrative {
|
|
4
|
+
whyItMatters: string;
|
|
5
|
+
whatWeDo: string;
|
|
6
|
+
}
|
|
3
7
|
interface ProspectStrategyCardProps {
|
|
4
8
|
strategy: Strategy;
|
|
5
9
|
index: number;
|
|
6
10
|
profile: ClientProfile;
|
|
7
11
|
computed: ComputedMap;
|
|
12
|
+
/** LLM-generated narrative — when present, overrides all static/computed text */
|
|
13
|
+
llmNarrative?: LlmStrategyNarrative;
|
|
8
14
|
}
|
|
9
|
-
export declare function ProspectStrategyCard({ strategy: s, index: i, profile, computed }: ProspectStrategyCardProps): React.JSX.Element;
|
|
15
|
+
export declare function ProspectStrategyCard({ strategy: s, index: i, profile, computed, llmNarrative }: ProspectStrategyCardProps): React.JSX.Element;
|
|
10
16
|
export {};
|
|
@@ -8,17 +8,19 @@ const react_1 = __importDefault(require("react"));
|
|
|
8
8
|
const strategyNarrative_1 = require("../../lib/data/strategyNarrative");
|
|
9
9
|
const strategyProspect_1 = require("../../lib/data/strategyProspect");
|
|
10
10
|
const theme_1 = require("./theme");
|
|
11
|
-
function ProspectStrategyCard({ strategy: s, index: i, profile, computed }) {
|
|
12
|
-
var _a, _b;
|
|
11
|
+
function ProspectStrategyCard({ strategy: s, index: i, profile, computed, llmNarrative }) {
|
|
12
|
+
var _a, _b, _c, _d;
|
|
13
13
|
const p = strategyProspect_1.STRATEGY_PROSPECT[s.rank];
|
|
14
14
|
const narr = strategyNarrative_1.STRATEGY_NARRATIVE[s.rank];
|
|
15
15
|
const c = computed.get(s.rank);
|
|
16
|
-
const whyText = narr ? narr.whyMatters(profile) : strategyNarrative_1.NARRATIVE_FALLBACK.whyMatters(s);
|
|
17
|
-
const breakdownText = narr ? narr.breakdown(profile, c) : strategyNarrative_1.NARRATIVE_FALLBACK.breakdown(s, c);
|
|
18
|
-
const specialistText = (narr === null || narr === void 0 ? void 0 : narr.whySpecialist) || strategyNarrative_1.NARRATIVE_FALLBACK.whySpecialist(s);
|
|
19
|
-
const nextText = narr ? narr.nextSteps : strategyNarrative_1.NARRATIVE_FALLBACK.nextSteps(s);
|
|
20
16
|
const scaledLo = Math.round(((_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo) / 1000);
|
|
21
17
|
const scaledHi = Math.round(((_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi) / 1000);
|
|
18
|
+
// When LLM narrative is provided, use it for whyItMatters and whatWeDo.
|
|
19
|
+
// HOW SAVINGS BREAK DOWN and WHY YOU NEED A SPECIALIST remain deterministic.
|
|
20
|
+
const whyText = (_c = llmNarrative === null || llmNarrative === void 0 ? void 0 : llmNarrative.whyItMatters) !== null && _c !== void 0 ? _c : (narr ? narr.whyMatters(profile) : strategyNarrative_1.NARRATIVE_FALLBACK.whyMatters(s));
|
|
21
|
+
const breakdownText = narr ? narr.breakdown(profile, c) : strategyNarrative_1.NARRATIVE_FALLBACK.breakdown(s, c);
|
|
22
|
+
const specialistText = (narr === null || narr === void 0 ? void 0 : narr.whySpecialist) || strategyNarrative_1.NARRATIVE_FALLBACK.whySpecialist(s);
|
|
23
|
+
const nextText = (_d = llmNarrative === null || llmNarrative === void 0 ? void 0 : llmNarrative.whatWeDo) !== null && _d !== void 0 ? _d : (narr ? narr.nextSteps : strategyNarrative_1.NARRATIVE_FALLBACK.nextSteps(s));
|
|
22
24
|
return (react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", boxShadow: theme_1.T.shadow } },
|
|
23
25
|
react_1.default.createElement("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 16 } },
|
|
24
26
|
react_1.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14 } },
|
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { ClientProfile, TaxAxisScreenProps } from "../../lib/types";
|
|
3
|
+
import type { LlmStrategyNarrative } from "./ProspectStrategyCard";
|
|
4
|
+
/** LLM output payload shape returned by generateProspectReport mutation */
|
|
5
|
+
export interface LlmOutputPayload {
|
|
6
|
+
execSummary: string;
|
|
7
|
+
strategyNarratives: Record<string, LlmStrategyNarrative>;
|
|
8
|
+
additionalStrategiesBlurb: string;
|
|
9
|
+
}
|
|
10
|
+
/** Backend-computed math payload */
|
|
11
|
+
export interface BackendComputePayload {
|
|
12
|
+
savingsLo: number;
|
|
13
|
+
savingsHi: number;
|
|
14
|
+
eligibleCount: number;
|
|
15
|
+
highPriorityCount: number;
|
|
16
|
+
quickWinCount: number;
|
|
17
|
+
gapBlock: {
|
|
18
|
+
currentTax: number;
|
|
19
|
+
optimizedTax: number;
|
|
20
|
+
realizedSavings: number;
|
|
21
|
+
realizedPctOfCurrent: number;
|
|
22
|
+
fedRatePct: number;
|
|
23
|
+
stateRatePct: number;
|
|
24
|
+
};
|
|
25
|
+
barData: Array<{
|
|
26
|
+
name: string;
|
|
27
|
+
mid: number;
|
|
28
|
+
pct: number;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
3
31
|
export interface TaxAxisProspectReportProps extends TaxAxisScreenProps {
|
|
4
32
|
profile: ClientProfile;
|
|
33
|
+
/** When provided (from generateProspectReport mutation), all math comes from backend */
|
|
34
|
+
backendComputePayload?: BackendComputePayload | null;
|
|
35
|
+
/** LLM-generated narrative text from the mutation */
|
|
36
|
+
outputPayload?: LlmOutputPayload | null;
|
|
5
37
|
onUpgrade?: () => void;
|
|
6
38
|
onPresent?: () => void;
|
|
7
39
|
onReset?: () => void;
|
|
8
40
|
}
|
|
9
|
-
export declare function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }: TaxAxisProspectReportProps): React.JSX.Element;
|
|
41
|
+
export declare function TaxAxisProspectReport({ profile, backendComputePayload, outputPayload, onUpgrade, onPresent, onReset, }: TaxAxisProspectReportProps): React.JSX.Element;
|
|
@@ -35,47 +35,48 @@ const SampleAnalysisPreview_1 = require("./SampleAnalysisPreview");
|
|
|
35
35
|
const ProspectDocuments_1 = require("./ProspectDocuments");
|
|
36
36
|
const ProspectNextSteps_1 = require("./ProspectNextSteps");
|
|
37
37
|
const ProspectPrintView_1 = require("./ProspectPrintView");
|
|
38
|
-
function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
|
|
38
|
+
function TaxAxisProspectReport({ profile, backendComputePayload, outputPayload, onUpgrade, onPresent, onReset, }) {
|
|
39
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
39
40
|
const bizName = profile.bizName || "Client";
|
|
40
41
|
const [confirmReset, setConfirmReset] = (0, react_1.useState)(false);
|
|
41
42
|
const [printMode, setPrintMode] = (0, react_1.useState)(false);
|
|
42
|
-
// ═══ ELIGIBLE STRATEGIES ═══
|
|
43
|
+
// ═══ ELIGIBLE STRATEGIES (client-side, for rendering strategy cards) ═══
|
|
43
44
|
const eligible = (0, react_1.useMemo)(() => (0, compute_1.filterEligibleStrategies)(profile), [profile]);
|
|
44
45
|
const high = eligible.filter(s => s.priority === "HIGH");
|
|
45
46
|
const quick = eligible.filter(s => s.priority === "QUICK WIN");
|
|
46
|
-
// Top 3 by score for the sales narrative
|
|
47
47
|
const top3 = (0, react_1.useMemo)(() => [...eligible].sort((a, b) => b.score - a.score).slice(0, 3), [eligible]);
|
|
48
48
|
const remaining = eligible.length - top3.length;
|
|
49
49
|
const remainingNames = eligible.filter(s => !top3.includes(s)).slice(0, 4).map(s => s.name);
|
|
50
|
-
// ═══ SAVINGS RANGE
|
|
50
|
+
// ═══ SAVINGS RANGE — backend-authoritative when available ═══
|
|
51
51
|
const computed = (0, react_1.useMemo)(() => (0, compute_1.computeAllStrategies)(profile), [profile]);
|
|
52
|
-
const displayLo =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
52
|
+
const displayLo = backendComputePayload
|
|
53
|
+
? Math.round(backendComputePayload.savingsLo / 1000)
|
|
54
|
+
: Math.round(eligible.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) / 1000);
|
|
55
|
+
const displayHi = backendComputePayload
|
|
56
|
+
? Math.round(backendComputePayload.savingsHi / 1000)
|
|
57
|
+
: Math.round(eligible.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) / 1000);
|
|
58
|
+
// ═══ GAP BLOCK — backend-authoritative when available ═══
|
|
59
|
+
const fedRatePct = (_a = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.fedRatePct) !== null && _a !== void 0 ? _a : (parseFloat((profile.federalRate || "24").replace("%", "")) || 24);
|
|
60
|
+
const stateRatePct = (_b = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.stateRatePct) !== null && _b !== void 0 ? _b : (parseFloat(profile.stateRate || "4.95") || 4.95);
|
|
61
|
+
const currentTax = (_c = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.currentTax) !== null && _c !== void 0 ? _c : Math.round(((0, compute_1.parseNum)(profile.netIncome) || Math.round((0, compute_1.parseNum)(profile.revenue) * 0.20))
|
|
62
|
+
* (fedRatePct + stateRatePct) / 100);
|
|
63
|
+
const realizedSavings = (_d = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.realizedSavings) !== null && _d !== void 0 ? _d : Math.min(Math.round((displayLo + displayHi) / 2 * 1000 * 0.325), Math.round(currentTax * 0.6));
|
|
64
|
+
const optimizedTax = (_e = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.optimizedTax) !== null && _e !== void 0 ? _e : (currentTax - realizedSavings);
|
|
65
|
+
const realizedPctOfCurrent = (_f = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.realizedPctOfCurrent) !== null && _f !== void 0 ? _f : (currentTax > 0 ? Math.round((realizedSavings / currentTax) * 100) : 0);
|
|
66
|
+
// ═══ BAR CHART DATA ═══
|
|
67
|
+
const barData = (_g = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.barData) !== null && _g !== void 0 ? _g : (() => {
|
|
68
|
+
const totalMid = eligible.reduce((a, s) => { var _a, _b; const c = computed.get(s.rank); return a + (((_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo) + ((_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi)) / 2; }, 0);
|
|
69
|
+
return top3.map(s => { var _a, _b; const c = computed.get(s.rank); const mid = Math.round((((_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo) + ((_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi)) / 2); return { name: s.name, mid, pct: totalMid > 0 ? Math.round(mid / totalMid * 100) : 0 }; });
|
|
70
|
+
})();
|
|
57
71
|
const maxMid = Math.max(...barData.map(b => b.mid), 1);
|
|
58
|
-
// ═══ GAP BLOCK MATH — Current vs Optimized tax position ═══
|
|
59
|
-
const rev = (0, compute_1.parseNum)(profile.revenue);
|
|
60
|
-
const netIncome = (0, compute_1.parseNum)(profile.netIncome) || Math.round(rev * 0.20);
|
|
61
|
-
const fedRatePct = parseFloat((profile.federalRate || "24").replace("%", "")) || 24;
|
|
62
|
-
const stateRatePct = parseFloat(profile.stateRate || "4.95") || 4.95;
|
|
63
|
-
const combinedRate = (fedRatePct + stateRatePct) / 100;
|
|
64
|
-
const currentTax = Math.round(netIncome * combinedRate);
|
|
65
|
-
const theoreticalMidK = (displayLo + displayHi) / 2;
|
|
66
|
-
const realizedSavings = Math.min(Math.round(theoreticalMidK * 1000 * 0.325), Math.round(currentTax * 0.6));
|
|
67
|
-
const optimizedTax = currentTax - realizedSavings;
|
|
68
|
-
const realizedPctOfCurrent = currentTax > 0 ? Math.round((realizedSavings / currentTax) * 100) : 0;
|
|
69
|
-
// Section numbering — shifts when Additional Strategies section is present
|
|
70
|
-
const sectionNums = remaining > 0
|
|
71
|
-
? { recommended: "02", additional: "03", sample: "04", documents: "05", nextSteps: "06" }
|
|
72
|
-
: { recommended: "02", additional: null, sample: "03", documents: "04", nextSteps: "05" };
|
|
73
72
|
// ═══ DOCUMENT GROUPING ═══
|
|
74
73
|
const allDocsNeeded = [...new Set(eligible.flatMap(s => { var _a; return ((_a = strategyProspect_1.STRATEGY_PROSPECT[s.rank]) === null || _a === void 0 ? void 0 : _a.docsNeeded) || []; }))];
|
|
75
74
|
const requiredDocs = ["Profit & Loss Statement (current year)", "Balance Sheet (current year)"];
|
|
76
75
|
const recommendedDocs = ["Federal Tax Return (most recent)", "W-2 / Officer Compensation records"];
|
|
77
76
|
const conditionalDocs = allDocsNeeded.filter(d => !requiredDocs.includes(d) && !recommendedDocs.includes(d)).slice(0, 5);
|
|
78
|
-
|
|
77
|
+
const sectionNums = remaining > 0
|
|
78
|
+
? { recommended: "02", additional: "03", sample: "04", documents: "05", nextSteps: "06" }
|
|
79
|
+
: { recommended: "02", additional: null, sample: "03", documents: "04", nextSteps: "05" };
|
|
79
80
|
const handlePrint = () => {
|
|
80
81
|
setPrintMode(true);
|
|
81
82
|
requestAnimationFrame(() => {
|
|
@@ -86,18 +87,16 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
|
|
|
86
87
|
});
|
|
87
88
|
});
|
|
88
89
|
};
|
|
89
|
-
// ═══ PRINT MODE — Light theme layout ═══
|
|
90
90
|
if (printMode) {
|
|
91
91
|
return (react_1.default.createElement(ProspectPrintView_1.ProspectPrintView, { profile: profile, bizName: bizName, displayLo: displayLo, displayHi: displayHi, currentTax: currentTax, optimizedTax: optimizedTax, realizedSavings: realizedSavings, realizedPctOfCurrent: realizedPctOfCurrent, fedRatePct: fedRatePct, stateRatePct: stateRatePct, eligible: eligible, high: high, quick: quick, top3: top3, computed: computed, fmtTax: compute_1.fmtTax, fmtSavings: compute_1.fmtSavings, requiredDocs: requiredDocs, recommendedDocs: recommendedDocs, conditionalDocs: conditionalDocs }));
|
|
92
92
|
}
|
|
93
|
-
// ═══ INTERACTIVE MODE — Dark theme layout ═══
|
|
94
93
|
return (react_1.default.createElement("div", null,
|
|
95
94
|
react_1.default.createElement(ProspectCover_1.ProspectCover, { profile: profile, bizName: bizName, displayLo: displayLo, displayHi: displayHi }),
|
|
96
95
|
react_1.default.createElement(TaxPositionGap_1.TaxPositionGap, { bizName: bizName, currentTax: currentTax, optimizedTax: optimizedTax, realizedSavings: realizedSavings, realizedPctOfCurrent: realizedPctOfCurrent, fedRatePct: fedRatePct, stateRatePct: stateRatePct, eligibleCount: eligible.length, fmtTax: compute_1.fmtTax, fmtSavings: compute_1.fmtSavings }),
|
|
97
96
|
react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", marginBottom: 24, boxShadow: theme_1.T.shadow } },
|
|
98
97
|
react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.15em", color: theme_1.T.text4, marginBottom: 16, fontFamily: theme_1.T.mono } }, "AT A GLANCE"),
|
|
99
98
|
react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 16 } }, [
|
|
100
|
-
{ v: `$${displayLo}K
|
|
99
|
+
{ v: `$${displayLo}K–$${displayHi}K`, l: "Est. Savings" },
|
|
101
100
|
{ v: String(eligible.length), l: "Strategies Found" },
|
|
102
101
|
{ v: String(high.length), l: "High Impact" },
|
|
103
102
|
{ v: String(quick.length), l: "Quick Wins" },
|
|
@@ -109,29 +108,30 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
|
|
|
109
108
|
react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.mono } }, "01"),
|
|
110
109
|
react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", color: theme_1.T.text4, fontFamily: theme_1.T.body } }, "EXECUTIVE SUMMARY")),
|
|
111
110
|
react_1.default.createElement("div", { style: { height: 2, background: theme_1.T.accent, width: 48, marginBottom: 16 } }),
|
|
112
|
-
react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8, marginBottom: 12 } },
|
|
113
|
-
"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
111
|
+
(outputPayload === null || outputPayload === void 0 ? void 0 : outputPayload.execSummary) ? (react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8, marginBottom: 12 } }, outputPayload.execSummary)) : (react_1.default.createElement(react_1.default.Fragment, null,
|
|
112
|
+
react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8, marginBottom: 12 } },
|
|
113
|
+
"We evaluated ",
|
|
114
|
+
bizName,
|
|
115
|
+
"'s tax position against 25 federal strategies and identified ",
|
|
116
|
+
react_1.default.createElement("strong", { style: { color: theme_1.T.white } },
|
|
117
|
+
eligible.length,
|
|
118
|
+
" applicable opportunities"),
|
|
119
|
+
" with combined estimated savings of ",
|
|
120
|
+
react_1.default.createElement("strong", { style: { color: theme_1.T.accentLt } },
|
|
121
|
+
"$",
|
|
122
|
+
displayLo,
|
|
123
|
+
"K\u2013$",
|
|
124
|
+
displayHi,
|
|
125
|
+
"K annually"),
|
|
126
|
+
". ",
|
|
127
|
+
high.length,
|
|
128
|
+
" strategies could have significant impact."),
|
|
129
|
+
react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8 } },
|
|
130
|
+
"The top ",
|
|
131
|
+
top3.length,
|
|
132
|
+
" strategies are detailed below.",
|
|
133
|
+
quick.length > 0 ? ` ${quick.length} can be implemented this week with no structural changes.` : "",
|
|
134
|
+
" These are profile-based estimates \u2014 uploading financial documents will refine savings calculations and unlock all 25 strategies.")))),
|
|
135
135
|
react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "20px 24px", marginBottom: 24, boxShadow: theme_1.T.shadow } },
|
|
136
136
|
react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, color: theme_1.T.text4, textTransform: "uppercase", letterSpacing: "0.12em", marginBottom: 12, fontFamily: theme_1.T.mono } }, "SAVINGS DISTRIBUTION"),
|
|
137
137
|
barData.map((b, i) => (react_1.default.createElement("div", { key: i, style: { display: "flex", alignItems: "center", gap: 10, marginBottom: i < barData.length - 1 ? 8 : 0 } },
|
|
@@ -151,7 +151,10 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
|
|
|
151
151
|
react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.mono } }, "02"),
|
|
152
152
|
react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", color: theme_1.T.text4, fontFamily: theme_1.T.body } }, "RECOMMENDED STRATEGIES")),
|
|
153
153
|
react_1.default.createElement("div", { style: { height: 2, background: theme_1.T.accent, width: 48, marginBottom: 16 } }),
|
|
154
|
-
react_1.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 14 } }, top3.map((s, i) =>
|
|
154
|
+
react_1.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 14 } }, top3.map((s, i) => {
|
|
155
|
+
var _a;
|
|
156
|
+
return (react_1.default.createElement(ProspectStrategyCard_1.ProspectStrategyCard, { key: s.rank, strategy: s, index: i, profile: profile, computed: computed, llmNarrative: (_a = outputPayload === null || outputPayload === void 0 ? void 0 : outputPayload.strategyNarratives) === null || _a === void 0 ? void 0 : _a[`S${s.rank}`] }));
|
|
157
|
+
}))),
|
|
155
158
|
remaining > 0 && (react_1.default.createElement("div", { style: { marginBottom: 24 } },
|
|
156
159
|
react_1.default.createElement("div", { style: { display: "flex", alignItems: "baseline", gap: 10, marginBottom: 6 } },
|
|
157
160
|
react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.mono } }, "03"),
|
|
@@ -162,10 +165,8 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
|
|
|
162
165
|
"+ ",
|
|
163
166
|
remaining,
|
|
164
167
|
" more strategies identified"),
|
|
165
|
-
react_1.default.createElement("div", { style: { fontSize: 13, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.7, marginBottom: 10 } },
|
|
166
|
-
|
|
167
|
-
remainingNames.join(", "),
|
|
168
|
-
remaining > 4 ? " and more" : ""),
|
|
168
|
+
react_1.default.createElement("div", { style: { fontSize: 13, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.7, marginBottom: 10 } }, (outputPayload === null || outputPayload === void 0 ? void 0 : outputPayload.additionalStrategiesBlurb)
|
|
169
|
+
|| `Including: ${remainingNames.join(", ")}${remaining > 4 ? " and more" : ""}`),
|
|
169
170
|
react_1.default.createElement("div", { style: { fontSize: 12, color: theme_1.T.text3, fontFamily: theme_1.T.body, lineHeight: 1.6, borderLeft: `3px solid ${theme_1.T.accent}`, paddingLeft: 12 } }, "Full analysis with documents unlocks specific savings estimates for every eligible strategy.")))),
|
|
170
171
|
react_1.default.createElement(SampleAnalysisPreview_1.SampleAnalysisPreview, { sectionNum: sectionNums.sample, eligibleCount: eligible.length }),
|
|
171
172
|
react_1.default.createElement(ProspectDocuments_1.ProspectDocuments, { sectionNum: sectionNums.documents, requiredDocs: requiredDocs, recommendedDocs: recommendedDocs, conditionalDocs: conditionalDocs }),
|
|
@@ -25,6 +25,42 @@ for (const s of data_1.STRATEGIES) {
|
|
|
25
25
|
CATEGORY_BY_RANK.set(s.rank, s.cat);
|
|
26
26
|
CODE_BY_RANK.set(s.rank, s.code);
|
|
27
27
|
}
|
|
28
|
+
// Deterministic timeline bucket assignment by strategy number.
|
|
29
|
+
// Overrides LLM-assigned buckets to ensure consistent reports across runs.
|
|
30
|
+
// Bucket assignment logic:
|
|
31
|
+
// now: CPA can action in <2 hours, no external coordination needed
|
|
32
|
+
// 30d: Requires professional setup, plan changes, or payroll adjustments
|
|
33
|
+
// 90d: Requires specialist studies, amended returns, or multi-party coordination
|
|
34
|
+
const TIMELINE_BUCKET_BY_STRATEGY = {
|
|
35
|
+
// --- THIS WEEK: Documentation, reclassification, simple calculations ---
|
|
36
|
+
10: "now", // S10 Business Meals §274 — reclassify existing expenses
|
|
37
|
+
11: "now", // S11 Home Office §280A — calculate deduction, measure space
|
|
38
|
+
13: "now", // S13 Augusta Rule §280A(g) — document rental days
|
|
39
|
+
17: "now", // S17 Vehicle Expense §274 — review mileage logs, elect method
|
|
40
|
+
// --- WITHIN 30 DAYS: Professional action, plan setup, payroll changes ---
|
|
41
|
+
1: "30d", // S1 S-Corp Salary §3121 — BLS benchmarking memo, payroll adjustment
|
|
42
|
+
2: "30d", // S2 Section 179 — asset purchase timing, election
|
|
43
|
+
3: "30d", // S3 Bonus Depreciation §168(k) — election decision
|
|
44
|
+
7: "30d", // S7 Retirement §401(k) — plan design review, contribution change
|
|
45
|
+
9: "30d", // S9 HSA §223 — enrollment, contribution adjustment
|
|
46
|
+
12: "30d", // S12 Accountable Plan §62 — establish/update plan document
|
|
47
|
+
14: "30d", // S14 Family Employment §73 — payroll onboarding, age verification
|
|
48
|
+
21: "30d", // S21 Tips Deduction (OBBBA) — payroll system update
|
|
49
|
+
22: "30d", // S22 Overtime Deduction (OBBBA) — payroll system update
|
|
50
|
+
25: "30d", // S25 Trump Accounts (OBBBA) — account establishment
|
|
51
|
+
// --- WITHIN 90 DAYS: Specialist study, amended returns, multi-party coordination ---
|
|
52
|
+
4: "90d", // S4 QBI §199A — SSTB analysis, entity structure review
|
|
53
|
+
5: "90d", // S5 R&D Credit §41 — specialist study, Form 6765 prep
|
|
54
|
+
6: "90d", // S6 WOTC §51 — certification process, hiring program setup
|
|
55
|
+
8: "90d", // S8 Cost Segregation §168 — engineering study required
|
|
56
|
+
15: "90d", // S15 PTE/SALT — state election filing, deadline coordination
|
|
57
|
+
16: "90d", // S16 §179D Energy — energy efficiency certification required
|
|
58
|
+
18: "90d", // S18 Insurance §79/§264 — carrier coordination, policy restructuring
|
|
59
|
+
19: "90d", // S19 §1031 Like-Kind — qualified intermediary, identification periods
|
|
60
|
+
20: "90d", // S20 QSBS §1202 — stock qualification analysis, holding period review
|
|
61
|
+
23: "90d", // S23 §174A R&E Catch-Up — amended returns for 2022-2024
|
|
62
|
+
24: "90d", // S24 Childcare §45F — facility/program establishment
|
|
63
|
+
};
|
|
28
64
|
// ---------------------------------------------------------------------------
|
|
29
65
|
// Core mapping — engine strategy_analysis entry -> canonical Strategy
|
|
30
66
|
// ---------------------------------------------------------------------------
|
|
@@ -48,9 +84,9 @@ function mapAnalysisToStrategy(sa, idx, pc) {
|
|
|
48
84
|
const formsFromTrace = Array.isArray(ct.forms_required)
|
|
49
85
|
? ct.forms_required.join(", ")
|
|
50
86
|
: undefined;
|
|
51
|
-
// Timeline bucket: derive from priority as a reasonable default
|
|
52
87
|
const priority = (sa.priority_tier || "MEDIUM").toUpperCase();
|
|
53
|
-
|
|
88
|
+
// Deterministic bucket from static map; falls back to "30d" for unknown strategies.
|
|
89
|
+
const timelineBucket = TIMELINE_BUCKET_BY_STRATEGY[strategyNumber !== null && strategyNumber !== void 0 ? strategyNumber : 0] || "30d";
|
|
54
90
|
return {
|
|
55
91
|
rank: idx + 1,
|
|
56
92
|
strategyNumber: strategyNumber !== null && strategyNumber !== void 0 ? strategyNumber : undefined,
|
|
@@ -64,7 +100,7 @@ function mapAnalysisToStrategy(sa, idx, pc) {
|
|
|
64
100
|
entities: [],
|
|
65
101
|
lo,
|
|
66
102
|
hi,
|
|
67
|
-
timeline: timelineBucket === "now" ? "
|
|
103
|
+
timeline: timelineBucket === "now" ? "This week" : timelineBucket === "30d" ? "Within 30 days" : timelineBucket === "filing" ? "At filing" : "Within 90 days",
|
|
68
104
|
timelineBucket,
|
|
69
105
|
abstract: sa.engagement_recommendation || "",
|
|
70
106
|
warning: (_j = sa.risk_disclosure) !== null && _j !== void 0 ? _j : null,
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
export interface
|
|
2
|
-
label
|
|
1
|
+
export interface FieldDef {
|
|
2
|
+
label: string;
|
|
3
3
|
sourceRef?: string;
|
|
4
4
|
}
|
|
5
5
|
export interface CatalogSection {
|
|
6
6
|
head: string;
|
|
7
7
|
fields: string[];
|
|
8
8
|
}
|
|
9
|
-
export interface
|
|
9
|
+
export interface DocumentCatalogEntry {
|
|
10
10
|
sections: CatalogSection[];
|
|
11
|
-
fields: Record<string,
|
|
11
|
+
fields: Record<string, FieldDef>;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
* Keyed by documentType string returned by the backend in parsedData.documentType.
|
|
15
|
-
* When DocumentReviewModal receives parsedData, it looks up the documentType here
|
|
16
|
-
* to get section groupings and field labels. Falls back to flat rendering if no entry.
|
|
17
|
-
*/
|
|
18
|
-
export declare const DOCUMENT_FIELD_CATALOG: Record<string, CatalogEntry>;
|
|
13
|
+
export declare const DOCUMENT_FIELD_CATALOG: Record<string, DocumentCatalogEntry>;
|