diff --git a/src/components/CareerCoach.js b/src/components/CareerCoach.js index 6ff1142..4124e0a 100644 --- a/src/components/CareerCoach.js +++ b/src/components/CareerCoach.js @@ -3,6 +3,21 @@ import authFetch from "../utils/authFetch.js"; const isoToday = new Date().toISOString().slice(0,10); // top-level helper +function buildInterviewPrompt(careerName, jobDescription = "") { + return ` +You are an expert interviewer for the role **${careerName}**. +Ask one challenging behavioural or technical question **specific to this career**, +wait for the candidate's reply, then: + + • Score the answer 1–5 + • Give concise feedback (1-2 sentences) + • Ask the next question (up to 5 total) + +After 5 questions or if the user types "quit interview", end the session. + +Do NOT output milestones JSON.`; +} + /* ---------------------------------------------- Hidden prompts for the quick-action buttons ---------------------------------------------- */ @@ -193,23 +208,40 @@ I'm here to support you with personalized coaching. What would you like to focus /* ------------ quick-action buttons ------------- */ function triggerQuickAction(type) { - if (loading) return; + if (loading) return; - // 1. Add a visible note for user *without* showing the raw system prompt + if (type === "interview") { + const career = scenarioRow?.career_name || "the target role"; + const desc = scenarioRow?.job_description || ""; + const hiddenSystem = { + role: "system", + content: buildInterviewPrompt(career, desc) // ← dynamic prompt + }; const note = { role: "assistant", content: - type === "interview" - ? "Starting mock interview! (answer each question and I’ll give feedback)" - : `Sure! Let me create a ${type === "networking" ? "Networking" : "Job-Search"} roadmap for you…` + "Starting mock interview focused on **" + career + "**. Answer each question and I'll give feedback!" }; - const hiddenSystem = { role: "system", content: QUICK_PROMPTS[type] }; - - const updatedHistory = [...messages, note, hiddenSystem]; - setMessages([...messages, note]); // show only the friendly note - callAi(updatedHistory); + const updated = [...messages, note, hiddenSystem]; + setMessages([...messages, note]); + callAi(updated); + return; } + /* networking / jobSearch unchanged */ + const note = { + role: "assistant", + content: + type === "networking" + ? "Sure! Let me create a Networking roadmap for you…" + : "Sure! Let me create a Job-Search roadmap for you…" + }; + const hiddenSystem = { role: "system", content: QUICK_PROMPTS[type] }; + const updated = [...messages, note, hiddenSystem]; + setMessages([...messages, note]); + callAi(updated); +} + /* ------------ render ------------- */ return (
diff --git a/src/utils/FinancialProjectionService.js b/src/utils/FinancialProjectionService.js index 22a02d5..e825bcb 100644 --- a/src/utils/FinancialProjectionService.js +++ b/src/utils/FinancialProjectionService.js @@ -1,5 +1,21 @@ import moment from 'moment'; + +/** ------------------------------------------------- + * Utility: coerce ANY input to a safe number + * - Empty string, null, undefined, NaN → 0 + * - Valid numeric string → Number(value) + * - Already-a-number → Number(value) + * Keeps everything finite so .toFixed() is safe + * -------------------------------------------------*/ +const n = (val, fallback = 0) => { + const num = Number(val); + return Number.isFinite(num) ? num : fallback; +}; + +const num = (v) => + v === null || v === undefined || v === '' || Number.isNaN(+v) ? 0 : +v; + /*************************************************** * HELPER: Approx State Tax Rates ***************************************************/ @@ -89,65 +105,95 @@ export function simulateFinancialProjection(userProfile) { /*************************************************** * 1) DESTRUCTURE USER PROFILE ***************************************************/ - const { - // Basic incomes - currentSalary = 0, - monthlyExpenses = 0, - monthlyDebtPayments = 0, - partTimeIncome = 0, - extraPayment = 0, +/* 1️⃣ Destructure exactly the same keys you already use … */ +/* but add an underscore so we can soon normalise each one. */ +const { + // Basic incomes ---------------------------------------------- + currentSalary: _currentSalary = 0, + monthlyExpenses: _monthlyExpenses = 0, + monthlyDebtPayments: _monthlyDebtPayments = 0, + partTimeIncome: _partTimeIncome = 0, + extraPayment: _extraPayment = 0, - // Student loan config - studentLoanAmount = 0, - interestRate = 5, - loanTerm = 10, - loanDeferralUntilGraduation = false, + // Student-loan config ---------------------------------------- + studentLoanAmount: _studentLoanAmount = 0, + interestRate: _interestRate = 5, + loanTerm: _loanTerm = 10, + loanDeferralUntilGraduation = false, - // College config - inCollege = false, // <<==== user-provided - programType, - hoursCompleted = 0, - creditHoursPerYear, - calculatedTuition, - enrollmentDate, - gradDate, - startDate, - academicCalendar = 'monthly', - annualFinancialAid = 0, + // College ---------------------------------------------------- + inCollege = false, + programType, + hoursCompleted: _hoursCompleted = 0, + creditHoursPerYear: _creditHoursPerYear, + calculatedTuition: _calculatedTuition, + enrollmentDate, + gradDate, + startDate, + academicCalendar = 'monthly', + annualFinancialAid: _annualFinancialAid = 0, - // Post-college salary - expectedSalary = 0, + // Post-college salary ---------------------------------------- + expectedSalary: _expectedSalary = 0, - // Savings & monthly contributions - emergencySavings = 0, - retirementSavings = 0, - monthlyRetirementContribution = 0, - monthlyEmergencyContribution = 0, + // Savings & contributions ------------------------------------ + emergencySavings: _emergencySavings = 0, + retirementSavings: _retirementSavings = 0, + monthlyRetirementContribution:_monthlyRetirementContribution = 0, + monthlyEmergencyContribution:_monthlyEmergencyContribution = 0, - // Surplus distribution - surplusEmergencyAllocation = 50, - surplusRetirementAllocation = 50, + // Surplus distribution --------------------------------------- + surplusEmergencyAllocation: _surplusEmergencyAllocation = 50, + surplusRetirementAllocation: _surplusRetirementAllocation = 50, - // Program length override - programLength, + // Other ------------------------------------------------------- + programLength, + stateCode = 'GA', + milestoneImpacts = [], + simulationYears = 20, - // State code - stateCode = 'GA', + interestStrategy = 'NONE', + flatAnnualRate: _flatAnnualRate = 0.06, + monthlyReturnSamples = [], + randomRangeMin: _randomRangeMin = -0.02, + randomRangeMax: _randomRangeMax = 0.02 +} = userProfile; - // Financial milestone impacts - milestoneImpacts = [], +/* 2️⃣ Immediately convert every money/percentage/count field to a real Number */ +const currentSalary = num(_currentSalary); +const monthlyExpenses = num(_monthlyExpenses); +const monthlyDebtPayments = num(_monthlyDebtPayments); +const partTimeIncome = num(_partTimeIncome); +const extraPayment = num(_extraPayment); - // Simulation duration - simulationYears = 20, +const studentLoanAmount = num(_studentLoanAmount); +const interestRate = num(_interestRate); +const loanTerm = num(_loanTerm); - interestStrategy = 'NONE', // 'NONE' | 'FLAT' | 'MONTE_CARLO' - flatAnnualRate = 0.06, // 6% default if using FLAT - monthlyReturnSamples = [], // if using historical-based random sampling - randomRangeMin = -0.02, // if using a random range approach - randomRangeMax = 0.02, +const hoursCompleted = num(_hoursCompleted); +const creditHoursPerYear = num(_creditHoursPerYear); +const calculatedTuition = num(_calculatedTuition); +const annualFinancialAid = num(_annualFinancialAid); +const expectedSalary = num(_expectedSalary); - } = userProfile; +const emergencySavings = num(_emergencySavings); +const retirementSavings = num(_retirementSavings); +const monthlyRetirementContribution= num(_monthlyRetirementContribution); +const monthlyEmergencyContribution = num(_monthlyEmergencyContribution); +const surplusEmergencyAllocation = num(_surplusEmergencyAllocation); +const surplusRetirementAllocation = num(_surplusRetirementAllocation); + +const flatAnnualRate = num(_flatAnnualRate); +const randomRangeMin = num(_randomRangeMin); +const randomRangeMax = num(_randomRangeMax); + +/* ------------------------------------------------- + * Use the “…Safe” variables below instead of the + * raw ones whenever you’ll call .toFixed() or do + * arithmetic. All names are preserved; only the + * “Safe” suffix distinguishes the sanitized values. + * -------------------------------------------------*/ /*************************************************** * HELPER: Retirement Interest Rate @@ -325,40 +371,40 @@ for (let monthIndex = 0; monthIndex < maxMonths; monthIndex++) { } /************************************************ - * 7.3 MILESTONE IMPACTS - ************************************************/ - let extraImpactsThisMonth = 0; - milestoneImpacts.forEach((impact) => { - const startDateClamped = moment(impact.start_date).startOf('month'); - let startOffset = startDateClamped.diff(scenarioStartClamped, 'months'); - if (startOffset < 0) startOffset = 0; + * 7.3 MILESTONE IMPACTS – strict number handling + ************************************************/ +let extraImpactsThisMonth = 0; - let endOffset = Infinity; - if (impact.end_date && impact.end_date.trim() !== '') { - const endDateClamped = moment(impact.end_date).startOf('month'); - endOffset = endDateClamped.diff(scenarioStartClamped, 'months'); - if (endOffset < 0) endOffset = 0; - } +milestoneImpacts.forEach((rawImpact) => { + /* --- safety / coercion ------------------------------------------------ */ + const amount = Number(rawImpact.amount) || 0; // ← always a number + const type = (rawImpact.impact_type || 'MONTHLY').toUpperCase(); // 'ONE_TIME' | 'MONTHLY' + const direction = (rawImpact.direction || 'subtract').toLowerCase(); // 'add' | 'subtract' + + /* --- date math -------------------------------------------------------- */ + const startDateClamped = moment(rawImpact.start_date).startOf('month'); + let startOffset = startDateClamped.diff(scenarioStartClamped, 'months'); + if (startOffset < 0) startOffset = 0; + + let endOffset = Infinity; + if (rawImpact.end_date && rawImpact.end_date.trim() !== '') { + const endDateClamped = moment(rawImpact.end_date).startOf('month'); + endOffset = endDateClamped.diff(scenarioStartClamped, 'months'); + if (endOffset < 0) endOffset = 0; + } + + /* --- apply impact ----------------------------------------------------- */ + const applyAmount = (dir) => + dir === 'add' ? (baseMonthlyIncome += amount) : (extraImpactsThisMonth += amount); + + if (type === 'ONE_TIME') { + if (monthIndex === startOffset) applyAmount(direction); + } else { + // MONTHLY (or anything else) – apply for the whole span + if (monthIndex >= startOffset && monthIndex <= endOffset) applyAmount(direction); + } +}); - if (impact.impact_type === 'ONE_TIME') { - if (monthIndex === startOffset) { - if (impact.direction === 'add') { - baseMonthlyIncome += impact.amount; - } else { - extraImpactsThisMonth += impact.amount; - } - } - } else { - // 'MONTHLY' - if (monthIndex >= startOffset && monthIndex <= endOffset) { - if (impact.direction === 'add') { - baseMonthlyIncome += impact.amount; - } else { - extraImpactsThisMonth += impact.amount; - } - } - } - }); /************************************************ * 7.4 CALCULATE TAXES diff --git a/user_profile.db b/user_profile.db index cab669b..7829304 100644 Binary files a/user_profile.db and b/user_profile.db differ