@paro.io/expert-shared-components 1.14.67 → 1.14.68
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/tax-axis/components/intake/ClientParametersSection.js +6 -9
- package/lib/tax-axis/components/intake/StrategyRadar.js +25 -4
- package/lib/tax-axis/components/intake/intakeSchema.d.ts +3 -0
- package/lib/tax-axis/components/intake/intakeSchema.js +2 -0
- package/lib/tax-axis/lib/adapters/useEngineOutput.d.ts +46 -132
- package/lib/tax-axis/lib/adapters/useEngineOutput.js +44 -149
- package/lib/tax-axis/lib/compute/index.js +6 -0
- package/lib/tax-axis/lib/data/strategies.js +45 -47
- package/lib/tax-axis/lib/documentFieldCatalog.d.ts +4 -4
- package/lib/tax-axis/lib/documentFieldCatalog.js +190 -802
- package/lib/tax-axis/lib/types/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -70,12 +70,6 @@ const PERIOD_OPTIONS = ["Full Year", "YTD"];
|
|
|
70
70
|
const YEAR_OPTIONS = ["2024", "2025", "2026"];
|
|
71
71
|
function ClientParametersSection({ userContext = "expert", }) {
|
|
72
72
|
const { control, formState: { errors }, } = (0, react_hook_form_1.useFormContext)();
|
|
73
|
-
// TECH DEBT (post-BDO): Analysis Period dropdown is local useState only —
|
|
74
|
-
// value does not persist to form state because ClientProfile has no `period`
|
|
75
|
-
// field. computeAllStrategies does not read this value. Spec defect.
|
|
76
|
-
// Resolve post-BDO by either adding `period` to ClientProfile or removing
|
|
77
|
-
// the field entirely.
|
|
78
|
-
const [analysisPeriod, setAnalysisPeriod] = (0, react_1.useState)("Full Year");
|
|
79
73
|
const [expanded, setExpanded] = (0, react_1.useState)(true);
|
|
80
74
|
return (react_1.default.createElement(TaxAxisCard_1.TaxAxisCard, null,
|
|
81
75
|
react_1.default.createElement("div", { onClick: () => setExpanded((prev) => !prev), className: "flex items-center justify-between cursor-pointer", style: { marginBottom: expanded ? 14 : 0 } },
|
|
@@ -106,9 +100,12 @@ function ClientParametersSection({ userContext = "expert", }) {
|
|
|
106
100
|
errors.industry && react_1.default.createElement("div", { className: errCls }, errors.industry.message)),
|
|
107
101
|
react_1.default.createElement("div", null,
|
|
108
102
|
react_1.default.createElement("label", { className: labelCls }, "Analysis Period"),
|
|
109
|
-
react_1.default.createElement(
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
react_1.default.createElement(react_hook_form_1.Controller, { name: "period", control: control, render: ({ field }) => {
|
|
104
|
+
var _a;
|
|
105
|
+
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)))),
|
|
107
|
+
react_1.default.createElement(Chevron, null)));
|
|
108
|
+
} })),
|
|
112
109
|
react_1.default.createElement("div", null,
|
|
113
110
|
react_1.default.createElement("label", { className: labelCls }, "Annual Revenue"),
|
|
114
111
|
react_1.default.createElement(react_hook_form_1.Controller, { name: "revenue", control: control, render: ({ field }) => (react_1.default.createElement("div", { className: "relative" },
|
|
@@ -106,13 +106,34 @@ function StrategyRadar({ profile }) {
|
|
|
106
106
|
const eligibleStrategies = (0, compute_1.filterEligibleStrategies)(safeProfile);
|
|
107
107
|
const eligibleIds = new Set(eligibleStrategies.map(s => String(s.rank)));
|
|
108
108
|
const stratCount = eligibleStrategies.length;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
109
|
+
// Serialize to primitive — watch() may return a reference-stable proxy,
|
|
110
|
+
// so [profile] as a dep would never re-fire the memo on field changes
|
|
111
|
+
const profileKey = JSON.stringify(profile);
|
|
112
|
+
const computed = (0, react_1.useMemo)(() => (0, compute_1.computeAllStrategies)(safeProfile), [profileKey]);
|
|
113
|
+
const fedRate = parseFloat((profile.federalRate || "24").replace("%", "")) / 100 || 0.24;
|
|
114
|
+
const stateRate = parseFloat(profile.stateRate || "4.95") / 100;
|
|
115
|
+
const netIncome = parseInt((profile.netIncome || "0").replace(/,/g, "")) || Math.round(rev * 0.3);
|
|
116
|
+
let loSum = eligibleStrategies.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);
|
|
117
|
+
let hiSum = eligibleStrategies.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);
|
|
118
|
+
// Tax-capacity cap: savings cannot exceed approximate total tax liability
|
|
119
|
+
const approxTaxLiability = Math.round(netIncome * (fedRate + stateRate));
|
|
120
|
+
if (approxTaxLiability > 0) {
|
|
121
|
+
hiSum = Math.min(hiSum, approxTaxLiability);
|
|
122
|
+
loSum = Math.min(loSum, hiSum);
|
|
123
|
+
// If cap squashed lo close to hi, re-spread to maintain a meaningful range
|
|
124
|
+
if (hiSum > 0 && loSum >= hiSum * 0.9) {
|
|
125
|
+
loSum = Math.round(hiSum / 3);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Aggregate spread constraint: max 3× ratio between hi and lo
|
|
129
|
+
if (hiSum > 0 && loSum > 0 && hiSum / loSum > 3.0) {
|
|
130
|
+
loSum = Math.round(hiSum / 3.0);
|
|
131
|
+
}
|
|
112
132
|
const highImpact = eligibleStrategies.filter(s => s.priority === "HIGH").length;
|
|
113
133
|
const quickWins = eligibleStrategies.filter(s => s.priority === "QUICK WIN").length;
|
|
114
134
|
const obbbaNew = eligibleStrategies.filter(s => s.cat === "obbba_new").length;
|
|
115
|
-
|
|
135
|
+
// PTE elections are only available for pass-through entities; C-Corps are ineligible
|
|
136
|
+
const hasPTE = entity !== "C-Corporation" && states.some(s => states_1.PTE_STATES.has(s));
|
|
116
137
|
const hasNoTax = states.some(s => states_1.NO_INCOME_TAX.has(s));
|
|
117
138
|
const total = RADAR_NODES.length;
|
|
118
139
|
// G4 — Sizing: compact vs expanded
|
|
@@ -31,6 +31,7 @@ export declare const intakeSchema: Yup.ObjectSchema<import("yup/lib/object").Ass
|
|
|
31
31
|
hsaContributions: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
32
32
|
wotcHires: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
33
33
|
familyEmployed: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
34
|
+
period: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
34
35
|
}>, import("yup/lib/object").AnyObject, import("yup/lib/object").TypeOfShape<import("yup/lib/object").Assign<import("yup/lib/object").ObjectShape, {
|
|
35
36
|
bizName: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
36
37
|
cpaName: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
@@ -62,6 +63,7 @@ export declare const intakeSchema: Yup.ObjectSchema<import("yup/lib/object").Ass
|
|
|
62
63
|
hsaContributions: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
63
64
|
wotcHires: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
64
65
|
familyEmployed: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
66
|
+
period: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
65
67
|
}>>, import("yup/lib/object").AssertsShape<import("yup/lib/object").Assign<import("yup/lib/object").ObjectShape, {
|
|
66
68
|
bizName: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
67
69
|
cpaName: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
@@ -93,5 +95,6 @@ export declare const intakeSchema: Yup.ObjectSchema<import("yup/lib/object").Ass
|
|
|
93
95
|
hsaContributions: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
94
96
|
wotcHires: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
95
97
|
familyEmployed: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
98
|
+
period: Yup.StringSchema<string, import("yup/lib/types").AnyObject, string>;
|
|
96
99
|
}>>>;
|
|
97
100
|
export declare const intakeDefaultValues: Partial<ClientProfile>;
|
|
@@ -56,6 +56,7 @@ exports.intakeSchema = Yup.object().shape({
|
|
|
56
56
|
hsaContributions: Yup.string().ensure(),
|
|
57
57
|
wotcHires: Yup.string().ensure(),
|
|
58
58
|
familyEmployed: Yup.string().ensure(),
|
|
59
|
+
period: Yup.string().ensure(),
|
|
59
60
|
});
|
|
60
61
|
exports.intakeDefaultValues = {
|
|
61
62
|
bizName: "",
|
|
@@ -88,4 +89,5 @@ exports.intakeDefaultValues = {
|
|
|
88
89
|
hsaContributions: "0",
|
|
89
90
|
wotcHires: "No",
|
|
90
91
|
familyEmployed: "No",
|
|
92
|
+
period: "Full Year",
|
|
91
93
|
};
|
|
@@ -1,151 +1,65 @@
|
|
|
1
1
|
import type { Strategy, ComputedMap } from "../types";
|
|
2
|
-
export interface SourceDocumentRef {
|
|
3
|
-
field_label: string;
|
|
4
|
-
value_display: string;
|
|
5
|
-
source_hint: string;
|
|
6
|
-
}
|
|
7
|
-
export interface EngineCalculationTrace {
|
|
8
|
-
strategy_id: string;
|
|
9
|
-
branch_executed: string | null;
|
|
10
|
-
formula: string;
|
|
11
|
-
inputs: Record<string, unknown>;
|
|
12
|
-
result_gross: number | null;
|
|
13
|
-
confidence_interval: {
|
|
14
|
-
low: number;
|
|
15
|
-
high: number;
|
|
16
|
-
basis: string;
|
|
17
|
-
} | null;
|
|
18
|
-
position_strength: string;
|
|
19
|
-
irs_cite: string;
|
|
20
|
-
data_quality_flag: string | null;
|
|
21
|
-
forms_required: string[];
|
|
22
|
-
deadline_flag: string | null;
|
|
23
|
-
amt_flag: boolean;
|
|
24
|
-
source_documents?: SourceDocumentRef[];
|
|
25
|
-
specialist_note?: string | null;
|
|
26
|
-
}
|
|
27
2
|
export interface EngineStrategyAnalysis {
|
|
28
|
-
strategy_id
|
|
29
|
-
strategy_name
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
complexity: number;
|
|
36
|
-
risk: number;
|
|
37
|
-
cash_flow: number;
|
|
3
|
+
strategy_id?: string;
|
|
4
|
+
strategy_name?: string;
|
|
5
|
+
lo?: number;
|
|
6
|
+
hi?: number;
|
|
7
|
+
estimatedSavings?: {
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
38
10
|
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
11
|
+
priority?: string;
|
|
12
|
+
score?: number;
|
|
13
|
+
weighted_score?: number;
|
|
14
|
+
quick_win?: boolean;
|
|
15
|
+
timeline_bucket?: string;
|
|
16
|
+
calculation_trace?: {
|
|
17
|
+
irs_cite?: string;
|
|
18
|
+
forms_required?: string[];
|
|
19
|
+
source_documents?: Array<{
|
|
20
|
+
field_label: string;
|
|
21
|
+
value_display: string;
|
|
22
|
+
source_hint: string;
|
|
23
|
+
}>;
|
|
24
|
+
position_strength?: string;
|
|
25
|
+
specialist_note?: string;
|
|
26
|
+
};
|
|
27
|
+
why_it_applies?: string;
|
|
28
|
+
next_step?: string;
|
|
29
|
+
risk_disclosure?: string;
|
|
52
30
|
}
|
|
53
31
|
export interface EngineCpaWorkflow {
|
|
54
|
-
|
|
55
|
-
section_6694_notices: Array<{
|
|
56
|
-
strategy_id: string;
|
|
57
|
-
position_strength: string;
|
|
58
|
-
notice: string;
|
|
59
|
-
}>;
|
|
60
|
-
prior_year_flags: string[];
|
|
61
|
-
conflict_of_interest_checklist: Array<{
|
|
62
|
-
strategy_id: string;
|
|
63
|
-
conflict_prompt: string;
|
|
64
|
-
confirmed_no_conflict: boolean;
|
|
65
|
-
}>;
|
|
66
|
-
engagement_recommendations: string[];
|
|
32
|
+
engagement_recommendations?: string[];
|
|
67
33
|
}
|
|
68
34
|
export interface EngineOutput {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
data_quality_flags: string[];
|
|
81
|
-
};
|
|
82
|
-
eligibility_screening: {
|
|
83
|
-
excluded_strategies: Array<{
|
|
84
|
-
strategy_id: string;
|
|
85
|
-
status: "EXCLUDED";
|
|
86
|
-
reason: string;
|
|
87
|
-
irs_cite: string;
|
|
88
|
-
}>;
|
|
89
|
-
};
|
|
90
|
-
strategy_analysis: EngineStrategyAnalysis[];
|
|
91
|
-
strategy_interaction_warnings: Array<{
|
|
92
|
-
strategy_pair: [string, string];
|
|
93
|
-
warning_text: string;
|
|
94
|
-
}>;
|
|
95
|
-
implementation_roadmap: {
|
|
96
|
-
quick_wins: Array<{
|
|
97
|
-
strategy_id: string;
|
|
98
|
-
action: string;
|
|
99
|
-
deadline?: string | null;
|
|
100
|
-
}>;
|
|
101
|
-
immediate: Array<{
|
|
102
|
-
strategy_id: string;
|
|
103
|
-
action: string;
|
|
104
|
-
deadline?: string | null;
|
|
105
|
-
}>;
|
|
106
|
-
thirty_day: Array<{
|
|
107
|
-
strategy_id: string;
|
|
108
|
-
action: string;
|
|
109
|
-
deadline?: string | null;
|
|
110
|
-
}>;
|
|
111
|
-
ninety_day: Array<{
|
|
112
|
-
strategy_id: string;
|
|
113
|
-
action: string;
|
|
114
|
-
deadline?: string | null;
|
|
115
|
-
}>;
|
|
116
|
-
annual: Array<{
|
|
117
|
-
strategy_id: string;
|
|
118
|
-
action: string;
|
|
119
|
-
deadline?: string | null;
|
|
35
|
+
strategy_analysis?: EngineStrategyAnalysis[];
|
|
36
|
+
cpa_workflow?: EngineCpaWorkflow;
|
|
37
|
+
client_summary?: {
|
|
38
|
+
opening?: string;
|
|
39
|
+
closing?: string;
|
|
40
|
+
data_quality_note?: string;
|
|
41
|
+
interaction_warning?: string;
|
|
42
|
+
strategies?: Array<{
|
|
43
|
+
strategy_name?: string;
|
|
44
|
+
why_it_applies?: string;
|
|
45
|
+
next_step?: string;
|
|
120
46
|
}>;
|
|
121
47
|
};
|
|
122
|
-
|
|
123
|
-
client_summary: {
|
|
124
|
-
opening: string;
|
|
125
|
-
strategies: EngineClientSummaryStrategy[];
|
|
126
|
-
data_quality_note: string | null;
|
|
127
|
-
interaction_warning: string | null;
|
|
128
|
-
closing: string;
|
|
129
|
-
};
|
|
130
|
-
risk_disclosures: string[];
|
|
131
|
-
client_risk_disclosures: {
|
|
132
|
-
accuracy_penalty_warning: string | null;
|
|
133
|
-
applies_to_strategies: string[];
|
|
134
|
-
};
|
|
135
|
-
amt_flags: Array<{
|
|
136
|
-
trigger: string;
|
|
137
|
-
strategy_id: string;
|
|
138
|
-
flag_text: string;
|
|
139
|
-
}>;
|
|
140
|
-
nexus_flags: Array<{
|
|
48
|
+
nexus_flags?: Array<{
|
|
141
49
|
state: string;
|
|
142
50
|
nexus_type: string;
|
|
143
51
|
flag_text: string;
|
|
144
52
|
}>;
|
|
53
|
+
eligibility_screening?: {
|
|
54
|
+
excluded_strategies?: Array<{
|
|
55
|
+
strategy_id: string;
|
|
56
|
+
reason: string;
|
|
57
|
+
irs_cite: string;
|
|
58
|
+
}>;
|
|
59
|
+
};
|
|
145
60
|
}
|
|
146
61
|
export interface EngineOutputAdapterResult {
|
|
147
62
|
strategies: Strategy[];
|
|
148
63
|
computedMap: ComputedMap;
|
|
149
|
-
engineOutput: EngineOutput;
|
|
150
64
|
}
|
|
151
65
|
export declare function useEngineOutput(engineOutput: EngineOutput | null | undefined): EngineOutputAdapterResult | null;
|
|
@@ -1,161 +1,56 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
3
|
-
// useEngineOutput — adapts live EngineOutput from paro-graphql-api
|
|
4
|
-
// into the Strategy[] + ComputedMap shapes the FE components consume.
|
|
5
|
-
//
|
|
6
|
-
// Strategy: use the static STRATEGIES catalog as a structural template
|
|
7
|
-
// (rank, cat, entities, authority, etc.) and override savings/priority/score
|
|
8
|
-
// from the live engine data. Falls back to static data when a strategy_id
|
|
9
|
-
// has no match in the catalog (future-proofing for new strategies).
|
|
10
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.useEngineOutput = useEngineOutput;
|
|
13
4
|
const react_1 = require("react");
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const ENGINE_ID_TO_RANK = {
|
|
19
|
-
S1: 1, // Business Entity Structure (S-Corp election)
|
|
20
|
-
S2: 2, // Section 179
|
|
21
|
-
S3: 3, // Bonus Depreciation §168(k)
|
|
22
|
-
S4: 4, // QBI §199A
|
|
23
|
-
S5: 5, // R&D Credit §41
|
|
24
|
-
S6: 6, // WOTC §51
|
|
25
|
-
S7: 7, // Retirement Plan
|
|
26
|
-
S8: 8, // Cost Segregation
|
|
27
|
-
S9: 9, // HSA
|
|
28
|
-
S10: 10, // Business Meals
|
|
29
|
-
S11: 11, // Professional Services / Legal
|
|
30
|
-
S12: 12, // Home Office
|
|
31
|
-
S13: 13, // Health Care Credit §45R
|
|
32
|
-
S14: 14, // Charitable / DAF
|
|
33
|
-
S15: 15, // SALT PTE
|
|
34
|
-
S16: 16, // §163(j) Interest
|
|
35
|
-
S17: 17, // Income Deferral
|
|
36
|
-
S18: 18, // Opportunity Zone §1400Z
|
|
37
|
-
S19: 19, // §179D Energy
|
|
38
|
-
S20: 20, // Accounting Method
|
|
39
|
-
S21: 21, // OBBBA Overtime
|
|
40
|
-
S22: 22, // OBBBA Tips / Overtime
|
|
41
|
-
S23: 23, // §174A R&E catch-up
|
|
42
|
-
S24: 24, // Childcare §45F
|
|
43
|
-
S25: 25, // OBBBA Trump Accounts
|
|
44
|
-
};
|
|
45
|
-
const CATALOG_BY_RANK = new Map(data_1.STRATEGIES.map((s) => [s.rank, s]));
|
|
46
|
-
// ── Priority tier mapping ─────────────────────────────────────────────
|
|
47
|
-
function mapPriority(tier, quickWin) {
|
|
48
|
-
if (quickWin)
|
|
49
|
-
return "QUICK WIN";
|
|
50
|
-
if (tier === "HIGH")
|
|
51
|
-
return "HIGH";
|
|
52
|
-
if (tier === "MEDIUM")
|
|
53
|
-
return "MEDIUM";
|
|
54
|
-
if (tier === "LOW")
|
|
55
|
-
return "LOW";
|
|
56
|
-
return "NOT RECOMMENDED";
|
|
5
|
+
function humanize(id) {
|
|
6
|
+
return id
|
|
7
|
+
.replace(/[_-]/g, " ")
|
|
8
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
57
9
|
}
|
|
58
|
-
|
|
59
|
-
function resolveTimelineBucket(strategyId, roadmap) {
|
|
60
|
-
if (roadmap.quick_wins.some((r) => r.strategy_id === strategyId))
|
|
61
|
-
return "now";
|
|
62
|
-
if (roadmap.immediate.some((r) => r.strategy_id === strategyId))
|
|
63
|
-
return "now";
|
|
64
|
-
if (roadmap.thirty_day.some((r) => r.strategy_id === strategyId))
|
|
65
|
-
return "30d";
|
|
66
|
-
if (roadmap.ninety_day.some((r) => r.strategy_id === strategyId))
|
|
67
|
-
return "90d";
|
|
68
|
-
if (roadmap.annual.some((r) => r.strategy_id === strategyId))
|
|
69
|
-
return "filing";
|
|
70
|
-
return "filing";
|
|
71
|
-
}
|
|
72
|
-
// ── CalculationTrace → StrategyTraceStep[] ───────────────────────────
|
|
73
|
-
function traceFromEngine(trace) {
|
|
74
|
-
var _a, _b;
|
|
75
|
-
const steps = [];
|
|
76
|
-
// Emit formula as step 1
|
|
77
|
-
if (trace.formula) {
|
|
78
|
-
steps.push({
|
|
79
|
-
n: 1,
|
|
80
|
-
step: "Formula",
|
|
81
|
-
formula: trace.formula,
|
|
82
|
-
result: trace.result_gross != null ? `$${trace.result_gross.toLocaleString()}` : "See CI",
|
|
83
|
-
src: trace.irs_cite || "",
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
// Emit confidence interval as step 2 when present
|
|
87
|
-
if (((_a = trace.confidence_interval) === null || _a === void 0 ? void 0 : _a.low) != null && ((_b = trace.confidence_interval) === null || _b === void 0 ? void 0 : _b.high) != null) {
|
|
88
|
-
steps.push({
|
|
89
|
-
n: 2,
|
|
90
|
-
step: "Confidence range",
|
|
91
|
-
formula: `Low × ${trace.confidence_interval.basis}`,
|
|
92
|
-
result: `$${trace.confidence_interval.low.toLocaleString()} – $${trace.confidence_interval.high.toLocaleString()}`,
|
|
93
|
-
src: trace.irs_cite || "",
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
return steps;
|
|
97
|
-
}
|
|
98
|
-
function adaptEngineOutput(engineOutput) {
|
|
10
|
+
function mapSingleStrategy(sa, idx) {
|
|
99
11
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
onlyIndustry: template === null || template === void 0 ? void 0 : template.onlyIndustry,
|
|
132
|
-
excludeIndustry: template === null || template === void 0 ? void 0 : template.excludeIndustry,
|
|
133
|
-
preferIndustry: template === null || template === void 0 ? void 0 : template.preferIndustry,
|
|
134
|
-
minEmployees: template === null || template === void 0 ? void 0 : template.minEmployees,
|
|
135
|
-
needsOwnerComp: template === null || template === void 0 ? void 0 : template.needsOwnerComp,
|
|
136
|
-
clientBrief: (_p = clientSummaryEntry === null || clientSummaryEntry === void 0 ? void 0 : clientSummaryEntry.why_it_applies) !== null && _p !== void 0 ? _p : template === null || template === void 0 ? void 0 : template.clientBrief,
|
|
137
|
-
action: (_q = clientSummaryEntry === null || clientSummaryEntry === void 0 ? void 0 : clientSummaryEntry.next_step) !== null && _q !== void 0 ? _q : template === null || template === void 0 ? void 0 : template.action,
|
|
138
|
-
sourceDocuments: (_r = trace === null || trace === void 0 ? void 0 : trace.source_documents) !== null && _r !== void 0 ? _r : [],
|
|
139
|
-
quickWin: analysis.quick_win,
|
|
140
|
-
positionStrength: (_s = trace === null || trace === void 0 ? void 0 : trace.position_strength) !== null && _s !== void 0 ? _s : undefined,
|
|
141
|
-
specialistNote: (_t = trace === null || trace === void 0 ? void 0 : trace.specialist_note) !== null && _t !== void 0 ? _t : undefined,
|
|
142
|
-
};
|
|
143
|
-
strategies.push(strategy);
|
|
144
|
-
computedMap.set(strategy.rank, {
|
|
145
|
-
lo,
|
|
146
|
-
hi,
|
|
147
|
-
trace: strategy.trace,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
// Sort by weighted_score descending (same ordering the FE uses)
|
|
151
|
-
strategies.sort((a, b) => b.score - a.score);
|
|
152
|
-
return { strategies, computedMap, engineOutput };
|
|
12
|
+
const ct = (_a = sa.calculation_trace) !== null && _a !== void 0 ? _a : {};
|
|
13
|
+
const lo = (_d = (_b = sa.lo) !== null && _b !== void 0 ? _b : (_c = sa.estimatedSavings) === null || _c === void 0 ? void 0 : _c.min) !== null && _d !== void 0 ? _d : 0;
|
|
14
|
+
const hi = (_g = (_e = sa.hi) !== null && _e !== void 0 ? _e : (_f = sa.estimatedSavings) === null || _f === void 0 ? void 0 : _f.max) !== null && _g !== void 0 ? _g : 0;
|
|
15
|
+
const weightedScore = (_j = (_h = sa.weighted_score) !== null && _h !== void 0 ? _h : sa.score) !== null && _j !== void 0 ? _j : (sa.priority === "HIGH" ? 85 : sa.priority === "MEDIUM" ? 70 : 55);
|
|
16
|
+
const timelineBucket = (_k = sa.timeline_bucket) !== null && _k !== void 0 ? _k : (sa.priority === "HIGH" ? "now" : "90d");
|
|
17
|
+
return {
|
|
18
|
+
rank: idx + 1,
|
|
19
|
+
code: (_l = sa.strategy_id) !== null && _l !== void 0 ? _l : `S${idx + 1}`,
|
|
20
|
+
name: (_m = sa.strategy_name) !== null && _m !== void 0 ? _m : humanize((_o = sa.strategy_id) !== null && _o !== void 0 ? _o : `Strategy ${idx + 1}`),
|
|
21
|
+
cat: "income",
|
|
22
|
+
priority: (((_p = sa.priority) !== null && _p !== void 0 ? _p : "MEDIUM").toUpperCase()),
|
|
23
|
+
score: Math.round(weightedScore),
|
|
24
|
+
entities: [],
|
|
25
|
+
lo,
|
|
26
|
+
hi,
|
|
27
|
+
timeline: timelineBucket === "now" ? "Act now" : timelineBucket === "30d" ? "Within 30 days" : "Within 90 days",
|
|
28
|
+
timelineBucket,
|
|
29
|
+
clientBrief: (_q = sa.why_it_applies) !== null && _q !== void 0 ? _q : "Your preparer will review this strategy and include the analysis in your engagement report.",
|
|
30
|
+
action: (_r = sa.next_step) !== null && _r !== void 0 ? _r : "Work with your CPA to implement this strategy.",
|
|
31
|
+
forms: Array.isArray(ct.forms_required) ? ct.forms_required.join(", ") : undefined,
|
|
32
|
+
abstract: sa.why_it_applies,
|
|
33
|
+
warning: (_s = sa.risk_disclosure) !== null && _s !== void 0 ? _s : null,
|
|
34
|
+
sources: [],
|
|
35
|
+
trace: [],
|
|
36
|
+
cost: undefined,
|
|
37
|
+
sourceDocuments: Array.isArray(ct.source_documents) ? ct.source_documents : undefined,
|
|
38
|
+
positionStrength: ct.position_strength,
|
|
39
|
+
specialistNote: ct.specialist_note,
|
|
40
|
+
authority: ct.irs_cite,
|
|
41
|
+
quickWin: (_t = sa.quick_win) !== null && _t !== void 0 ? _t : false,
|
|
42
|
+
};
|
|
153
43
|
}
|
|
154
|
-
// ── React hook ────────────────────────────────────────────────────────
|
|
155
44
|
function useEngineOutput(engineOutput) {
|
|
156
45
|
return (0, react_1.useMemo)(() => {
|
|
157
|
-
|
|
46
|
+
const analysis = engineOutput === null || engineOutput === void 0 ? void 0 : engineOutput.strategy_analysis;
|
|
47
|
+
if (!Array.isArray(analysis) || analysis.length === 0)
|
|
158
48
|
return null;
|
|
159
|
-
|
|
49
|
+
const strategies = analysis.map((sa, idx) => mapSingleStrategy(sa, idx));
|
|
50
|
+
const computedMap = new Map();
|
|
51
|
+
for (const s of strategies) {
|
|
52
|
+
computedMap.set(s.rank, { lo: s.lo, hi: s.hi });
|
|
53
|
+
}
|
|
54
|
+
return { strategies, computedMap };
|
|
160
55
|
}, [engineOutput]);
|
|
161
56
|
}
|
|
@@ -159,5 +159,11 @@ function computeAllStrategies(profile) {
|
|
|
159
159
|
results.set(s.rank, { lo: Math.round(s.lo * scale), hi: Math.round(s.hi * scale), sources: s.sources, trace: s.trace });
|
|
160
160
|
}
|
|
161
161
|
});
|
|
162
|
+
// Enforce max 3× spread per strategy — tighten lo toward hi if ratio is too wide
|
|
163
|
+
for (const [rank, v] of results) {
|
|
164
|
+
if (v.hi > 0 && v.lo > 0 && v.hi / v.lo > 3.0) {
|
|
165
|
+
results.set(rank, Object.assign(Object.assign({}, v), { lo: Math.round(v.hi / 3.0) }));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
162
168
|
return results;
|
|
163
169
|
}
|