@paro.io/expert-shared-components 1.14.50 → 1.14.52

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.
@@ -56,6 +56,20 @@ function toInt(value) {
56
56
  const parsed = Number(String(value || '0').replace(/[^\d.-]/g, ''));
57
57
  return Number.isFinite(parsed) ? parsed : 0;
58
58
  }
59
+ const ENTITY_DISPLAY_TO_KEY = {
60
+ 'S-Corporation': 'S_CORP',
61
+ 'C-Corporation': 'C_CORP',
62
+ 'LLC': 'LLC',
63
+ 'Partnership': 'PARTNERSHIP',
64
+ 'Sole Proprietor': 'SOLE_PROP',
65
+ 'Sole Proprietorship': 'SOLE_PROP',
66
+ };
67
+ function entityTypeKey(entity) {
68
+ var _a;
69
+ if (!entity)
70
+ return undefined;
71
+ return (_a = ENTITY_DISPLAY_TO_KEY[entity]) !== null && _a !== void 0 ? _a : entity;
72
+ }
59
73
  function buildSessionInput(profile) {
60
74
  const taxYear = Number(profile.year) || new Date().getFullYear();
61
75
  return {
@@ -138,7 +152,7 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
138
152
  setStep('DOCUMENT_UPLOAD');
139
153
  }), [createSessionIfNeeded]);
140
154
  const handleUploadDocument = (0, react_1.useCallback)((doc, file) => __awaiter(void 0, void 0, void 0, function* () {
141
- var _a;
155
+ var _a, _b;
142
156
  const ensuredSessionId = sessionId || (profile ? yield createSessionIfNeeded(profile) : null);
143
157
  if (!ensuredSessionId) {
144
158
  throw new Error('Unable to upload without a session.');
@@ -168,7 +182,9 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
168
182
  fileName: file.name,
169
183
  fileType: file.type || 'application/octet-stream',
170
184
  fileSize: file.size,
171
- documentType: doc.id.toUpperCase().replace(/[^A-Z0-9]+/g, '_'),
185
+ // doc.documentType is the canonical lowercase snake-case key (e.g. "profit_loss").
186
+ // doc.id equals doc.documentType for the v2 catalog.
187
+ documentType: (_b = doc.documentType) !== null && _b !== void 0 ? _b : doc.id,
172
188
  s3Key: uploadClient.state.fileName,
173
189
  };
174
190
  return taxAxisApi.uploadFile(uploadInput);
@@ -252,7 +268,7 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
252
268
  react_1.default.createElement(TaxAxisProspectReport_1.TaxAxisProspectReport, { profile: profile, userContext: userContext, onUpgrade: () => setStep('DOCUMENT_UPLOAD'), onPresent: () => setStep('PRESENTATION'), onReset: handleReset })));
253
269
  case 'DOCUMENT_UPLOAD':
254
270
  return (react_1.default.createElement(ShellContainer, null,
255
- react_1.default.createElement(TaxAxisDocuments_1.TaxAxisDocuments, { profile: profile, userContext: userContext, onUploadDocument: handleUploadDocument, onDeleteDocument: handleDeleteDocument, fetchUploadedDocuments: () => __awaiter(void 0, void 0, void 0, function* () {
271
+ react_1.default.createElement(TaxAxisDocuments_1.TaxAxisDocuments, { profile: profile, entityType: entityTypeKey(profile.entity), userContext: userContext, onUploadDocument: handleUploadDocument, onDeleteDocument: handleDeleteDocument, fetchUploadedDocuments: () => __awaiter(void 0, void 0, void 0, function* () {
256
272
  const ensuredSessionId = sessionId || (profile ? yield createSessionIfNeeded(profile) : null);
257
273
  if (!ensuredSessionId) {
258
274
  return [];
@@ -11,6 +11,18 @@ export interface LlmStrategy {
11
11
  summary: string;
12
12
  implementationSteps: string[];
13
13
  requiredForms: string[];
14
+ strategyId?: string;
15
+ status?: "COMPLETE" | "FAILED";
16
+ error?: string;
17
+ weightedScore?: number;
18
+ quickWin?: boolean;
19
+ calculationTrace?: Record<string, unknown>;
20
+ }
21
+ export interface LlmExcludedStrategy {
22
+ strategy_id: string;
23
+ status?: "EXCLUDED";
24
+ reason: string;
25
+ irs_cite?: string;
14
26
  }
15
27
  export interface LlmResult {
16
28
  strategies: LlmStrategy[];
@@ -22,14 +34,25 @@ export interface LlmResult {
22
34
  notEligibleCount: number;
23
35
  estimatedSavingsMin: number;
24
36
  estimatedSavingsMax: number;
37
+ failedCount?: number;
38
+ top3?: string[];
25
39
  };
26
40
  meta: {
27
41
  provider: string;
28
- tokenCount: number;
42
+ tokenCount?: number;
43
+ totalTokens?: number;
44
+ inputTokens?: number;
45
+ outputTokens?: number;
46
+ latencyMs?: number;
29
47
  sourceDocumentCount: number;
30
48
  parsedDocumentCount: number;
31
49
  detectedDocumentTypes: string[];
50
+ modelId?: string;
51
+ promptVersion?: string;
32
52
  };
53
+ excludedStrategies?: LlmExcludedStrategy[];
54
+ engineOutput?: Record<string, unknown>;
55
+ rawOutput?: Record<string, unknown>;
33
56
  }
34
57
  export interface TaxAxisDashboardProps extends TaxAxisScreenProps {
35
58
  profile: ClientProfile;
@@ -156,7 +156,11 @@ function TaxAxisDashboard({ profile, llmResult, onDownloadClient, onDownloadPrep
156
156
  " · ",
157
157
  ((_b = llmResult.meta.detectedDocumentTypes) === null || _b === void 0 ? void 0 : _b.length) || 0,
158
158
  " document types detected"),
159
- react_1.default.createElement("span", { className: "text-tax-axis-text-4" }, llmResult.meta.tokenCount > 0 ? `${llmResult.meta.tokenCount} tokens` : ""))),
159
+ react_1.default.createElement("span", { className: "text-tax-axis-text-4" }, (() => {
160
+ var _a, _b;
161
+ const tokens = (_b = (_a = llmResult.meta.totalTokens) !== null && _a !== void 0 ? _a : llmResult.meta.tokenCount) !== null && _b !== void 0 ? _b : 0;
162
+ return tokens > 0 ? `${tokens} tokens` : "";
163
+ })()))),
160
164
  react_1.default.createElement(DashboardSummary_1.DashboardSummary, { profile: profile, dashEligible: dashEligible, computed: computed, dataConfirmed: dataConfirmed, reviewUnreviewed: reviewStatus.unreviewed }),
161
165
  react_1.default.createElement(DashboardTopBar_1.DashboardTopBar, { topTab: topTab, setTopTab: setTopTab, dataConfirmed: dataConfirmed, reviewUnreviewed: reviewStatus.unreviewed }),
162
166
  topTab === "extraction" && (react_1.default.createElement("div", null,
@@ -1,8 +1,10 @@
1
1
  import React from "react";
2
2
  import { ClientProfile, TaxAxisScreenProps } from "../../lib/types";
3
+ import { TaxAxisEntityTypeKey } from "../../lib/data/documents";
3
4
  import { DocState } from "./DocumentCard";
4
5
  export interface TaxAxisDocumentsProps extends TaxAxisScreenProps {
5
6
  profile: ClientProfile;
7
+ entityType?: TaxAxisEntityTypeKey | string | null;
6
8
  onContinue: () => void;
7
9
  onBack: () => void;
8
10
  onUploadDocument?: (doc: DocState, file: File) => Promise<void | {
@@ -21,4 +23,4 @@ export interface TaxAxisDocumentsProps extends TaxAxisScreenProps {
21
23
  updatedAt?: string | null;
22
24
  }>>;
23
25
  }
24
- export declare function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
26
+ export declare function TaxAxisDocuments({ profile, entityType, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
@@ -37,24 +37,6 @@ const react_1 = __importStar(require("react"));
37
37
  const documents_1 = require("../../lib/data/documents");
38
38
  const TaxAxisButton_1 = require("../shared/TaxAxisButton");
39
39
  const DocumentTier_1 = require("./DocumentTier");
40
- const STUB_FILENAMES = {
41
- "1120s": "2025_1120S.pdf",
42
- "state-return": "2025_State_Return.pdf",
43
- "payroll": "2025_Payroll.xlsx",
44
- "pnl": "QBO_PnL_2025.pdf",
45
- "balance": "QBO_BS_2025.pdf",
46
- "cashflow": "QBO_CashFlow_2025.pdf",
47
- "fixed-assets": "Fixed_Assets.xlsx",
48
- "prior-returns": "Prior_Returns.pdf",
49
- };
50
- const HELP_OVERRIDES = {
51
- "1120s": "Improves accuracy of every strategy",
52
- "payroll": "Critical for Entity Structure and QBI calculations",
53
- "fixed-assets": "Unlocks Cost Segregation (potential $15K\u201385K)",
54
- "state-return": "If client operates in income-tax states",
55
- "cashflow": "Helps with income deferral and timing strategies",
56
- "prior-returns": "Improves confidence intervals (more years = tighter estimates)",
57
- };
58
40
  const TIER_DEFS = [
59
41
  {
60
42
  key: "required",
@@ -64,7 +46,7 @@ const TIER_DEFS = [
64
46
  labelColor: "#C53030",
65
47
  badgeColor: "red",
66
48
  badgeText: "REQUIRED",
67
- ids: ["pnl", "balance"],
49
+ ids: [],
68
50
  },
69
51
  {
70
52
  key: "recommended",
@@ -74,7 +56,7 @@ const TIER_DEFS = [
74
56
  labelColor: "#FB9A1D",
75
57
  badgeColor: "orange",
76
58
  badgeText: "RECOMMENDED",
77
- ids: ["1120s", "payroll", "fixed-assets"],
59
+ ids: [],
78
60
  },
79
61
  {
80
62
  key: "conditional",
@@ -84,16 +66,21 @@ const TIER_DEFS = [
84
66
  labelColor: "#C8CCE5",
85
67
  badgeColor: "neutral",
86
68
  badgeText: "CONDITIONAL",
87
- ids: ["state-return", "cashflow", "prior-returns"],
69
+ ids: [],
88
70
  },
89
71
  ];
90
- function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext = "expert", }) {
91
- const normalizeDocumentType = (value) => String(value || "").toUpperCase().replace(/[^A-Z0-9]+/g, "_");
92
- const docSpecs = (0, react_1.useMemo)(() => (0, documents_1.getDocSpecs)(profile), [profile]);
72
+ function TaxAxisDocuments({ profile, entityType, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext = "expert", }) {
73
+ const docSpecs = (0, react_1.useMemo)(() => (0, documents_1.getDocSpecs)(entityType !== null && entityType !== void 0 ? entityType : undefined), [entityType]);
74
+ // Build tier defs dynamically from the doc spec list so they're always in sync.
75
+ const tierDefs = (0, react_1.useMemo)(() => {
76
+ return TIER_DEFS.map((def) => (Object.assign(Object.assign({}, def), { ids: docSpecs
77
+ .filter((s) => s.tier === def.key)
78
+ .map((s) => s.id) })));
79
+ }, [docSpecs]);
93
80
  const [docs, setDocs] = (0, react_1.useState)(() => docSpecs.map((s) => (Object.assign(Object.assign({}, s), { status: "empty", fileName: null, parseError: null }))));
94
81
  const [uploadedDocIds, setUploadedDocIds] = (0, react_1.useState)({});
95
82
  const [continueError, setContinueError] = (0, react_1.useState)(null);
96
- const pollForParseStatus = (idx, fileName, expectedDocumentType) => __awaiter(this, void 0, void 0, function* () {
83
+ const pollForParseStatus = (idx, fileName, documentType) => __awaiter(this, void 0, void 0, function* () {
97
84
  if (!fetchUploadedDocuments)
98
85
  return;
99
86
  const maxAttempts = 80;
@@ -101,11 +88,11 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
101
88
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
102
89
  try {
103
90
  const uploaded = yield fetchUploadedDocuments();
104
- const exactTypeMatch = uploaded.filter((doc) => doc.fileName === fileName &&
105
- normalizeDocumentType(String(doc.documentType || "")) ===
106
- normalizeDocumentType(expectedDocumentType));
107
- const filenameMatches = uploaded.filter((doc) => doc.fileName === fileName);
108
- const candidates = exactTypeMatch.length > 0 ? exactTypeMatch : filenameMatches;
91
+ // Match by documentType first (exact lowercase key), fall back to fileName.
92
+ const typeMatch = uploaded.filter((doc) => doc.fileName === fileName &&
93
+ String(doc.documentType || "").toLowerCase() === documentType.toLowerCase());
94
+ const nameMatch = uploaded.filter((doc) => doc.fileName === fileName);
95
+ const candidates = typeMatch.length > 0 ? typeMatch : nameMatch;
109
96
  const match = candidates
110
97
  .slice()
111
98
  .sort((a, b) => new Date(String(b.updatedAt || "")).getTime() -
@@ -128,7 +115,7 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
128
115
  }
129
116
  }
130
117
  }
131
- catch (pollError) {
118
+ catch (_pollError) {
132
119
  // keep polling
133
120
  }
134
121
  yield new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
@@ -141,13 +128,14 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
141
128
  return;
142
129
  setContinueError(null);
143
130
  setDocs((prev) => prev.map((d, i) => i === idx
144
- ? Object.assign(Object.assign({}, d), { status: "validating", fileName: file.name || STUB_FILENAMES[d.id] || "document.pdf", parseError: null }) : d));
131
+ ? Object.assign(Object.assign({}, d), { status: "validating", fileName: file.name, parseError: null }) : d));
145
132
  try {
146
133
  const uploadResult = onUploadDocument
147
134
  ? yield onUploadDocument(selectedDoc, file)
148
135
  : undefined;
149
- const nextFileName = file.name || STUB_FILENAMES[selectedDoc.id] || "document.pdf";
150
- const expectedDocumentType = normalizeDocumentType(selectedDoc.id);
136
+ const nextFileName = file.name;
137
+ // Use the canonical documentType from the catalog entry.
138
+ const documentType = selectedDoc.documentType;
151
139
  const immediateStatus = String((uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.status) || "").toUpperCase();
152
140
  if (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.documentId) {
153
141
  setUploadedDocIds((prev) => (Object.assign(Object.assign({}, prev), { [idx]: uploadResult.documentId })));
@@ -162,9 +150,8 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
162
150
  ? Object.assign(Object.assign({}, d), { status: "failed", fileName: (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.fileName) || nextFileName, parseError: (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.parseError) || "Parsing failed. Please upload a correct document." }) : d));
163
151
  return;
164
152
  }
165
- setDocs((prev) => prev.map((d, i) => i === idx
166
- ? Object.assign(Object.assign({}, d), { status: "parsing", fileName: nextFileName }) : d));
167
- yield pollForParseStatus(idx, nextFileName, expectedDocumentType);
153
+ setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: "parsing", fileName: nextFileName }) : d));
154
+ yield pollForParseStatus(idx, nextFileName, documentType);
168
155
  }
169
156
  catch (uploadError) {
170
157
  const errorMessage = uploadError instanceof Error
@@ -185,8 +172,8 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
185
172
  try {
186
173
  yield onDeleteDocument(docId);
187
174
  }
188
- catch (deleteError) {
189
- // still clear from UI even if backend delete fails
175
+ catch (_deleteError) {
176
+ // clear from UI even if backend delete fails
190
177
  }
191
178
  }
192
179
  setUploadedDocIds((prev) => {
@@ -209,16 +196,11 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
209
196
  const validCount = docs.filter((d) => d.status === "valid").length;
210
197
  const failedCount = docs.filter((d) => d.status === "failed").length;
211
198
  const busyCount = docs.filter((d) => d.status === "validating" || d.status === "parsing").length;
212
- const coveragePct = validCount === 0
213
- ? 32
214
- : validCount === 1
215
- ? 50
216
- : validCount === 2
217
- ? 60
218
- : validCount <= 4
219
- ? 75
220
- : validCount <= 6
221
- ? 90
199
+ const coveragePct = validCount === 0 ? 32
200
+ : validCount === 1 ? 50
201
+ : validCount === 2 ? 60
202
+ : validCount <= 4 ? 75
203
+ : validCount <= 6 ? 90
222
204
  : 100;
223
205
  const strategiesCovered = Math.round((25 * coveragePct) / 100);
224
206
  return (react_1.default.createElement("div", { className: "max-w-[580px] mx-auto" },
@@ -262,7 +244,7 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
262
244
  border: "1px solid rgba(197,48,48,0.3)",
263
245
  color: "#FEB2B2",
264
246
  } }, continueError)),
265
- 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, onRemove: handleRemove }))),
247
+ tierDefs.map((tier) => (react_1.default.createElement(DocumentTier_1.DocumentTier, { key: tier.key, tier: tier, docs: docs, helpOverrides: {}, onUpload: handleUpload, onClear: handleClear, onRemove: handleRemove }))),
266
248
  react_1.default.createElement("div", { className: "flex gap-3 mt-6" },
267
249
  react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary", onClick: onBack }, "Back"),
268
250
  react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: handleContinue, className: "flex-1", disabled: busyCount > 0 }, busyCount > 0
@@ -1,3 +1,3 @@
1
- import type { ClientProfile, DocSpec } from "../types";
2
- export declare const DOC_SPECS_BASE: DocSpec[];
3
- export declare function getDocSpecs(profile?: ClientProfile): DocSpec[];
1
+ import { DocSpec } from "../types";
2
+ export type TaxAxisEntityTypeKey = "S_CORP" | "C_CORP" | "PARTNERSHIP" | "LLC" | "SOLE_PROP";
3
+ export declare function getDocSpecs(entityType?: TaxAxisEntityTypeKey | string | null): DocSpec[];
@@ -1,33 +1,228 @@
1
1
  "use strict";
2
2
  // ═══════════════════════════════════════════════════════════════════
3
- // TaxAxis — Document checklist base + profile-aware resolver
4
- // Verbatim from the mock (App.jsx:575-603).
3
+ // TaxAxis — Document catalog (v2, aligned with parser v2 / LLM parser)
4
+ //
5
+ // documentType values are the canonical lowercase snake-case keys that the
6
+ // backend documentCatalog uses. These are sent verbatim on the
7
+ // uploadTaxAxisDocument mutation.
5
8
  // ═══════════════════════════════════════════════════════════════════
6
9
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.DOC_SPECS_BASE = void 0;
8
10
  exports.getDocSpecs = getDocSpecs;
9
- const states_1 = require("./states");
10
- exports.DOC_SPECS_BASE = [
11
- { id: "1120s", name: "Federal Tax Return (1120S/1065/1040)", accept: [".pdf", ".docx", ".doc"], strategies: ["QBI \u00A7199A", "S-Corp Structure", "\u00A7179 Expensing", "Bonus Depreciation", "Business Interest \u00A7163(j)"], required: "optional", help: "Optional \u2014 startups or new entities may not have prior returns. Improves accuracy if available." },
12
- { id: "state-return", name: "State Return(s)", accept: [".pdf", ".docx", ".doc"], strategies: ["SALT / PTE Optimization", "State Nexus Analysis"], required: "conditional", help: "Required if client operates in income-tax states. Not needed for WY, NV, TX, FL, etc." },
13
- { id: "payroll", name: "Payroll Records (W-3 / 941)", accept: [".pdf", ".xlsx", ".csv", ".docx", ".doc"], strategies: ["S-Corp Salary", "HSA Maximization", "WOTC", "Overtime Deduction"], required: "optional", help: "Optional for V1 \u2014 intake questions substitute for most payroll data." },
14
- { id: "pnl", name: "Profit & Loss Statement (Current Year)", accept: [".pdf", ".xlsx", ".csv", ".docx", ".doc"], strategies: ["QBI \u00A7199A", "R&D Credit", "Business Meals", "Professional Fees", "Home Office"], required: true, help: "Required \u2014 current year P&L is the minimum input for savings calculations." },
15
- { id: "balance", name: "Balance Sheet (Current Year)", accept: [".pdf", ".xlsx", ".docx", ".doc"], strategies: ["Business Interest \u00A7163(j)", "\u00A7179 Expensing", "Opportunity Zone"], required: true, help: "Required \u2014 needed for asset-based strategies and financial health assessment." },
16
- { id: "cashflow", name: "Cash Flow Statement", accept: [".pdf", ".xlsx", ".docx", ".doc"], strategies: ["Income Deferral", "Tax Method Elections", "Estimated Tax Timing"], required: "optional", help: "Recommended \u2014 helps with income deferral and timing strategies." },
17
- { id: "fixed-assets", name: "Fixed Asset Schedule", accept: [".pdf", ".xlsx", ".docx", ".doc"], strategies: ["\u00A7179", "Bonus Depreciation \u00A7168(k)", "Cost Segregation"], required: "conditional", help: "Required for real estate clients (cost segregation). Optional otherwise." },
18
- { id: "prior-returns", name: "Prior Year Returns (2022\u20132024)", accept: [".pdf", ".docx", ".doc"], strategies: ["R&E Catch-Up \u00A7174A", "Charitable Bunching", "Confidence Intervals"], required: false, help: "More years = tighter estimates. 1 year: \u00B130% range. 2\u20133 years: \u00B115%. 4\u20135 years: \u00B15%." },
11
+ const DOC_META = {
12
+ federal_tax_return: {
13
+ name: "Federal Tax Return (1120S / 1065 / 1040 / 1120)",
14
+ strategies: ["QBI §199A", "S-Corp Structure", "§179 Expensing", "Bonus Depreciation", "Business Interest §163(j)"],
15
+ tier: "recommended",
16
+ help: "Improves accuracy of every strategy. Startups may not have prior returns.",
17
+ },
18
+ profit_loss: {
19
+ name: "Profit & Loss Statement (Current Year)",
20
+ strategies: ["QBI §199A", "R&D Credit", "Business Meals", "Professional Fees", "Home Office"],
21
+ tier: "required",
22
+ help: "Required — current year P&L is the minimum input for savings calculations.",
23
+ },
24
+ balance_sheet: {
25
+ name: "Balance Sheet (Current Year)",
26
+ strategies: ["Business Interest §163(j)", "§179 Expensing", "Opportunity Zone"],
27
+ tier: "required",
28
+ help: "Required — needed for asset-based strategies and financial health assessment.",
29
+ },
30
+ payroll_records: {
31
+ name: "Payroll Records (W-3 / 941)",
32
+ strategies: ["S-Corp Salary", "HSA Maximization", "WOTC", "Overtime Deduction"],
33
+ tier: "recommended",
34
+ help: "Critical for Entity Structure and QBI calculations.",
35
+ },
36
+ fixed_asset_schedule: {
37
+ name: "Fixed Asset Schedule",
38
+ strategies: ["§179", "Bonus Depreciation §168(k)", "Cost Segregation"],
39
+ tier: "conditional",
40
+ help: "Required for real estate clients (cost segregation). Optional otherwise.",
41
+ },
42
+ state_tax_return: {
43
+ name: "State Return(s)",
44
+ strategies: ["SALT / PTE Optimization", "State Nexus Analysis"],
45
+ tier: "conditional",
46
+ help: "Required if client operates in income-tax states.",
47
+ },
48
+ schedule_k1_s_corp: {
49
+ name: "Schedule K-1 (S-Corp, 1120S)",
50
+ strategies: ["QBI §199A", "S-Corp Basis", "Distributions"],
51
+ tier: "recommended",
52
+ help: "One per shareholder — needed for QBI and basis calculations.",
53
+ },
54
+ schedule_k1_partnership: {
55
+ name: "Schedule K-1 (Partnership, 1065)",
56
+ strategies: ["QBI §199A", "SE Tax", "At-Risk Basis"],
57
+ tier: "recommended",
58
+ help: "One per partner — needed for SE tax and QBI calculations.",
59
+ },
60
+ schedule_se: {
61
+ name: "Schedule SE (Self-Employment Tax)",
62
+ strategies: ["SE Tax Deduction", "S-Corp Conversion"],
63
+ tier: "conditional",
64
+ help: "Required for sole props and GP partners with SE income.",
65
+ },
66
+ form_8829: {
67
+ name: "Form 8829 (Home Office)",
68
+ strategies: ["Home Office Deduction"],
69
+ tier: "conditional",
70
+ help: "Only for sole proprietors who use part of their home for business.",
71
+ },
72
+ form_2553: {
73
+ name: "Form 2553 (S-Corp Election)",
74
+ strategies: ["S-Corp Structure", "Entity Restructuring"],
75
+ tier: "conditional",
76
+ help: "Confirms S-Corp election date and effective year — needed for entity analysis.",
77
+ },
78
+ w3: {
79
+ name: "W-3 (Transmittal of W-2s)",
80
+ strategies: ["S-Corp Salary", "HSA Maximization", "WOTC"],
81
+ tier: "recommended",
82
+ help: "Total wages across all employees — cross-checks payroll records.",
83
+ },
84
+ form_1099_nec_summary: {
85
+ name: "1099-NEC Summary",
86
+ strategies: ["Contractor vs Employee", "R&D Credit"],
87
+ tier: "conditional",
88
+ help: "Required if client paid $600+ to contractors.",
89
+ },
90
+ form_4562: {
91
+ name: "Form 4562 (Depreciation & Amortization)",
92
+ strategies: ["§179", "Bonus Depreciation", "Cost Segregation"],
93
+ tier: "conditional",
94
+ help: "Detailed depreciation schedule — unlocks Section 179 and bonus dep strategies.",
95
+ },
96
+ form_8889: {
97
+ name: "Form 8889 (HSA)",
98
+ strategies: ["HSA Maximization"],
99
+ tier: "conditional",
100
+ help: "Only if client has or is considering an HSA-eligible HDHP.",
101
+ },
102
+ rent_roll: {
103
+ name: "Rent Roll",
104
+ strategies: ["Real Estate — Cost Segregation", "QBI §199A (Rental)"],
105
+ tier: "conditional",
106
+ help: "Required for real estate clients to assess rental income and vacancy.",
107
+ },
108
+ mortgage_loan_documents: {
109
+ name: "Mortgage / Loan Documents",
110
+ strategies: ["Business Interest §163(j)", "Real Estate Strategies"],
111
+ tier: "conditional",
112
+ help: "1098s and loan statements — needed for interest limitation analysis.",
113
+ },
114
+ operating_agreement: {
115
+ name: "Operating Agreement / Partnership Agreement",
116
+ strategies: ["Entity Restructuring", "Special Allocations §704(b)"],
117
+ tier: "conditional",
118
+ help: "LLCs and partnerships — confirms ownership %, distributions, elections.",
119
+ },
120
+ rd_activity_documentation: {
121
+ name: "R&D Activity Documentation",
122
+ strategies: ["R&D Credit §41"],
123
+ tier: "conditional",
124
+ help: "Only if client conducts qualifying research activities.",
125
+ },
126
+ vehicle_mileage_log: {
127
+ name: "Vehicle Mileage Log",
128
+ strategies: ["Vehicle Deduction", "Standard vs Actual Method"],
129
+ tier: "conditional",
130
+ help: "Required for sole props claiming vehicle deductions.",
131
+ },
132
+ };
133
+ // ---------------------------------------------------------------------------
134
+ // Accepted file types — aligned with BE ALLOWED_EXTENSIONS (no .doc)
135
+ // ---------------------------------------------------------------------------
136
+ const ACCEPT_PDF_DOCX = [".pdf", ".docx"];
137
+ const ACCEPT_PDF_DOCX_XLSX = [".pdf", ".docx", ".xlsx", ".xls", ".csv"];
138
+ const ACCEPT_PDF_DOCX_XLSX_IMG = [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".png", ".jpg", ".jpeg"];
139
+ const ACCEPT_BY_TYPE = {
140
+ federal_tax_return: ACCEPT_PDF_DOCX,
141
+ profit_loss: ACCEPT_PDF_DOCX_XLSX,
142
+ balance_sheet: ACCEPT_PDF_DOCX_XLSX,
143
+ payroll_records: ACCEPT_PDF_DOCX_XLSX,
144
+ fixed_asset_schedule: ACCEPT_PDF_DOCX_XLSX,
145
+ state_tax_return: ACCEPT_PDF_DOCX,
146
+ schedule_k1_s_corp: ACCEPT_PDF_DOCX,
147
+ schedule_k1_partnership: ACCEPT_PDF_DOCX,
148
+ schedule_se: ACCEPT_PDF_DOCX,
149
+ form_8829: ACCEPT_PDF_DOCX,
150
+ form_2553: ACCEPT_PDF_DOCX,
151
+ w3: ACCEPT_PDF_DOCX_XLSX_IMG,
152
+ form_1099_nec_summary: ACCEPT_PDF_DOCX_XLSX,
153
+ form_4562: ACCEPT_PDF_DOCX,
154
+ form_8889: ACCEPT_PDF_DOCX,
155
+ rent_roll: ACCEPT_PDF_DOCX_XLSX,
156
+ mortgage_loan_documents: ACCEPT_PDF_DOCX,
157
+ operating_agreement: ACCEPT_PDF_DOCX,
158
+ rd_activity_documentation: ACCEPT_PDF_DOCX_XLSX,
159
+ vehicle_mileage_log: ACCEPT_PDF_DOCX_XLSX,
160
+ };
161
+ // ---------------------------------------------------------------------------
162
+ // Entity → document types map (mirrors BE ENTITY_DOCUMENT_MAP exactly)
163
+ // ---------------------------------------------------------------------------
164
+ const S_CORP_TYPES = [
165
+ "profit_loss", "balance_sheet",
166
+ "federal_tax_return", "payroll_records", "w3", "schedule_k1_s_corp",
167
+ "form_2553", "form_1099_nec_summary", "state_tax_return", "form_4562",
168
+ "form_8889", "fixed_asset_schedule", "rent_roll", "mortgage_loan_documents",
169
+ "operating_agreement", "rd_activity_documentation",
19
170
  ];
20
- function getDocSpecs(profile) {
21
- const states = (profile === null || profile === void 0 ? void 0 : profile.states) || [];
22
- const industry = (profile === null || profile === void 0 ? void 0 : profile.industry) || "";
23
- const allNoTax = states.length > 0 && states.every(s => states_1.NO_INCOME_TAX.has(s));
24
- const isRealEstate = industry === "Real Estate" || industry === "Construction";
25
- return exports.DOC_SPECS_BASE.map(d => {
26
- let req = d.required;
27
- if (d.id === "state-return")
28
- req = allNoTax ? false : "optional";
29
- if (d.id === "fixed-assets")
30
- req = isRealEstate ? true : false;
31
- return Object.assign(Object.assign({}, d), { required: req });
32
- });
171
+ const C_CORP_TYPES = [
172
+ "profit_loss", "balance_sheet",
173
+ "federal_tax_return", "payroll_records", "w3",
174
+ "form_1099_nec_summary", "state_tax_return", "form_4562", "form_8889",
175
+ "fixed_asset_schedule", "rent_roll", "mortgage_loan_documents",
176
+ "operating_agreement", "rd_activity_documentation",
177
+ ];
178
+ const PARTNERSHIP_TYPES = [
179
+ "profit_loss", "balance_sheet",
180
+ "federal_tax_return", "payroll_records", "w3", "schedule_k1_partnership",
181
+ "schedule_se", "form_1099_nec_summary", "state_tax_return", "form_4562",
182
+ "form_8889", "fixed_asset_schedule", "rent_roll", "mortgage_loan_documents",
183
+ "operating_agreement", "rd_activity_documentation",
184
+ ];
185
+ const SOLE_PROP_TYPES = [
186
+ "profit_loss", "balance_sheet",
187
+ "federal_tax_return", "schedule_se", "form_8829", "form_1099_nec_summary",
188
+ "state_tax_return", "form_4562", "form_8889", "fixed_asset_schedule",
189
+ "vehicle_mileage_log", "rent_roll", "mortgage_loan_documents",
190
+ "rd_activity_documentation",
191
+ ];
192
+ const LLC_TYPES = Array.from(new Set([
193
+ ...S_CORP_TYPES, ...C_CORP_TYPES, ...PARTNERSHIP_TYPES, ...SOLE_PROP_TYPES,
194
+ ]));
195
+ const ENTITY_DOCUMENT_TYPES = {
196
+ S_CORP: S_CORP_TYPES,
197
+ C_CORP: C_CORP_TYPES,
198
+ PARTNERSHIP: PARTNERSHIP_TYPES,
199
+ SOLE_PROP: SOLE_PROP_TYPES,
200
+ LLC: LLC_TYPES,
201
+ };
202
+ // ---------------------------------------------------------------------------
203
+ // Build DocSpec list for a given entity type key
204
+ // ---------------------------------------------------------------------------
205
+ function buildDocSpec(documentType) {
206
+ const meta = DOC_META[documentType];
207
+ if (!meta)
208
+ return null;
209
+ return {
210
+ id: documentType,
211
+ documentType,
212
+ name: meta.name,
213
+ accept: ACCEPT_BY_TYPE[documentType] || ACCEPT_PDF_DOCX,
214
+ strategies: meta.strategies,
215
+ required: meta.tier === "required" ? true : meta.tier === "recommended" ? "optional" : "conditional",
216
+ tier: meta.tier,
217
+ help: meta.help,
218
+ };
219
+ }
220
+ function getDocSpecs(entityType) {
221
+ var _a;
222
+ const types = entityType
223
+ ? ((_a = ENTITY_DOCUMENT_TYPES[entityType]) !== null && _a !== void 0 ? _a : LLC_TYPES)
224
+ : LLC_TYPES;
225
+ return types
226
+ .map(buildDocSpec)
227
+ .filter((s) => s !== null);
33
228
  }
@@ -153,12 +153,15 @@ export interface ProspectStrategyMeta {
153
153
  typicalRange: string;
154
154
  prospectNote: string;
155
155
  }
156
+ export type DocTier = "required" | "recommended" | "conditional";
156
157
  export interface DocSpec {
157
158
  id: string;
159
+ documentType: string;
158
160
  name: string;
159
161
  accept: string[];
160
162
  strategies: string[];
161
163
  required: boolean | "optional" | "conditional";
164
+ tier: DocTier;
162
165
  help: string;
163
166
  }
164
167
  export interface SidebarLookupEntry {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paro.io/expert-shared-components",
3
- "version": "1.14.50",
3
+ "version": "1.14.52",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {