diff --git a/Cyrano/src/engines/base-engine.ts b/Cyrano/src/engines/base-engine.ts index 90033bac..c653fcd1 100644 --- a/Cyrano/src/engines/base-engine.ts +++ b/Cyrano/src/engines/base-engine.ts @@ -707,14 +707,4 @@ export abstract class BaseEngine { * Cleanup resources */ abstract cleanup(): Promise; -} - - -} -} -} -} -} -} -} } \ No newline at end of file diff --git a/Cyrano/src/engines/chronometric/chronometric-engine.ts b/Cyrano/src/engines/chronometric/chronometric-engine.ts index 15669e5e..bfa174f3 100644 --- a/Cyrano/src/engines/chronometric/chronometric-engine.ts +++ b/Cyrano/src/engines/chronometric/chronometric-engine.ts @@ -341,22 +341,3 @@ export class ChronometricEngine extends BaseEngine { // Export singleton instance export const chronometricEngine = new ChronometricEngine(); - -} -} -} -} -} -} -} -] -} -} -} -} -} -} -} -} -] -} \ No newline at end of file diff --git a/Cyrano/src/engines/chronometric/modules/cost-estimation-module.ts b/Cyrano/src/engines/chronometric/modules/cost-estimation-module.ts index bc894fb9..09459db8 100644 --- a/Cyrano/src/engines/chronometric/modules/cost-estimation-module.ts +++ b/Cyrano/src/engines/chronometric/modules/cost-estimation-module.ts @@ -417,14 +417,3 @@ export class CostEstimationModule extends BaseModule { } // Export singleton instance -export const costEstimationModule = new CostEstimationModule(); - -} -} -} -} -} -} -} -} -} \ No newline at end of file diff --git a/Cyrano/src/engines/chronometric/modules/pattern-learning-module.ts b/Cyrano/src/engines/chronometric/modules/pattern-learning-module.ts index 7f6f8044..cef99eb5 100644 --- a/Cyrano/src/engines/chronometric/modules/pattern-learning-module.ts +++ b/Cyrano/src/engines/chronometric/modules/pattern-learning-module.ts @@ -368,9 +368,3 @@ export class PatternLearningModule extends BaseModule { } // Export singleton instance -export const patternLearningModule = new PatternLearningModule(); - - -} -} -} \ No newline at end of file diff --git a/Cyrano/src/engines/chronometric/modules/time-reconstruction-module.ts b/Cyrano/src/engines/chronometric/modules/time-reconstruction-module.ts index d4225346..83120ba7 100644 --- a/Cyrano/src/engines/chronometric/modules/time-reconstruction-module.ts +++ b/Cyrano/src/engines/chronometric/modules/time-reconstruction-module.ts @@ -489,10 +489,3 @@ export class TimeReconstructionModule extends BaseModule { } // Export singleton instance -export const timeReconstructionModule = new TimeReconstructionModule(); - -} -} -} -} -} \ No newline at end of file diff --git a/Cyrano/src/engines/chronometric/services/baseline-config.ts b/Cyrano/src/engines/chronometric/services/baseline-config.ts index 8a62adc2..8e25fe21 100644 --- a/Cyrano/src/engines/chronometric/services/baseline-config.ts +++ b/Cyrano/src/engines/chronometric/services/baseline-config.ts @@ -128,19 +128,3 @@ export async function addOffDay(userId: string, date: string): Promise { - const config = await getBaselineConfig(userId); - if (!config || !config.offDays) { - return config; - } - - const offDays = config.offDays.filter(d => d !== date); - - return await saveBaselineConfig({ - ...config, - offDays, - }); -} - - -} \ No newline at end of file diff --git a/Cyrano/src/engines/chronometric/services/cost-estimation.ts b/Cyrano/src/engines/chronometric/services/cost-estimation.ts index 9e5cdbdb..ae3d64ee 100644 --- a/Cyrano/src/engines/chronometric/services/cost-estimation.ts +++ b/Cyrano/src/engines/chronometric/services/cost-estimation.ts @@ -295,19 +295,3 @@ ${new Date().toISOString().split('T')[0]} // Export singleton instance export const costEstimationService = new CostEstimationService(); - -} -} -} -} -) -} -} -} -} -) -} -} -) -} -} \ No newline at end of file diff --git a/Cyrano/src/engines/chronometric/services/profitability-analyzer.ts b/Cyrano/src/engines/chronometric/services/profitability-analyzer.ts index 0ad15ba9..c857a582 100644 --- a/Cyrano/src/engines/chronometric/services/profitability-analyzer.ts +++ b/Cyrano/src/engines/chronometric/services/profitability-analyzer.ts @@ -262,14 +262,3 @@ export async function getProfitabilitySummary(userId: string): Promise<{ totalActual, overallVariance: totalActual - totalBudgeted, }; -} - - -} -} -} -} -} -} -} -} \ No newline at end of file diff --git a/Cyrano/src/http-bridge.ts b/Cyrano/src/http-bridge.ts index 6567243d..49cbbab9 100644 --- a/Cyrano/src/http-bridge.ts +++ b/Cyrano/src/http-bridge.ts @@ -1015,8 +1015,14 @@ app.get('/api/good-counsel/overview', async (req, res) => { // FORECASTER API (LexFiat Forecasterâ„¢ standalone frontend compatibility) // ============================================================================ +// Import FederalTaxInputSchema dynamically for validation +// Note: We use z.lazy() to avoid circular dependency issues with dynamic imports const ForecastHttpRequestSchema = z.object({ - forecast_input: z.any(), + forecast_input: z.lazy(() => { + // This will be validated in the handler after importing the schema + // Using z.record allows flexibility for additional properties + return z.record(z.any()); + }), branding: z.object({ presentationMode: z.enum(['strip', 'watermark', 'none']).optional(), userRole: z.enum(['attorney', 'staff', 'client', 'other']).optional(), @@ -1050,8 +1056,19 @@ app.post('/api/forecast/tax', async (req, res) => { } // Use calculateFederal() for complete credit calculations (CTC/ODC/ACTC/EITC) - const { calculateFederal } = await import('./modules/forecast/formulas/tax-formulas.js'); - const calculatedValues = calculateFederal(forecast_input); + const { calculateFederal, FederalTaxInputSchema } = await import('./modules/forecast/formulas/tax-formulas.js'); + + // Validate forecast_input with FederalTaxInputSchema for type safety + const validationResult = FederalTaxInputSchema.safeParse(forecast_input); + if (!validationResult.success) { + return res.status(400).json({ + success: false, + error: 'Invalid forecast_input data', + details: validationResult.error.issues + }); + } + + const calculatedValues = calculateFederal(validationResult.data); res.json({ success: true, @@ -1080,8 +1097,19 @@ app.post('/api/forecast/tax/pdf', async (req, res) => { const year = forecast_input?.year || new Date().getFullYear(); // 1) Calculate values using calculateFederal() for complete credit calculations - const { calculateFederal } = await import('./modules/forecast/formulas/tax-formulas.js'); - const calculated = calculateFederal(forecast_input); + const { calculateFederal, FederalTaxInputSchema } = await import('./modules/forecast/formulas/tax-formulas.js'); + + // Validate forecast_input with Zod schema + const validationResult = FederalTaxInputSchema.safeParse(forecast_input); + if (!validationResult.success) { + return res.status(400).json({ + success: false, + error: 'Invalid forecast_input data', + details: validationResult.error.issues + }); + } + + const calculated = calculateFederal(validationResult.data); // 2) Map to 1040 fill keys (minimal set; expands as module evolves) const filingStatusIndex: Record = { diff --git a/Cyrano/src/modules/forecast/formulas/tax-formulas.ts b/Cyrano/src/modules/forecast/formulas/tax-formulas.ts index 80e948f6..9591a453 100644 --- a/Cyrano/src/modules/forecast/formulas/tax-formulas.ts +++ b/Cyrano/src/modules/forecast/formulas/tax-formulas.ts @@ -9,6 +9,8 @@ * Implements IRS tax brackets and calculation logic for 2018-2025 */ +import { z } from 'zod'; + export type FilingStatus = 'single' | 'married_joint' | 'married_separate' | 'head_of_household' | 'qualifying_widow'; export interface TaxBracket { @@ -511,6 +513,30 @@ export interface FederalTaxInput { estimatedWithholding?: number; } +/** + * Zod validation schema for FederalTaxInput + * Ensures type safety and runtime validation at API boundaries + */ +export const FederalTaxInputSchema = z.object({ + year: z.number().int().min(2018).max(2025), + filingStatus: z.enum(['single', 'married_joint', 'married_separate', 'head_of_household', 'qualifying_widow']), + wages: z.number().nonnegative(), + selfEmploymentIncome: z.number().nonnegative().optional(), + interestIncome: z.number().nonnegative().optional(), + dividendIncome: z.number().nonnegative().optional(), + capitalGains: z.number().optional(), + otherIncome: z.number().optional(), + itemizedDeductions: z.number().nonnegative().optional(), + standardDeduction: z.number().nonnegative().optional(), + qualifyingChildrenUnder17: z.number().int().nonnegative().optional(), + otherDependents: z.number().int().nonnegative().optional(), + filerAge: z.number().int().positive().optional(), + spouseAge: z.number().int().positive().optional(), + canBeClaimedAsDependent: z.boolean().optional(), + estimatedWithholding: z.number().nonnegative().optional() +}); + + /** * Standalone backend result interface (for compatibility) */ diff --git a/apps/forecaster/backend/package-lock.json b/apps/forecaster/backend/package-lock.json index 71cd2070..0885ef3a 100644 --- a/apps/forecaster/backend/package-lock.json +++ b/apps/forecaster/backend/package-lock.json @@ -16,7 +16,7 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.0.3", + "@types/node": "^25.0.8", "tsx": "^4.21.0", "typescript": "^5.9.3" } @@ -545,9 +545,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", - "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "version": "25.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz", + "integrity": "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/apps/forecaster/backend/package.json b/apps/forecaster/backend/package.json index f677e3f8..3063e887 100644 --- a/apps/forecaster/backend/package.json +++ b/apps/forecaster/backend/package.json @@ -18,9 +18,8 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.0.3", + "@types/node": "^25.0.8", "tsx": "^4.21.0", "typescript": "^5.9.3" } } - diff --git a/apps/forecaster/backend/src/city/city-tax.ts b/apps/forecaster/backend/src/city/city-tax.ts index 6a52c45f..9610f9b6 100644 --- a/apps/forecaster/backend/src/city/city-tax.ts +++ b/apps/forecaster/backend/src/city/city-tax.ts @@ -57,8 +57,4 @@ export function calculateCityTax(input: CityTaxInput): CityTaxResult { refundOrBalance, warnings }; -} - - -} } \ No newline at end of file diff --git a/apps/forecaster/backend/src/pdf/pdf-filler.ts b/apps/forecaster/backend/src/pdf/pdf-filler.ts index e5a46e7c..eb37ae90 100644 --- a/apps/forecaster/backend/src/pdf/pdf-filler.ts +++ b/apps/forecaster/backend/src/pdf/pdf-filler.ts @@ -138,10 +138,4 @@ export async function applyBranding(params: { const bytes = await pdfDoc.save(); return { pdfBase64: toBase64(bytes) }; -} - - -} -} -} -) \ No newline at end of file +} \ No newline at end of file diff --git a/apps/forecaster/backend/src/tax/federal.ts b/apps/forecaster/backend/src/tax/federal.ts index 4a9cb528..6a2ce6cd 100644 --- a/apps/forecaster/backend/src/tax/federal.ts +++ b/apps/forecaster/backend/src/tax/federal.ts @@ -12,7 +12,7 @@ import { type FederalTaxInput, type FederalTaxResult, type FilingStatus, -} from '../../../../Cyrano/src/modules/forecast/formulas/tax-formulas.js'; +} from '../../../../../Cyrano/src/modules/forecast/formulas/tax-formulas.js'; // Re-export for backward compatibility export type { FederalTaxInput, FederalTaxResult, FilingStatus }; diff --git a/apps/forecaster/backend/tsconfig.json b/apps/forecaster/backend/tsconfig.json index 541b656e..2997c793 100644 --- a/apps/forecaster/backend/tsconfig.json +++ b/apps/forecaster/backend/tsconfig.json @@ -5,13 +5,12 @@ "module": "ESNext", "moduleResolution": "bundler", "outDir": "dist", - "rootDir": "src", "strict": true, "skipLibCheck": true, "esModuleInterop": true, "resolveJsonModule": true, "types": ["node"] }, - "include": ["src"] + "include": ["src", "../../../Cyrano/src/modules/forecast/formulas/tax-formulas.ts"] }