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