@paro.io/expert-shared-components 1.14.48 → 1.14.49

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.
Files changed (29) hide show
  1. package/lib/components/TaxAxis/TaxAxisApi.d.ts +38 -0
  2. package/lib/components/TaxAxis/TaxAxisApi.js +2 -0
  3. package/lib/components/TaxAxis/TaxAxisShell.d.ts +3 -0
  4. package/lib/components/TaxAxis/TaxAxisShell.js +268 -0
  5. package/lib/components/TaxAxis/index.d.ts +3 -0
  6. package/lib/components/TaxAxis/index.js +8 -0
  7. package/lib/components/TaxAxis/types.d.ts +16 -0
  8. package/lib/components/TaxAxis/types.js +2 -0
  9. package/lib/components/shared/UploadClient.d.ts +2 -1
  10. package/lib/components/shared/UploadClient.js +6 -2
  11. package/lib/index.d.ts +3 -1
  12. package/lib/index.js +3 -1
  13. package/lib/tax-axis/components/clientReport/TaxAxisClientReport.js +1 -1
  14. package/lib/tax-axis/components/dashboard/DashboardActions.js +3 -1
  15. package/lib/tax-axis/components/dashboard/TaxAxisDashboard.js +1 -2
  16. package/lib/tax-axis/components/documents/DocumentCard.d.ts +1 -1
  17. package/lib/tax-axis/components/documents/DocumentCard.js +14 -4
  18. package/lib/tax-axis/components/documents/DocumentTier.d.ts +1 -1
  19. package/lib/tax-axis/components/documents/DocumentTier.js +1 -1
  20. package/lib/tax-axis/components/documents/TaxAxisDocuments.d.ts +3 -1
  21. package/lib/tax-axis/components/documents/TaxAxisDocuments.js +28 -12
  22. package/lib/tax-axis/components/intake/IntakeCtaCards.js +1 -1
  23. package/lib/tax-axis/components/intake/TaxAxisIntake.js +1 -1
  24. package/lib/tax-axis/components/preparerWorkpaper/TaxAxisPreparerWorkpaper.js +1 -1
  25. package/lib/tax-axis/components/prospectReport/ProspectPrintView.js +0 -2
  26. package/lib/tax-axis/lib/data/strategies.js +9 -9
  27. package/package.json +1 -1
  28. package/lib/README.md +0 -2
  29. package/lib/package.json +0 -68
@@ -0,0 +1,38 @@
1
+ export type TaxAxisSessionInput = {
2
+ clientId?: number | null;
3
+ freelancerId: number;
4
+ taxYear: number;
5
+ businessName?: string;
6
+ entityType?: string;
7
+ industry?: string;
8
+ annualRevenue?: number;
9
+ w2Compensation?: number;
10
+ employeeCount?: number;
11
+ states?: string[];
12
+ };
13
+ export type TaxAxisUploadInput = {
14
+ sessionId: string;
15
+ clientId?: number | null;
16
+ freelancerId?: number;
17
+ fileName: string;
18
+ fileType: string;
19
+ data?: string;
20
+ fileSize?: number;
21
+ documentType?: string;
22
+ s3Key: string;
23
+ };
24
+ export type TaxAxisApi = {
25
+ createSession: (input: TaxAxisSessionInput) => Promise<any>;
26
+ getSession: (sessionId: string) => Promise<any>;
27
+ listSessions: (input?: Record<string, unknown>) => Promise<any[]>;
28
+ uploadFile: (input: TaxAxisUploadInput) => Promise<any>;
29
+ analyzeDocuments: (sessionId: string) => Promise<any>;
30
+ saveReviewedData: (documentId: string, reviewedData: Record<string, unknown>) => Promise<any>;
31
+ runLlm: (sessionId: string) => Promise<any>;
32
+ runEval: (llmRunId: string) => Promise<any>;
33
+ runReportGateways: (sessionId: string) => Promise<any>;
34
+ getReports: (sessionId: string) => Promise<any[]>;
35
+ updateReport: (reportId: string, reviewedReport: Record<string, unknown>) => Promise<any>;
36
+ generatePdf: (sessionId: string) => Promise<any>;
37
+ getArtifacts: (sessionId: string) => Promise<any[]>;
38
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ import type { TaxAxisShellProps } from './types';
2
+ export declare const TaxAxisShell: ({ taxAxisApi, userContext, initialSessionId, initialProfile, onSessionChange, documentUploadUrl, uploadBucketName, sessionDefaults, }: TaxAxisShellProps) => JSX.Element;
3
+ export default TaxAxisShell;
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.TaxAxisShell = void 0;
39
+ const react_1 = __importStar(require("react"));
40
+ const TaxAxisIntake_1 = require("../../tax-axis/components/intake/TaxAxisIntake");
41
+ const TaxAxisProspectReport_1 = require("../../tax-axis/components/prospectReport/TaxAxisProspectReport");
42
+ const TaxAxisDocuments_1 = require("../../tax-axis/components/documents/TaxAxisDocuments");
43
+ const TaxAxisProcessing_1 = require("../../tax-axis/components/processing/TaxAxisProcessing");
44
+ const TaxAxisDashboard_1 = require("../../tax-axis/components/dashboard/TaxAxisDashboard");
45
+ const TaxAxisExtractionReview_1 = require("../../tax-axis/components/extractionReview/TaxAxisExtractionReview");
46
+ const TaxAxisClientReport_1 = require("../../tax-axis/components/clientReport/TaxAxisClientReport");
47
+ const TaxAxisPreparerWorkpaper_1 = require("../../tax-axis/components/preparerWorkpaper/TaxAxisPreparerWorkpaper");
48
+ const TaxAxisPresentationMode_1 = require("../../tax-axis/components/presentationMode/TaxAxisPresentationMode");
49
+ const UploadClient_1 = __importDefault(require("../shared/UploadClient"));
50
+ function toInt(value) {
51
+ const parsed = Number(String(value || '0').replace(/[^\d.-]/g, ''));
52
+ return Number.isFinite(parsed) ? parsed : 0;
53
+ }
54
+ function buildSessionInput(profile) {
55
+ // Placeholder defaults are overridden by sessionDefaults from the host app.
56
+ const taxYear = Number(profile.year) || new Date().getFullYear();
57
+ return {
58
+ clientId: null,
59
+ freelancerId: 0,
60
+ taxYear,
61
+ businessName: profile.bizName,
62
+ entityType: profile.entity,
63
+ industry: profile.industry,
64
+ annualRevenue: toInt(profile.revenue),
65
+ w2Compensation: toInt(profile.ownerComp),
66
+ employeeCount: toInt(profile.employees),
67
+ states: profile.states || [],
68
+ };
69
+ }
70
+ function ShellContainer({ children, fullWidth = false, }) {
71
+ if (fullWidth) {
72
+ return react_1.default.createElement(react_1.default.Fragment, null, children);
73
+ }
74
+ return (react_1.default.createElement("div", { className: 'min-h-screen bg-tax-axis-navy text-white font-tax-axis-body' },
75
+ react_1.default.createElement("div", { className: 'max-w-[960px] mx-auto px-5 py-7' }, children)));
76
+ }
77
+ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, initialProfile, onSessionChange, documentUploadUrl, uploadBucketName, sessionDefaults, }) => {
78
+ const [step, setStep] = (0, react_1.useState)('SESSION_SETUP');
79
+ const [profile, setProfile] = (0, react_1.useState)(initialProfile ? Object.assign({}, initialProfile) : null);
80
+ const [sessionId, setSessionId] = (0, react_1.useState)(initialSessionId || null);
81
+ const [isProspectFlow, setIsProspectFlow] = (0, react_1.useState)(false);
82
+ const [isBusy, setIsBusy] = (0, react_1.useState)(false);
83
+ const [error, setError] = (0, react_1.useState)(null);
84
+ const updateSessionId = (0, react_1.useCallback)((nextSessionId) => {
85
+ setSessionId(nextSessionId);
86
+ if (onSessionChange) {
87
+ onSessionChange(nextSessionId);
88
+ }
89
+ }, [onSessionChange]);
90
+ const createSessionIfNeeded = (0, react_1.useCallback)((nextProfile) => __awaiter(void 0, void 0, void 0, function* () {
91
+ var _a;
92
+ if (sessionId)
93
+ return sessionId;
94
+ try {
95
+ const sessionInput = buildSessionInput(nextProfile);
96
+ const created = yield taxAxisApi.createSession(Object.assign(Object.assign({}, sessionInput), { clientId: (_a = sessionDefaults === null || sessionDefaults === void 0 ? void 0 : sessionDefaults.clientId) !== null && _a !== void 0 ? _a : null, freelancerId: (sessionDefaults === null || sessionDefaults === void 0 ? void 0 : sessionDefaults.freelancerId) || sessionInput.freelancerId }));
97
+ const maybeSessionId = (created === null || created === void 0 ? void 0 : created.sessionId) || (created === null || created === void 0 ? void 0 : created.id) || null;
98
+ if (maybeSessionId) {
99
+ updateSessionId(maybeSessionId);
100
+ return maybeSessionId;
101
+ }
102
+ }
103
+ catch (requestError) {
104
+ setError('Unable to create Tax Axis session right now.');
105
+ }
106
+ return null;
107
+ }), [sessionId, sessionDefaults === null || sessionDefaults === void 0 ? void 0 : sessionDefaults.clientId, sessionDefaults === null || sessionDefaults === void 0 ? void 0 : sessionDefaults.freelancerId, taxAxisApi, updateSessionId]);
108
+ const handleReset = (0, react_1.useCallback)(() => {
109
+ setStep('SESSION_SETUP');
110
+ setProfile(null);
111
+ setIsProspectFlow(false);
112
+ setError(null);
113
+ updateSessionId(null);
114
+ }, [updateSessionId]);
115
+ const handleProspect = (0, react_1.useCallback)((nextProfile) => {
116
+ setProfile(nextProfile);
117
+ setIsProspectFlow(true);
118
+ setStep('PROSPECT_REPORT');
119
+ }, []);
120
+ const handleFullAnalysis = (0, react_1.useCallback)((nextProfile) => __awaiter(void 0, void 0, void 0, function* () {
121
+ setProfile(nextProfile);
122
+ setIsProspectFlow(false);
123
+ setError(null);
124
+ setIsBusy(true);
125
+ const createdSessionId = yield createSessionIfNeeded(nextProfile);
126
+ setIsBusy(false);
127
+ if (!createdSessionId) {
128
+ return;
129
+ }
130
+ setStep('DOCUMENT_UPLOAD');
131
+ }), [createSessionIfNeeded]);
132
+ const handleUploadDocument = (0, react_1.useCallback)((doc, file) => __awaiter(void 0, void 0, void 0, function* () {
133
+ var _a;
134
+ const ensuredSessionId = sessionId || (profile ? yield createSessionIfNeeded(profile) : null);
135
+ if (!ensuredSessionId) {
136
+ throw new Error('Unable to upload without a session.');
137
+ }
138
+ if (!documentUploadUrl) {
139
+ throw new Error('Document upload service URL is missing.');
140
+ }
141
+ if (!uploadBucketName) {
142
+ throw new Error('Tax Axis document bucket is missing.');
143
+ }
144
+ const uploadClient = new UploadClient_1.default({
145
+ fileSelected: file,
146
+ fileName: file.name,
147
+ projectId: 0,
148
+ documentUploadUrl,
149
+ bucketName: uploadBucketName,
150
+ keyPrefix: `tax-axis/${ensuredSessionId}`,
151
+ });
152
+ const uploadResponse = yield uploadClient.triggerMultipartUpload();
153
+ if (!uploadResponse) {
154
+ throw new Error('File upload to storage failed.');
155
+ }
156
+ const uploadInput = {
157
+ sessionId: ensuredSessionId,
158
+ clientId: (_a = sessionDefaults === null || sessionDefaults === void 0 ? void 0 : sessionDefaults.clientId) !== null && _a !== void 0 ? _a : null,
159
+ freelancerId: sessionDefaults === null || sessionDefaults === void 0 ? void 0 : sessionDefaults.freelancerId,
160
+ fileName: file.name,
161
+ fileType: file.type || 'application/octet-stream',
162
+ fileSize: file.size,
163
+ documentType: doc.id.toUpperCase().replace(/[^A-Z0-9]+/g, '_'),
164
+ s3Key: uploadClient.state.fileName,
165
+ };
166
+ yield taxAxisApi.uploadFile(uploadInput);
167
+ }), [
168
+ createSessionIfNeeded,
169
+ documentUploadUrl,
170
+ profile,
171
+ sessionDefaults === null || sessionDefaults === void 0 ? void 0 : sessionDefaults.clientId,
172
+ sessionDefaults === null || sessionDefaults === void 0 ? void 0 : sessionDefaults.freelancerId,
173
+ sessionId,
174
+ taxAxisApi,
175
+ uploadBucketName,
176
+ ]);
177
+ const handleAnalyzeDocuments = (0, react_1.useCallback)(() => __awaiter(void 0, void 0, void 0, function* () {
178
+ if (!profile)
179
+ return;
180
+ setError(null);
181
+ setIsBusy(true);
182
+ const ensuredSessionId = yield createSessionIfNeeded(profile);
183
+ if (ensuredSessionId) {
184
+ try {
185
+ yield taxAxisApi.analyzeDocuments(ensuredSessionId);
186
+ }
187
+ catch (requestError) {
188
+ setError('Analysis trigger failed. Continuing with mock processing flow.');
189
+ }
190
+ }
191
+ setIsBusy(false);
192
+ setStep('PROCESSING');
193
+ }), [createSessionIfNeeded, profile, taxAxisApi]);
194
+ const handleSendReport = (0, react_1.useCallback)(() => __awaiter(void 0, void 0, void 0, function* () {
195
+ if (!sessionId) {
196
+ setError('No session id available for sending report.');
197
+ return;
198
+ }
199
+ setError(null);
200
+ setIsBusy(true);
201
+ try {
202
+ yield taxAxisApi.generatePdf(sessionId);
203
+ }
204
+ catch (requestError) {
205
+ setError('Failed to request report delivery. Please try again.');
206
+ }
207
+ finally {
208
+ setIsBusy(false);
209
+ }
210
+ }), [sessionId, taxAxisApi]);
211
+ const currentView = (0, react_1.useMemo)(() => {
212
+ if (step === 'SESSION_SETUP') {
213
+ return (react_1.default.createElement(ShellContainer, null,
214
+ react_1.default.createElement(TaxAxisIntake_1.TaxAxisIntake, { userContext: userContext, initialProfile: initialProfile, onProspect: handleProspect, onFullAnalysis: handleFullAnalysis })));
215
+ }
216
+ if (!profile) {
217
+ return (react_1.default.createElement(ShellContainer, null,
218
+ react_1.default.createElement("div", { className: 'text-sm text-tax-axis-text' }, "Session expired. Start a new analysis.")));
219
+ }
220
+ switch (step) {
221
+ case 'PROSPECT_REPORT':
222
+ return (react_1.default.createElement(ShellContainer, null,
223
+ react_1.default.createElement(TaxAxisProspectReport_1.TaxAxisProspectReport, { profile: profile, userContext: userContext, onUpgrade: () => setStep('DOCUMENT_UPLOAD'), onPresent: () => setStep('PRESENTATION'), onReset: handleReset })));
224
+ case 'DOCUMENT_UPLOAD':
225
+ return (react_1.default.createElement(ShellContainer, null,
226
+ react_1.default.createElement(TaxAxisDocuments_1.TaxAxisDocuments, { profile: profile, userContext: userContext, onUploadDocument: handleUploadDocument, onContinue: handleAnalyzeDocuments, onBack: () => isProspectFlow ? setStep('PROSPECT_REPORT') : setStep('SESSION_SETUP') })));
227
+ case 'PROCESSING':
228
+ return (react_1.default.createElement(ShellContainer, null,
229
+ react_1.default.createElement(TaxAxisProcessing_1.TaxAxisProcessing, { profile: profile, userContext: userContext, onComplete: () => setStep('DASHBOARD') })));
230
+ case 'PARSED_REVIEW':
231
+ return (react_1.default.createElement(ShellContainer, null,
232
+ react_1.default.createElement(TaxAxisExtractionReview_1.TaxAxisExtractionReview, { userContext: userContext, onBack: () => setStep('DASHBOARD'), onConfirmAndUnlock: () => setStep('DASHBOARD') })));
233
+ case 'DASHBOARD':
234
+ return (react_1.default.createElement(ShellContainer, null,
235
+ react_1.default.createElement(TaxAxisDashboard_1.TaxAxisDashboard, { profile: profile, userContext: userContext, onDownloadClient: () => setStep('CLIENT_REPORT'), onDownloadPreparer: () => setStep('PREPARER_WORKPAPER'), onPresent: () => setStep('PRESENTATION'), onSend: handleSendReport, onReviewData: () => setStep('PARSED_REVIEW'), onReset: handleReset })));
236
+ case 'CLIENT_REPORT':
237
+ return (react_1.default.createElement(ShellContainer, { fullWidth: true },
238
+ react_1.default.createElement(TaxAxisClientReport_1.TaxAxisClientReport, { profile: profile, userContext: userContext, onBack: () => setStep('DASHBOARD'), onNavigatePreparer: () => setStep('PREPARER_WORKPAPER') })));
239
+ case 'PREPARER_WORKPAPER':
240
+ return (react_1.default.createElement(ShellContainer, { fullWidth: true },
241
+ react_1.default.createElement(TaxAxisPreparerWorkpaper_1.TaxAxisPreparerWorkpaper, { profile: profile, userContext: userContext, onBack: () => setStep('DASHBOARD'), onToggleToClient: () => setStep('CLIENT_REPORT') })));
242
+ case 'PRESENTATION':
243
+ return (react_1.default.createElement(ShellContainer, { fullWidth: true },
244
+ react_1.default.createElement(TaxAxisPresentationMode_1.TaxAxisPresentationMode, { profile: profile, userContext: userContext, onBack: () => setStep(isProspectFlow ? 'PROSPECT_REPORT' : 'DASHBOARD') })));
245
+ default:
246
+ return null;
247
+ }
248
+ }, [
249
+ step,
250
+ profile,
251
+ userContext,
252
+ initialProfile,
253
+ handleProspect,
254
+ handleFullAnalysis,
255
+ isProspectFlow,
256
+ handleAnalyzeDocuments,
257
+ handleUploadDocument,
258
+ handleReset,
259
+ handleSendReport,
260
+ ]);
261
+ return (react_1.default.createElement(react_1.default.Fragment, null,
262
+ error && (react_1.default.createElement("div", { className: 'fixed right-4 top-4 z-[200] rounded-lg border border-tax-axis-border bg-tax-axis-surface px-3 py-2 text-xs text-tax-axis-text' }, error)),
263
+ isBusy && (react_1.default.createElement("div", { className: 'fixed inset-0 z-[180] bg-black/25' },
264
+ react_1.default.createElement("div", { className: 'absolute left-1/2 top-6 -translate-x-1/2 rounded-md bg-tax-axis-surface px-3 py-2 text-xs text-tax-axis-text' }, "Syncing Tax Axis session..."))),
265
+ currentView));
266
+ };
267
+ exports.TaxAxisShell = TaxAxisShell;
268
+ exports.default = exports.TaxAxisShell;
@@ -0,0 +1,3 @@
1
+ export { default as TaxAxisShell } from './TaxAxisShell';
2
+ export type { TaxAxisApi, TaxAxisSessionInput, TaxAxisUploadInput } from './TaxAxisApi';
3
+ export type { TaxAxisShellProps, TaxAxisStep } from './types';
@@ -0,0 +1,8 @@
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.TaxAxisShell = void 0;
7
+ var TaxAxisShell_1 = require("./TaxAxisShell");
8
+ Object.defineProperty(exports, "TaxAxisShell", { enumerable: true, get: function () { return __importDefault(TaxAxisShell_1).default; } });
@@ -0,0 +1,16 @@
1
+ import type { TaxAxisApi } from './TaxAxisApi';
2
+ import type { ClientProfile, UserContext } from '../../tax-axis/lib/types';
3
+ export type TaxAxisStep = 'SESSION_SETUP' | 'PROSPECT_REPORT' | 'DOCUMENT_UPLOAD' | 'PROCESSING' | 'PARSED_REVIEW' | 'DASHBOARD' | 'CLIENT_REPORT' | 'PREPARER_WORKPAPER' | 'PRESENTATION';
4
+ export type TaxAxisShellProps = {
5
+ taxAxisApi: TaxAxisApi;
6
+ userContext?: UserContext;
7
+ initialSessionId?: string;
8
+ initialProfile?: Partial<ClientProfile>;
9
+ onSessionChange?: (sessionId: string | null) => void;
10
+ documentUploadUrl?: string;
11
+ uploadBucketName?: string;
12
+ sessionDefaults?: {
13
+ freelancerId: number;
14
+ clientId?: number | null;
15
+ };
16
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -8,9 +8,10 @@ export default class UploadClient {
8
8
  bucketName: string;
9
9
  escalationId?: string;
10
10
  taskId?: string;
11
+ keyPrefix?: string;
11
12
  };
12
13
  constructor(props: any);
13
- generateS3Key(projectId: number, escalationId: string, fileName: string, taskId: string): string;
14
+ generateS3Key(projectId: number, escalationId: string, fileName: string, taskId: string, keyPrefix?: string): string;
14
15
  triggerMultipartUpload(): Promise<string | undefined>;
15
16
  uploadMultiPartFile(): Promise<string | undefined>;
16
17
  completeUpload(partsArray: any): Promise<string | undefined>;
@@ -20,10 +20,14 @@ class UploadClient {
20
20
  bucketName: props.bucketName,
21
21
  escalationId: props.escalationId,
22
22
  taskId: props.taskId,
23
+ keyPrefix: props.keyPrefix,
23
24
  };
24
25
  }
25
- generateS3Key(projectId, escalationId, fileName, taskId) {
26
+ generateS3Key(projectId, escalationId, fileName, taskId, keyPrefix) {
26
27
  const sanitizedFileName = fileName.replace(/[^a-zA-Z0-9.-]/g, '_');
28
+ if (!!keyPrefix) {
29
+ return `${keyPrefix.replace(/\/+$/, '')}/${sanitizedFileName}`;
30
+ }
27
31
  if (!!taskId) {
28
32
  return `project-task-${taskId}/${sanitizedFileName}`;
29
33
  }
@@ -37,7 +41,7 @@ class UploadClient {
37
41
  return __awaiter(this, void 0, void 0, function* () {
38
42
  var _a, _b;
39
43
  try {
40
- const fileName = this.generateS3Key(this.state.projectId, (_a = this.state.escalationId) !== null && _a !== void 0 ? _a : '', this.state.fileName, (_b = this.state.taskId) !== null && _b !== void 0 ? _b : '');
44
+ const fileName = this.generateS3Key(this.state.projectId, (_a = this.state.escalationId) !== null && _a !== void 0 ? _a : '', this.state.fileName, (_b = this.state.taskId) !== null && _b !== void 0 ? _b : '', this.state.keyPrefix);
41
45
  this.state = Object.assign(Object.assign({}, this.state), { fileName: fileName });
42
46
  const params = {
43
47
  fileName: this.state.fileName,
package/lib/index.d.ts CHANGED
@@ -15,7 +15,7 @@ export { fileUploader } from './components/FileUploader';
15
15
  export { DiscussionSection } from './components/Invoices/DiscussionSection';
16
16
  export { fileDownloader } from './components/FileDownloader';
17
17
  export { Escalations } from './components/Escalations';
18
- export { ProjectIntelligence } from "./components/ProjectIntelligence";
18
+ export { ProjectIntelligence } from './components/ProjectIntelligence';
19
19
  export { SectionHeader } from './tax-axis';
20
20
  export { TaxAxisBadge } from './tax-axis';
21
21
  export { TaxAxisButton } from './tax-axis';
@@ -29,3 +29,5 @@ export { TaxAxisPreparerWorkpaper } from './tax-axis';
29
29
  export { TaxAxisExtractionReview } from './tax-axis';
30
30
  export { TaxAxisProspectReport } from './tax-axis';
31
31
  export { TaxAxisPresentationMode } from './tax-axis';
32
+ export { TaxAxisShell } from './components/TaxAxis';
33
+ export type { TaxAxisApi, TaxAxisSessionInput, TaxAxisUploadInput } from './components/TaxAxis';
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TaxAxisPresentationMode = exports.TaxAxisProspectReport = exports.TaxAxisExtractionReview = exports.TaxAxisPreparerWorkpaper = exports.TaxAxisClientReport = exports.TaxAxisDashboard = exports.TaxAxisProcessing = exports.TaxAxisDocuments = exports.TaxAxisIntake = exports.TaxAxisCard = exports.TaxAxisButton = exports.TaxAxisBadge = exports.SectionHeader = exports.ProjectIntelligence = exports.Escalations = exports.fileDownloader = exports.DiscussionSection = exports.fileUploader = exports.InvoiceCard = exports.ActiveProjectCard = exports.sharedUtils = exports.ServiceLinesTemplate = exports.HeaderNavBar = exports.DocumentCenter = exports.ProfileCompletedPercentage = exports.ExpertProfileHeader = exports.OrganizationChart = exports.FirmEmployeeSection = exports.ClientReferenceSection = exports.Reviews = exports.ReviewsTab = void 0;
3
+ exports.TaxAxisShell = exports.TaxAxisPresentationMode = exports.TaxAxisProspectReport = exports.TaxAxisExtractionReview = exports.TaxAxisPreparerWorkpaper = exports.TaxAxisClientReport = exports.TaxAxisDashboard = exports.TaxAxisProcessing = exports.TaxAxisDocuments = exports.TaxAxisIntake = exports.TaxAxisCard = exports.TaxAxisButton = exports.TaxAxisBadge = exports.SectionHeader = exports.ProjectIntelligence = exports.Escalations = exports.fileDownloader = exports.DiscussionSection = exports.fileUploader = exports.InvoiceCard = exports.ActiveProjectCard = exports.sharedUtils = exports.ServiceLinesTemplate = exports.HeaderNavBar = exports.DocumentCenter = exports.ProfileCompletedPercentage = exports.ExpertProfileHeader = exports.OrganizationChart = exports.FirmEmployeeSection = exports.ClientReferenceSection = exports.Reviews = exports.ReviewsTab = void 0;
4
4
  var ReviewsTab_1 = require("./components/ReviewsTab");
5
5
  Object.defineProperty(exports, "ReviewsTab", { enumerable: true, get: function () { return ReviewsTab_1.ReviewsTab; } });
6
6
  var Reviews_1 = require("./components/Reviews");
@@ -63,3 +63,5 @@ var tax_axis_12 = require("./tax-axis");
63
63
  Object.defineProperty(exports, "TaxAxisProspectReport", { enumerable: true, get: function () { return tax_axis_12.TaxAxisProspectReport; } });
64
64
  var tax_axis_13 = require("./tax-axis");
65
65
  Object.defineProperty(exports, "TaxAxisPresentationMode", { enumerable: true, get: function () { return tax_axis_13.TaxAxisPresentationMode; } });
66
+ var TaxAxis_1 = require("./components/TaxAxis");
67
+ Object.defineProperty(exports, "TaxAxisShell", { enumerable: true, get: function () { return TaxAxis_1.TaxAxisShell; } });
@@ -100,7 +100,7 @@ function TaxAxisClientReport({ profile, onBack, onNavigatePreparer }) {
100
100
  .report-meta { break-inside: avoid; page-break-inside: avoid; }
101
101
  .toc-page { break-after: page; page-break-after: always; }
102
102
  * { color-adjust: exact; -webkit-print-color-adjust: exact; }
103
- body, p, span, div, td, th, li { color: #212529 !important; }
103
+ p, span, div, td, th, li { color: #212529 !important; }
104
104
  h1, h2, h3, h4, h5, h6, strong, b { color: #060821 !important; }
105
105
  }
106
106
  `),
@@ -72,7 +72,9 @@ function DashboardActions({ profile, dashEligible, computed, onDownloadPreparer,
72
72
  data_1.STRATEGIES.length,
73
73
  " strategies \u2014 use this to close the engagement or expand scope."),
74
74
  react_1.default.createElement("div", { className: "flex gap-2.5 flex-wrap" },
75
- react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onPresent }, "Present to Client")))),
75
+ react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "orange", onClick: onSend }, "Send Report to Client"),
76
+ react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onPresent }, "Present to Client"),
77
+ react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary" }, "Generate Engagement Letter")))),
76
78
  react_1.default.createElement("div", { className: "flex gap-4 mt-4 pt-3.5", style: { borderTop: "1px solid rgba(36,131,132,0.15)" } }, [
77
79
  { v: "3x", l: "Faster than Manual" },
78
80
  { v: "25", l: "Strategies Scanned" },
@@ -218,8 +218,7 @@ function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPre
218
218
  } },
219
219
  react_1.default.createElement("strong", null, "\u00A76694 Preparer Compliance \u2014 "),
220
220
  "All HIGH priority strategies hold Substantial Authority (established IRS guidance supporting the position). No elevated preparer penalty exposure for positions taken on these strategies. OBBBA strategies default to Reasonable Basis pending IRS guidance \u2014 CPA verification required before filing."),
221
- react_1.default.createElement("div", { style: { position: "sticky", bottom: 24, zIndex: 10, paddingTop: 12, background: "linear-gradient(to bottom, transparent 0%, #060821 24%)" } },
222
- react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onDownloadPreparer, className: "w-full" }, "Download Full Preparer Report")))) : (
221
+ react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onDownloadPreparer, className: "w-full" }, "Download Full Preparer Report"))) : (
223
222
  /* ═══ CLIENT SUMMARY — Timeline View ═══ */
224
223
  react_1.default.createElement("div", null,
225
224
  react_1.default.createElement("p", { className: "text-sm text-tax-axis-text leading-[1.7] mb-6 font-tax-axis-body" },
@@ -11,7 +11,7 @@ interface DocumentCardProps {
11
11
  tierBadgeColor: "red" | "orange" | "neutral";
12
12
  tierBadgeText: string;
13
13
  helpOverride?: string;
14
- onUpload: () => void;
14
+ onUpload: (file: File) => void;
15
15
  onClear: () => void;
16
16
  }
17
17
  export declare function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, helpOverride, onUpload, onClear, }: DocumentCardProps): React.JSX.Element;
@@ -29,6 +29,7 @@ function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, hel
29
29
  var _a;
30
30
  const ss = STATUS_STYLES[doc.status];
31
31
  const leftBorder = doc.status === "valid" ? "rgba(15,110,86,0.6)" : tierBorderColor;
32
+ const fileInputId = `tax-axis-upload-${doc.id}`;
32
33
  return (react_1.default.createElement("div", { className: "bg-tax-axis-surface overflow-hidden", style: {
33
34
  border: `1px solid ${ss.cardBorder}`,
34
35
  borderLeft: `3px solid ${leftBorder}`,
@@ -54,8 +55,17 @@ function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, hel
54
55
  doc.status === "valid" && (react_1.default.createElement(react_1.default.Fragment, null,
55
56
  react_1.default.createElement("span", { className: "text-[10px] font-semibold font-tax-axis-mono", style: { color: "#0F6E56" } }, "Done"),
56
57
  react_1.default.createElement("button", { onClick: onClear, className: "bg-transparent border-none p-1 text-tax-axis-text-4 text-sm cursor-pointer" }, "\u00D7"))),
57
- doc.status === "empty" && (react_1.default.createElement("button", { onClick: onUpload, className: "rounded-md px-3.5 py-1.5 text-[11px] font-semibold text-tax-axis-teal-light font-tax-axis-mono cursor-pointer", style: {
58
- background: "rgba(36,131,132,0.10)",
59
- border: "1px solid rgba(36,131,132,0.2)",
60
- } }, "Upload"))))));
58
+ doc.status === "empty" && (react_1.default.createElement(react_1.default.Fragment, null,
59
+ react_1.default.createElement("input", { id: fileInputId, type: "file", className: "hidden", onChange: (event) => {
60
+ var _a;
61
+ const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
62
+ if (file) {
63
+ onUpload(file);
64
+ }
65
+ event.currentTarget.value = "";
66
+ } }),
67
+ react_1.default.createElement("label", { htmlFor: fileInputId, className: "rounded-md px-3.5 py-1.5 text-[11px] font-semibold text-tax-axis-teal-light font-tax-axis-mono cursor-pointer", style: {
68
+ background: "rgba(36,131,132,0.10)",
69
+ border: "1px solid rgba(36,131,132,0.2)",
70
+ } }, "Upload")))))));
61
71
  }
@@ -14,7 +14,7 @@ interface DocumentTierProps {
14
14
  tier: TierDef;
15
15
  docs: DocState[];
16
16
  helpOverrides: Record<string, string>;
17
- onUpload: (idx: number) => void;
17
+ onUpload: (idx: number, file: File) => void;
18
18
  onClear: (idx: number) => void;
19
19
  }
20
20
  export declare function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, }: DocumentTierProps): React.JSX.Element | null;
@@ -19,5 +19,5 @@ function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, }) {
19
19
  react_1.default.createElement("div", { className: "flex items-center gap-2 mb-2" },
20
20
  react_1.default.createElement("span", { className: "text-[10px] font-bold uppercase tracking-widest font-tax-axis-mono", style: { color: tier.labelColor } }, tier.label),
21
21
  react_1.default.createElement("span", { className: "text-[10px] text-tax-axis-text-4 font-tax-axis-body" }, tier.sublabel)),
22
- react_1.default.createElement("div", { className: "grid gap-1.5" }, tierDocs.map(({ doc, idx }) => (react_1.default.createElement(DocumentCard_1.DocumentCard, { key: doc.id, doc: doc, tierBorderColor: tier.borderColor, tierBadgeColor: tier.badgeColor, tierBadgeText: tier.badgeText, helpOverride: helpOverrides[doc.id], onUpload: () => onUpload(idx), onClear: () => onClear(idx) }))))));
22
+ react_1.default.createElement("div", { className: "grid gap-1.5" }, tierDocs.map(({ doc, idx }) => (react_1.default.createElement(DocumentCard_1.DocumentCard, { key: doc.id, doc: doc, tierBorderColor: tier.borderColor, tierBadgeColor: tier.badgeColor, tierBadgeText: tier.badgeText, helpOverride: helpOverrides[doc.id], onUpload: (file) => onUpload(idx, file), onClear: () => onClear(idx) }))))));
23
23
  }
@@ -1,8 +1,10 @@
1
1
  import React from "react";
2
2
  import { ClientProfile, TaxAxisScreenProps } from "../../lib/types";
3
+ import { DocState } from "./DocumentCard";
3
4
  export interface TaxAxisDocumentsProps extends TaxAxisScreenProps {
4
5
  profile: ClientProfile;
5
6
  onContinue: () => void;
6
7
  onBack: () => void;
8
+ onUploadDocument?: (doc: DocState, file: File) => Promise<void>;
7
9
  }
8
- export declare function TaxAxisDocuments({ profile, onContinue, onBack, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
10
+ export declare function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
@@ -22,6 +22,15 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
25
34
  Object.defineProperty(exports, "__esModule", { value: true });
26
35
  exports.TaxAxisDocuments = TaxAxisDocuments;
27
36
  const react_1 = __importStar(require("react"));
@@ -82,23 +91,32 @@ const TIER_DEFS = [
82
91
  ids: ["state-return", "cashflow", "prior-returns"],
83
92
  },
84
93
  ];
85
- function TaxAxisDocuments({ profile, onContinue, onBack, userContext: _userContext = "expert", }) {
94
+ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, userContext: _userContext = "expert", }) {
86
95
  const docSpecs = (0, react_1.useMemo)(() => (0, documents_1.getDocSpecs)(profile), [profile]);
87
96
  const [docs, setDocs] = (0, react_1.useState)(() => docSpecs.map((s) => (Object.assign(Object.assign({}, s), { status: "empty", fileName: null }))));
88
- // Stub upload: empty -> validating -> valid after a short delay
89
- // (mock App.jsx:1238-1241)
90
- const handleUpload = (idx) => {
97
+ const handleUpload = (idx, file) => __awaiter(this, void 0, void 0, function* () {
98
+ const selectedDoc = docs[idx];
99
+ if (!selectedDoc)
100
+ return;
91
101
  setDocs((prev) => prev.map((d, i) => i === idx
92
- ? Object.assign(Object.assign({}, d), { status: "validating", fileName: STUB_FILENAMES[d.id] || "document.pdf" }) : d));
93
- setTimeout(() => setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: "valid" }) : d)), 500 + Math.random() * 400);
94
- };
102
+ ? Object.assign(Object.assign({}, d), { status: "validating", fileName: file.name || STUB_FILENAMES[d.id] || "document.pdf" }) : d));
103
+ try {
104
+ if (onUploadDocument) {
105
+ yield onUploadDocument(selectedDoc, file);
106
+ }
107
+ setDocs((prev) => prev.map((d, i) => i === idx
108
+ ? Object.assign(Object.assign({}, d), { status: "valid", fileName: file.name || STUB_FILENAMES[d.id] || "document.pdf" }) : d));
109
+ }
110
+ catch (uploadError) {
111
+ setDocs((prev) => prev.map((d, i) => i === idx
112
+ ? Object.assign(Object.assign({}, d), { status: "empty", fileName: null }) : d));
113
+ }
114
+ });
95
115
  const handleClear = (idx) => {
96
116
  setDocs((prev) => prev.map((d, i) => i === idx
97
117
  ? Object.assign(Object.assign({}, d), { status: "empty", fileName: null }) : d));
98
118
  };
99
119
  const validCount = docs.filter((d) => d.status === "valid").length;
100
- const requiredCount = docs.filter((d) => d.required === true).length;
101
- const requiredValid = docs.filter((d) => d.required === true && d.status === "valid").length;
102
120
  // Coverage heuristic from mock (App.jsx:1361)
103
121
  const coveragePct = validCount === 0
104
122
  ? 32
@@ -151,7 +169,5 @@ function TaxAxisDocuments({ profile, onContinue, onBack, userContext: _userConte
151
169
  TIER_DEFS.map((tier) => (react_1.default.createElement(DocumentTier_1.DocumentTier, { key: tier.key, tier: tier, docs: docs, helpOverrides: HELP_OVERRIDES, onUpload: handleUpload, onClear: handleClear }))),
152
170
  react_1.default.createElement("div", { className: "flex gap-3 mt-6" },
153
171
  react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary", onClick: onBack }, "Back"),
154
- react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onContinue, disabled: requiredCount > requiredValid, className: "flex-1" }, requiredCount > requiredValid
155
- ? `Upload ${requiredCount - requiredValid} more required doc${requiredCount - requiredValid > 1 ? "s" : ""}`
156
- : "Run Analysis"))));
172
+ react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onContinue, className: "flex-1" }, "Run Analysis"))));
157
173
  }
@@ -8,7 +8,7 @@ const react_1 = __importDefault(require("react"));
8
8
  const TaxAxisButton_1 = require("../shared/TaxAxisButton");
9
9
  const TaxAxisBadge_1 = require("../shared/TaxAxisBadge");
10
10
  function IntakeCtaCards({ onProspect, onFullAnalysis, userContext = "expert", }) {
11
- return (react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3", style: { position: "sticky", bottom: 0, zIndex: 10, paddingTop: 12, paddingBottom: 4, background: "linear-gradient(to bottom, transparent 0%, #060821 16%)" } },
11
+ return (react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3" },
12
12
  react_1.default.createElement("div", { className: "bg-tax-axis-surface border border-tax-axis-border rounded-xl p-4 min-h-[140px] flex flex-col transition-all" },
13
13
  react_1.default.createElement("div", { className: "text-[11px] font-bold text-tax-axis-orange uppercase tracking-widest mb-2.5 font-tax-axis-body" }, "Prospect Report"),
14
14
  react_1.default.createElement("div", { className: "text-xs text-tax-axis-text-3 font-tax-axis-body mb-3" }, "Top 3 strategies \u00B7 Savings ranges \u00B7 ~2 min \u00B7 No docs"),
@@ -33,6 +33,6 @@ function TaxAxisIntake({ userContext = "expert", onProspect, onFullAnalysis, ini
33
33
  react_1.default.createElement(RefineAnalysisSection_1.RefineAnalysisSection, { userContext: userContext }),
34
34
  react_1.default.createElement(CpaIntakeQuestionsSection_1.CpaIntakeQuestionsSection, { userContext: userContext }),
35
35
  react_1.default.createElement(IntakeCtaCards_1.IntakeCtaCards, { onProspect: handleProspect, onFullAnalysis: handleFull, userContext: userContext })),
36
- react_1.default.createElement("div", { className: "sticky top-5 self-start", style: { minWidth: 0, overflow: 'hidden' } },
36
+ react_1.default.createElement("div", { className: "sticky top-5 self-start" },
37
37
  react_1.default.createElement(StrategyRadar_1.StrategyRadar, { profile: profile })))));
38
38
  }
@@ -111,7 +111,7 @@ function TaxAxisPreparerWorkpaper({ profile, onBack, onToggleToClient }) {
111
111
  .flex.flex-col.w-full { margin-top: 0 !important; }
112
112
  main.w-full { width: 100% !important; margin-left: 0 !important; padding: 0 !important; }
113
113
  * { color-adjust: exact; -webkit-print-color-adjust: exact; }
114
- body, p, span, div, td, th, li { color: #212529 !important; }
114
+ p, span, div, td, th, li { color: #212529 !important; }
115
115
  h1, h2, h3, h4, h5, h6, strong, b { color: #060821 !important; }
116
116
  }
117
117
  `),
@@ -16,8 +16,6 @@ function ProspectPrintView({ profile, bizName, displayLo, displayHi, currentTax,
16
16
  body * { visibility: hidden !important; }
17
17
  .prospect-print-root, .prospect-print-root * { visibility: visible !important; }
18
18
  .prospect-print-root { position: absolute !important; left: 0 !important; top: 0 !important; width: 100% !important; }
19
- body, p, span, div, td, th, li { color: #212529 !important; }
20
- h1, h2, h3, h4, h5, h6, strong, b { color: #060821 !important; }
21
19
  .no-print { display: none !important; }
22
20
  }
23
21
  `),
@@ -7,31 +7,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.ADDITIONAL = exports.STRATEGIES = void 0;
8
8
  /* ═══ 25 FEDERAL STRATEGIES (L2-1, OBBBA-Updated) ═══ */
9
9
  exports.STRATEGIES = [
10
- { rank: 1, name: "Business Entity Structure", code: "§3121", cat: "entity", needsOwnerComp: true, lo: 6000, hi: 14000, score: 72, priority: "HIGH", timeline: "30 days", timelineBucket: "30d",
10
+ { rank: 1, name: "Business Entity Structure", code: "§3121", cat: "entity", needsOwnerComp: true, lo: 8400, hi: 12100, score: 72, priority: "HIGH", timeline: "30 days", timelineBucket: "30d",
11
11
  authority: "IRC §3121 · Rev. Rul. 74-44", forms: "BLS comp memo", warning: "Salary below 50%-of-profit threshold. BLS memo required.", entities: ["S-Corporation"],
12
12
  abstract: "S-Corporation distributions above a 'reasonable salary' bypass 15.3% self-employment tax. Salary benchmarked against BLS industry reference ranges — NOT a 40% heuristic.",
13
13
  sources: [{ doc: "Form W-3", line: "Box 1", value: "$65,000", field: "Officer W-2 Salary" }, { doc: "Form 1120S", line: "Line 1", value: "$142,000", field: "Net Profit" }, { doc: "BLS OES 2025", line: "NAICS 54", value: "$58.2K–$112.6K", field: "Industry Range" }],
14
14
  trace: [{ n: 1, step: "Current salary", formula: "Form W-3", result: "$65,000", src: "W-3" }, { n: 2, step: "BLS benchmark", formula: "25th pctl", result: "$58,200", src: "BLS" }, { n: 3, step: "50% threshold", formula: "50% × $142K", result: "$71,000", src: "Rev. Rul. 74-44" }, { n: 4, step: "SE tax avoided", formula: "$77K × 14.13%", result: "$10,881/yr", src: "IRC §3111" }],
15
15
  clientBrief: "Your S-Corp structure saves significant employment taxes. We document why your salary is reasonable.", action: "Prepare BLS documentation memo before next payroll cycle", cost: "$300–$500" },
16
- { rank: 2, name: "Section 179 Expensing", code: "§179", cat: "depreciation", lo: 8000, hi: 22000, score: 88, priority: "HIGH", timeline: "File now", timelineBucket: "now",
16
+ { rank: 2, name: "Section 179 Expensing", code: "§179", cat: "depreciation", lo: 4200, hi: 18500, score: 88, priority: "HIGH", timeline: "File now", timelineBucket: "now",
17
17
  authority: "IRC §179 · Rev. Proc. 2025-32", forms: "Form 4562", warning: null, entities: ["S-Corporation", "C-Corporation", "Partnership/LLC", "Sole Proprietorship"],
18
18
  abstract: "Immediately expense qualifying equipment and software up to $1,250,000 for 2026. Phase-out begins at $3,130,000.",
19
19
  sources: [{ doc: "Fixed Asset Schedule", line: "2026 additions", value: "$87,000", field: "Equipment Purchases" }, { doc: "Rev. Proc. 2025-32", line: "Table 4", value: "$1,250,000", field: "2026 §179 Limit" }],
20
20
  trace: [{ n: 1, step: "Qualifying purchases", formula: "Fixed asset schedule", result: "$87,000", src: "Asset Sched." }, { n: 2, step: "Phase-out check", formula: "$87K << $3.13M", result: "Full deduction", src: "Rev. Proc." }, { n: 3, step: "Immediate deduction", formula: "$87,000 × 100%", result: "$87,000", src: "IRC §179" }],
21
21
  clientBrief: "All qualifying equipment purchases can be fully expensed this year instead of depreciated over time.", action: "Claim on return — confirm asset list with preparer", cost: "$0" },
22
- { rank: 3, name: "Bonus Depreciation §168(k)", code: "§168(k)", cat: "depreciation", lo: 8000, hi: 22000, score: 90, priority: "HIGH", timeline: "File now", timelineBucket: "now",
22
+ { rank: 3, name: "Bonus Depreciation §168(k)", code: "§168(k)", cat: "depreciation", lo: 6800, hi: 32000, score: 90, priority: "HIGH", timeline: "File now", timelineBucket: "now",
23
23
  authority: "IRC §168(k) · OBBBA §70301", forms: "Form 4562", warning: null, entities: ["S-Corporation", "C-Corporation", "Partnership/LLC", "Sole Proprietorship"],
24
24
  abstract: "OBBBA made 100% bonus depreciation PERMANENT for property placed in service after Jan 19, 2025. No more phase-down.",
25
25
  sources: [{ doc: "Fixed Asset Schedule", line: "2026 additions", value: "$87,000", field: "Qualifying Property" }, { doc: "OBBBA §70301", line: "Sec. 1", value: "100%", field: "Permanent Rate" }],
26
26
  trace: [{ n: 1, step: "Qualifying property", formula: "Recovery period ≤ 20 yrs", result: "$87,000", src: "Asset Sched." }, { n: 2, step: "Bonus rate", formula: "OBBBA permanent 100%", result: "100%", src: "OBBBA §70301" }],
27
27
  clientBrief: "100% bonus depreciation is now permanent under OBBBA. All qualifying assets get full first-year write-off.", action: "Review fixed asset schedule for qualifying additions", cost: "$0" },
28
- { rank: 4, name: "QBI Deduction §199A", code: "§199A", cat: "income", lo: 8000, hi: 22000, score: 87, priority: "HIGH", timeline: "File now", timelineBucket: "now",
28
+ { rank: 4, name: "QBI Deduction §199A", code: "§199A", cat: "income", lo: 18200, hi: 24700, score: 87, priority: "HIGH", timeline: "File now", timelineBucket: "now",
29
29
  authority: "IRC §199A · OBBBA §70105 · Rev. Proc. 2025-32", forms: "Form 8995", warning: null, entities: ["S-Corporation", "Partnership/LLC", "Sole Proprietorship"],
30
30
  abstract: "OBBBA-updated §199A: 23% rate, three-branch formula based on entity type, SSTB status, and W-2 wages or property basis.",
31
31
  sources: [{ doc: "Form 1120S", line: "Line 1", value: "$142,000", field: "QBI Base" }, { doc: "Form W-3", line: "Box 1", value: "$48,000", field: "W-2 Wages" }, { doc: "Rev. Proc. 2025-32", line: "Table 3", value: "$191,950", field: "Phase-Out" }],
32
32
  trace: [{ n: 1, step: "QBI", formula: "Net income from 1120S", result: "$142,000", src: "1120S" }, { n: 2, step: "Branch selection", formula: "$184K < $191,950 → Branch 1", result: "Branch 1", src: "Rev. Proc." }, { n: 3, step: "Tentative deduction", formula: "$142,000 × 23%", result: "$32,660", src: "OBBBA §70105" }, { n: 4, step: "W-2 wage cap", formula: "50% × $48,000", result: "$24,000 ← binding", src: "W-3" }, { n: 5, step: "Net savings", formula: "$24,000 × rate", result: "$21,276", src: "Eff. rate" }],
33
33
  clientBrief: "The QBI deduction shelters up to 23% of your business income. Nothing changes about how you run your business.", action: "Claim on return — no client action required", cost: "$0" },
34
- { rank: 5, name: "R&D Credit §41", code: "§41", cat: "credits", excludeIndustry: ["Restaurant / Hospitality", "Education", "Agriculture", "Retail / E-Commerce"], lo: 4000, hi: 18000, score: 82, priority: "HIGH", timeline: "90 days", timelineBucket: "90d",
34
+ { rank: 5, name: "R&D Credit §41", code: "§41", cat: "credits", excludeIndustry: ["Restaurant / Hospitality", "Education", "Agriculture", "Retail / E-Commerce"], lo: 8500, hi: 45000, score: 82, priority: "HIGH", timeline: "90 days", timelineBucket: "90d",
35
35
  authority: "IRC §41 · OBBBA §174A", forms: "Form 6765", warning: "R&D documentation required.", entities: ["S-Corporation", "C-Corporation", "Partnership/LLC"],
36
36
  abstract: "OBBBA restored immediate expensing for domestic R&E. The R&D credit provides dollar-for-dollar reduction. Small businesses can offset payroll taxes.",
37
37
  sources: [{ doc: "Client intake", line: "R&D activities", value: "Yes", field: "R&D Eligible" }, { doc: "Expense Report", line: "R&D categories", value: "$120,000", field: "Qualified Research Expenses" }],
@@ -43,19 +43,19 @@ exports.STRATEGIES = [
43
43
  sources: [{ doc: "Payroll", line: "New hires", value: "3", field: "2026 Hires" }, { doc: "DOL", line: "Target groups", value: "2 qualifying", field: "Eligible" }],
44
44
  trace: [{ n: 1, step: "Qualifying hires", formula: "Screening", result: "2 employees", src: "Payroll" }, { n: 2, step: "Credit", formula: "40% × qualifying wages", result: "$4,800", src: "§51(a)" }],
45
45
  clientBrief: "Two recent hires may qualify for the Work Opportunity Tax Credit worth up to $9,600.", action: "Submit Form 8850 within 28 days of hire", cost: "$200–$500" },
46
- { rank: 7, name: "Retirement Plan Upgrade", code: "§401(k)", cat: "retirement", lo: 8000, hi: 28000, score: 91, priority: "HIGH", timeline: "90 days", timelineBucket: "90d",
46
+ { rank: 7, name: "Retirement Plan Upgrade", code: "§401(k)", cat: "retirement", lo: 12500, hi: 16800, score: 91, priority: "HIGH", timeline: "90 days", timelineBucket: "90d",
47
47
  authority: "IRC §401(k) · SECURE 2.0 · Rev. Proc. 2025-32", forms: "Solo 401(k) · Form 5500-EZ", warning: null, entities: ["S-Corporation", "Partnership/LLC", "Sole Proprietorship"],
48
48
  abstract: "Solo 401(k) unlocks $23,500 employee deferral + 25% employer match. SECURE 2.0 adds startup credit up to $5,000/yr for 3 years.",
49
49
  sources: [{ doc: "K-1", line: "Line 13", value: "$14,500", field: "Current SEP-IRA" }, { doc: "W-3", line: "Box 1", value: "$65,000", field: "W-2 Comp" }, { doc: "Rev. Proc.", line: "Table 1", value: "$23,500", field: "2026 Deferral" }],
50
50
  trace: [{ n: 1, step: "Current SEP", formula: "K-1 Line 13", result: "$14,500", src: "K-1" }, { n: 2, step: "Solo 401(k) EE", formula: "2026 deferral", result: "$23,500", src: "Rev. Proc." }, { n: 3, step: "Solo 401(k) ER", formula: "25% × $65K", result: "$16,250", src: "§415(c)" }, { n: 4, step: "Incremental", formula: "$39,750 − $14,500", result: "$25,250", src: "Calc." }],
51
51
  clientBrief: "Switching retirement account type shelters an extra $25,000.", action: "Open Solo 401(k) by Dec 31, 2026", cost: "$200–$400" },
52
- { rank: 8, name: "Cost Segregation", code: "§168", cat: "depreciation", preferIndustry: ["Real Estate", "Construction"], lo: 0, hi: 8000, score: 89, priority: "HIGH", timeline: "90 days", timelineBucket: "90d",
52
+ { rank: 8, name: "Cost Segregation", code: "§168", cat: "depreciation", preferIndustry: ["Real Estate", "Construction"], lo: 15000, hi: 85000, score: 89, priority: "HIGH", timeline: "90 days", timelineBucket: "90d",
53
53
  authority: "IRC §168 · OBBBA §70301", forms: "Form 4562 · Engineering Study", warning: "Property ≥ $500K. Combined with 100% bonus depreciation.", entities: ["S-Corporation", "C-Corporation", "Partnership/LLC"],
54
54
  abstract: "Reclassifies building components from 39-year to 5/7/15-year recovery. Massive first-year deductions with permanent 100% bonus depreciation.",
55
55
  sources: [{ doc: "Intake", line: "Property value", value: "$1,200,000", field: "Commercial RE" }, { doc: "Intake", line: "Year acquired", value: "2024", field: "Acquisition Year" }],
56
56
  trace: [{ n: 1, step: "Eligible property", formula: "Value ≥ $500K", result: "$1.2M", src: "Intake" }, { n: 2, step: "Reclassifiable", formula: "20–40% typical", result: "$300,000", src: "Industry avg" }, { n: 3, step: "Bonus depreciation", formula: "100% permanent", result: "$300K yr 1", src: "OBBBA" }],
57
57
  clientBrief: "A cost segregation study on your commercial property could accelerate $300K+ in deductions to this year.", action: "Engage cost segregation engineer. 4–8 weeks.", cost: "$5,000–$15,000" },
58
- { rank: 9, name: "HSA Maximization", code: "§223", cat: "benefits", lo: 2800, hi: 5250, score: 94, priority: "QUICK WIN", timeline: "This week", timelineBucket: "now",
58
+ { rank: 9, name: "HSA Maximization", code: "§223", cat: "benefits", lo: 1800, hi: 2400, score: 94, priority: "QUICK WIN", timeline: "This week", timelineBucket: "now",
59
59
  authority: "IRC §223 · Rev. Proc. 2025-32", forms: "Form 8889", warning: null, entities: ["S-Corporation", "C-Corporation", "Partnership/LLC", "Sole Proprietorship"],
60
60
  abstract: "HSA contributions are triple-tax-advantaged. $4,350 remaining below the 2026 family limit of $8,550.",
61
61
  sources: [{ doc: "Payroll", line: "HSA election", value: "$4,200", field: "Current YTD" }, { doc: "Benefits", line: "Plan type", value: "HDHP", field: "Family" }, { doc: "Rev. Proc.", line: "Table 2", value: "$8,550", field: "2026 Limit" }],
@@ -109,7 +109,7 @@ exports.STRATEGIES = [
109
109
  sources: [{ doc: "1120S", line: "Revenue", value: "$500,000", field: "Revenue" }, { doc: "QBO", line: "AR aging", value: "$42,000", field: "Receivables" }],
110
110
  trace: [{ n: 1, step: "Cash method", formula: "Revenue ≤ $27M", result: "Eligible", src: "§448" }, { n: 2, step: "Deferral", formula: "Q4 timing", result: "$20K–$40K", src: "QBO" }],
111
111
  clientBrief: "Strategic timing of invoicing can defer $20K–$40K of income to next year.", action: "Review Q4 billing schedule", cost: "$0" },
112
- { rank: 18, name: "Opportunity Zone §1400Z", code: "§1400Z", cat: "investment", lo: 0, hi: 0, score: 55, priority: "LOW", timeline: "Year-end", timelineBucket: "90d",
112
+ { rank: 18, name: "Opportunity Zone §1400Z", code: "§1400Z", cat: "investment", lo: 5000, hi: 25000, score: 55, priority: "LOW", timeline: "Year-end", timelineBucket: "90d",
113
113
  authority: "IRC §1400Z · OBBBA transition rules", forms: "Form 8996 · Form 8997", warning: "Confirm deferred gain recognition under OBBBA transition rules.", entities: ["S-Corporation", "C-Corporation", "Partnership/LLC", "Sole Proprietorship"],
114
114
  abstract: "Rural QOZ improvement threshold reduced to 50%. QOF investments defer and potentially eliminate capital gains.",
115
115
  sources: [{ doc: "Brokerage", line: "Gains", value: "$85,000", field: "Cap Gains" }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paro.io/expert-shared-components",
3
- "version": "1.14.48",
3
+ "version": "1.14.49",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
package/lib/README.md DELETED
@@ -1,2 +0,0 @@
1
- # expert-shared-components
2
- Repository to store Components shared between the Expert Apps and Internal Apps
package/lib/package.json DELETED
@@ -1,68 +0,0 @@
1
- {
2
- "name": "@paro.io/expert-shared-components",
3
- "version": "1.14.48",
4
- "description": "",
5
- "main": "lib/index.js",
6
- "scripts": {
7
- "build": "tsc",
8
- "build:watch": "tsc --watch",
9
- "prepare": "yarn build",
10
- "test": "yarn test:build",
11
- "test:build": "yarn build",
12
- "clean": "yarn -rf lib",
13
- "predeploy": "tsc && cp package.json README.md ./lib",
14
- "link-local": "yarn link && cd node_modules/react && yarn link && cd ../react-dom && yarn link",
15
- "unlink-local": "yarn unlink && cd node_modules/react && yarn unlink && cd ../react-dom && yarn unlink"
16
- },
17
- "repository": "https://github.com/paroadmin/expert-shared-components.git",
18
- "keywords": [
19
- "react",
20
- "components",
21
- "shared"
22
- ],
23
- "author": "apande@paro.io",
24
- "license": "MIT",
25
- "dependencies": {
26
- "@date-io/dayjs": "1.x",
27
- "@fortawesome/fontawesome-svg-core": "^6.6.0",
28
- "@fortawesome/free-solid-svg-icons": "^6.6.0",
29
- "@fortawesome/react-fontawesome": "^0.2.2",
30
- "@hookform/resolvers": "3.3.4",
31
- "@material-ui/core": "^4.11.0",
32
- "@material-ui/icons": "^4.11.3",
33
- "@material-ui/lab": "^4.0.0-alpha.61",
34
- "@material-ui/pickers": "^3.3.11",
35
- "@paro.io/base-icons": "^1.0.4",
36
- "@paro.io/base-ui": "^1.8.3",
37
- "@types/react-input-mask": "^3.0.5",
38
- "dayjs": "^1.10.7",
39
- "lodash": "^4.17.21",
40
- "react": "^18.2.0",
41
- "react-copy-to-clipboard": "^5.0.4",
42
- "react-datepicker": "^4.6.0",
43
- "react-dom": "^17.0.2",
44
- "react-hook-form": "7.51.1",
45
- "react-hot-toast": "^2.4.1",
46
- "react-input-mask": "^3.0.0-alpha.2",
47
- "styled-components": "^5.3.3",
48
- "uuid": "8.3.2",
49
- "yup": "^0.32.11"
50
- },
51
- "devDependencies": {
52
- "@types/lodash": "^4.14.170",
53
- "@types/react": "18.3.12",
54
- "@types/react-copy-to-clipboard": "^5.0.2",
55
- "@types/react-datepicker": "^4.19.6",
56
- "@types/react-dom": "^18.2.22",
57
- "@types/styled-components": "^5.1.22",
58
- "@types/uuid": "^10.0.0",
59
- "@types/yup": "^0.29.13",
60
- "typescript": "^5.3.3"
61
- },
62
- "files": [
63
- "lib/**/*"
64
- ],
65
- "directories": {
66
- "lib": "lib"
67
- }
68
- }