number checks for simulator and fixed impacts to numbers.
This commit is contained in:
parent
a76c1babd2
commit
8232fd697e
@ -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 (
|
||||
<div className="border rounded-lg shadow bg-white p-6 mb-6">
|
||||
|
@ -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
|
||||
|
BIN
user_profile.db
BIN
user_profile.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user