number checks for simulator and fixed impacts to numbers.

This commit is contained in:
Josh 2025-06-12 12:31:02 +00:00
parent a76c1babd2
commit 8232fd697e
3 changed files with 167 additions and 89 deletions

View File

@ -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 15
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 Ill 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 (
<div className="border rounded-lg shadow bg-white p-6 mb-6">

View File

@ -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 youll 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

Binary file not shown.