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
|
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
|
Hidden prompts for the quick-action buttons
|
||||||
---------------------------------------------- */
|
---------------------------------------------- */
|
||||||
@ -195,20 +210,37 @@ I'm here to support you with personalized coaching. What would you like to focus
|
|||||||
function triggerQuickAction(type) {
|
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 = {
|
const note = {
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
content:
|
content:
|
||||||
type === "interview"
|
"Starting mock interview focused on **" + career + "**. Answer each question and I'll give feedback!"
|
||||||
? "Starting mock interview! (answer each question and I’ll give feedback)"
|
};
|
||||||
: `Sure! Let me create a ${type === "networking" ? "Networking" : "Job-Search"} roadmap for you…`
|
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 hiddenSystem = { role: "system", content: QUICK_PROMPTS[type] };
|
||||||
|
const updated = [...messages, note, hiddenSystem];
|
||||||
const updatedHistory = [...messages, note, hiddenSystem];
|
setMessages([...messages, note]);
|
||||||
setMessages([...messages, note]); // show only the friendly note
|
callAi(updated);
|
||||||
callAi(updatedHistory);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------ render ------------- */
|
/* ------------ render ------------- */
|
||||||
return (
|
return (
|
||||||
|
@ -1,5 +1,21 @@
|
|||||||
import moment from 'moment';
|
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
|
* HELPER: Approx State Tax Rates
|
||||||
***************************************************/
|
***************************************************/
|
||||||
@ -89,65 +105,95 @@ export function simulateFinancialProjection(userProfile) {
|
|||||||
/***************************************************
|
/***************************************************
|
||||||
* 1) DESTRUCTURE USER PROFILE
|
* 1) DESTRUCTURE USER PROFILE
|
||||||
***************************************************/
|
***************************************************/
|
||||||
const {
|
/* 1️⃣ Destructure exactly the same keys you already use … */
|
||||||
// Basic incomes
|
/* but add an underscore so we can soon normalise each one. */
|
||||||
currentSalary = 0,
|
const {
|
||||||
monthlyExpenses = 0,
|
// Basic incomes ----------------------------------------------
|
||||||
monthlyDebtPayments = 0,
|
currentSalary: _currentSalary = 0,
|
||||||
partTimeIncome = 0,
|
monthlyExpenses: _monthlyExpenses = 0,
|
||||||
extraPayment = 0,
|
monthlyDebtPayments: _monthlyDebtPayments = 0,
|
||||||
|
partTimeIncome: _partTimeIncome = 0,
|
||||||
|
extraPayment: _extraPayment = 0,
|
||||||
|
|
||||||
// Student loan config
|
// Student-loan config ----------------------------------------
|
||||||
studentLoanAmount = 0,
|
studentLoanAmount: _studentLoanAmount = 0,
|
||||||
interestRate = 5,
|
interestRate: _interestRate = 5,
|
||||||
loanTerm = 10,
|
loanTerm: _loanTerm = 10,
|
||||||
loanDeferralUntilGraduation = false,
|
loanDeferralUntilGraduation = false,
|
||||||
|
|
||||||
// College config
|
// College ----------------------------------------------------
|
||||||
inCollege = false, // <<==== user-provided
|
inCollege = false,
|
||||||
programType,
|
programType,
|
||||||
hoursCompleted = 0,
|
hoursCompleted: _hoursCompleted = 0,
|
||||||
creditHoursPerYear,
|
creditHoursPerYear: _creditHoursPerYear,
|
||||||
calculatedTuition,
|
calculatedTuition: _calculatedTuition,
|
||||||
enrollmentDate,
|
enrollmentDate,
|
||||||
gradDate,
|
gradDate,
|
||||||
startDate,
|
startDate,
|
||||||
academicCalendar = 'monthly',
|
academicCalendar = 'monthly',
|
||||||
annualFinancialAid = 0,
|
annualFinancialAid: _annualFinancialAid = 0,
|
||||||
|
|
||||||
// Post-college salary
|
// Post-college salary ----------------------------------------
|
||||||
expectedSalary = 0,
|
expectedSalary: _expectedSalary = 0,
|
||||||
|
|
||||||
// Savings & monthly contributions
|
// Savings & contributions ------------------------------------
|
||||||
emergencySavings = 0,
|
emergencySavings: _emergencySavings = 0,
|
||||||
retirementSavings = 0,
|
retirementSavings: _retirementSavings = 0,
|
||||||
monthlyRetirementContribution = 0,
|
monthlyRetirementContribution:_monthlyRetirementContribution = 0,
|
||||||
monthlyEmergencyContribution = 0,
|
monthlyEmergencyContribution:_monthlyEmergencyContribution = 0,
|
||||||
|
|
||||||
// Surplus distribution
|
// Surplus distribution ---------------------------------------
|
||||||
surplusEmergencyAllocation = 50,
|
surplusEmergencyAllocation: _surplusEmergencyAllocation = 50,
|
||||||
surplusRetirementAllocation = 50,
|
surplusRetirementAllocation: _surplusRetirementAllocation = 50,
|
||||||
|
|
||||||
// Program length override
|
// Other -------------------------------------------------------
|
||||||
programLength,
|
programLength,
|
||||||
|
|
||||||
// State code
|
|
||||||
stateCode = 'GA',
|
stateCode = 'GA',
|
||||||
|
|
||||||
// Financial milestone impacts
|
|
||||||
milestoneImpacts = [],
|
milestoneImpacts = [],
|
||||||
|
|
||||||
// Simulation duration
|
|
||||||
simulationYears = 20,
|
simulationYears = 20,
|
||||||
|
|
||||||
interestStrategy = 'NONE', // 'NONE' | 'FLAT' | 'MONTE_CARLO'
|
interestStrategy = 'NONE',
|
||||||
flatAnnualRate = 0.06, // 6% default if using FLAT
|
flatAnnualRate: _flatAnnualRate = 0.06,
|
||||||
monthlyReturnSamples = [], // if using historical-based random sampling
|
monthlyReturnSamples = [],
|
||||||
randomRangeMin = -0.02, // if using a random range approach
|
randomRangeMin: _randomRangeMin = -0.02,
|
||||||
randomRangeMax = 0.02,
|
randomRangeMax: _randomRangeMax = 0.02
|
||||||
|
} = userProfile;
|
||||||
|
|
||||||
} = userProfile;
|
/* 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);
|
||||||
|
|
||||||
|
const studentLoanAmount = num(_studentLoanAmount);
|
||||||
|
const interestRate = num(_interestRate);
|
||||||
|
const loanTerm = num(_loanTerm);
|
||||||
|
|
||||||
|
const hoursCompleted = num(_hoursCompleted);
|
||||||
|
const creditHoursPerYear = num(_creditHoursPerYear);
|
||||||
|
const calculatedTuition = num(_calculatedTuition);
|
||||||
|
const annualFinancialAid = num(_annualFinancialAid);
|
||||||
|
const expectedSalary = num(_expectedSalary);
|
||||||
|
|
||||||
|
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
|
* HELPER: Retirement Interest Rate
|
||||||
@ -325,40 +371,40 @@ for (let monthIndex = 0; monthIndex < maxMonths; monthIndex++) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/************************************************
|
/************************************************
|
||||||
* 7.3 MILESTONE IMPACTS
|
* 7.3 MILESTONE IMPACTS – strict number handling
|
||||||
************************************************/
|
************************************************/
|
||||||
let extraImpactsThisMonth = 0;
|
let extraImpactsThisMonth = 0;
|
||||||
milestoneImpacts.forEach((impact) => {
|
|
||||||
const startDateClamped = moment(impact.start_date).startOf('month');
|
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');
|
let startOffset = startDateClamped.diff(scenarioStartClamped, 'months');
|
||||||
if (startOffset < 0) startOffset = 0;
|
if (startOffset < 0) startOffset = 0;
|
||||||
|
|
||||||
let endOffset = Infinity;
|
let endOffset = Infinity;
|
||||||
if (impact.end_date && impact.end_date.trim() !== '') {
|
if (rawImpact.end_date && rawImpact.end_date.trim() !== '') {
|
||||||
const endDateClamped = moment(impact.end_date).startOf('month');
|
const endDateClamped = moment(rawImpact.end_date).startOf('month');
|
||||||
endOffset = endDateClamped.diff(scenarioStartClamped, 'months');
|
endOffset = endDateClamped.diff(scenarioStartClamped, 'months');
|
||||||
if (endOffset < 0) endOffset = 0;
|
if (endOffset < 0) endOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (impact.impact_type === 'ONE_TIME') {
|
/* --- apply impact ----------------------------------------------------- */
|
||||||
if (monthIndex === startOffset) {
|
const applyAmount = (dir) =>
|
||||||
if (impact.direction === 'add') {
|
dir === 'add' ? (baseMonthlyIncome += amount) : (extraImpactsThisMonth += amount);
|
||||||
baseMonthlyIncome += impact.amount;
|
|
||||||
|
if (type === 'ONE_TIME') {
|
||||||
|
if (monthIndex === startOffset) applyAmount(direction);
|
||||||
} else {
|
} else {
|
||||||
extraImpactsThisMonth += impact.amount;
|
// MONTHLY (or anything else) – apply for the whole span
|
||||||
|
if (monthIndex >= startOffset && monthIndex <= endOffset) applyAmount(direction);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
} else {
|
|
||||||
// 'MONTHLY'
|
|
||||||
if (monthIndex >= startOffset && monthIndex <= endOffset) {
|
|
||||||
if (impact.direction === 'add') {
|
|
||||||
baseMonthlyIncome += impact.amount;
|
|
||||||
} else {
|
|
||||||
extraImpactsThisMonth += impact.amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/************************************************
|
/************************************************
|
||||||
* 7.4 CALCULATE TAXES
|
* 7.4 CALCULATE TAXES
|
||||||
|
BIN
user_profile.db
BIN
user_profile.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user