Finacial disclaimer and SOC fix between Explorer and Education pages
This commit is contained in:
parent
c16d029432
commit
8c7bcb4696
2
.env
2
.env
@ -2,7 +2,7 @@ CORS_ALLOWED_ORIGINS=https://dev1.aptivaai.com,http://34.16.120.118:3000,http://
|
||||
SERVER1_PORT=5000
|
||||
SERVER2_PORT=5001
|
||||
SERVER3_PORT=5002
|
||||
IMG_TAG=4cfdf84-202508101351
|
||||
IMG_TAG=b0cbb65-202508101532
|
||||
|
||||
ENV_NAME=dev
|
||||
PROJECT=aptivaai-dev
|
@ -384,11 +384,11 @@ const handleCareerClick = useCallback(
|
||||
const s = salaryRes.data;
|
||||
const salaryData = s && Object.keys(s).length
|
||||
? [
|
||||
{ percentile: '10th Percentile', regionalSalary: +s.regional?.regional_PCT10 || 0, nationalSalary: +s.national?.national_PCT10 || 0 },
|
||||
{ percentile: '25th Percentile', regionalSalary: +s.regional?.regional_PCT25 || 0, nationalSalary: +s.national?.national_PCT25 || 0 },
|
||||
{ percentile: '10th Percentile', regionalSalary: +s.regional?.regional_PCT10 || 0, nationalSalary: +s.national?.national_PCT10 || 0 },
|
||||
{ percentile: '25th Percentile', regionalSalary: +s.regional?.regional_PCT25 || 0, nationalSalary: +s.national?.national_PCT25 || 0 },
|
||||
{ percentile: 'Median', regionalSalary: +s.regional?.regional_MEDIAN || 0, nationalSalary: +s.national?.national_MEDIAN || 0 },
|
||||
{ percentile: '75th Percentile', regionalSalary: +s.regional?.regional_PCT75 || 0, nationalSalary: +s.national?.national_PCT75 || 0 },
|
||||
{ percentile: '90th Percentile', regionalSalary: +s.regional?.regional_PCT90 || 0, nationalSalary: +s.national?.national_PCT90 || 0 },
|
||||
{ percentile: '75th Percentile', regionalSalary: +s.regional?.regional_PCT75 || 0, nationalSalary: +s.national?.national_PCT75 || 0 },
|
||||
{ percentile: '90th Percentile', regionalSalary: +s.regional?.regional_PCT90 || 0, nationalSalary: +s.national?.national_PCT90 || 0 },
|
||||
]
|
||||
: [];
|
||||
|
||||
@ -618,7 +618,6 @@ useEffect(() => {
|
||||
:
|
||||
3;
|
||||
|
||||
|
||||
const defaultMeaningValue = 3;
|
||||
|
||||
// 4) open the InterestMeaningModal instead of using prompt()
|
||||
@ -684,44 +683,72 @@ useEffect(() => {
|
||||
});
|
||||
};
|
||||
|
||||
const stripSoc = (s = '') => s.split('.')[0];
|
||||
// ------------------------------------------------------
|
||||
// "Select for Education" => navigate with CIP codes
|
||||
// ------------------------------------------------------
|
||||
// CareerExplorer.js
|
||||
const handleSelectForEducation = (career) => {
|
||||
// CareerExplorer.js
|
||||
const handleSelectForEducation = async (career) => {
|
||||
if (!career) return;
|
||||
|
||||
// ─── 1. Ask first ─────────────────────────────────────────────
|
||||
const ok = window.confirm(
|
||||
`Are you sure you want to move on to Educational Programs for “${career.title}”?`
|
||||
);
|
||||
if (!ok) return;
|
||||
|
||||
// ─── 2. Make sure we have a full SOC code ─────────────────────
|
||||
const fullSoc = career.soc_code || career.code || '';
|
||||
if (!fullSoc) {
|
||||
const baseSoc = stripSoc(fullSoc);
|
||||
if (!baseSoc) {
|
||||
alert('Sorry – this career is missing a valid SOC code.');
|
||||
return;
|
||||
}
|
||||
|
||||
// ─── 3. Find & clean CIP codes (may be empty) ─────────────────
|
||||
const match = masterCareerRatings.find(r => r.soc_code === fullSoc);
|
||||
const rawCips = match?.cip_codes ?? []; // original array
|
||||
const cleanedCips = cleanCipCodes(rawCips); // “0402”, “1409”, …
|
||||
// 1) try local JSON by base SOC (tolerates .00 vs none)
|
||||
const match = masterCareerRatings.find(r => stripSoc(r.soc_code) === baseSoc);
|
||||
let rawCips = Array.isArray(match?.cip_codes) ? match.cip_codes : [];
|
||||
let cleanedCips = cleanCipCodes(rawCips);
|
||||
|
||||
// ─── 4. Persist ONE tidy object for later pages ───────────────
|
||||
// 2) fallback: ask server2 to map SOC→CIP if local didn’t have any
|
||||
if (!cleanedCips.length) {
|
||||
try {
|
||||
const candidates = [
|
||||
fullSoc, // as-is
|
||||
baseSoc, // stripped
|
||||
fullSoc.includes('.') ? null : `${fullSoc}.00` // add .00 if missing
|
||||
].filter(Boolean);
|
||||
|
||||
let fromApi = null;
|
||||
for (const soc of candidates) {
|
||||
const res = await fetch(`/api/cip/${soc}`);
|
||||
if (res.ok) {
|
||||
const { cipCode } = await res.json();
|
||||
if (cipCode) { fromApi = cipCode; break; }
|
||||
}
|
||||
}
|
||||
|
||||
if (fromApi) {
|
||||
rawCips = [fromApi];
|
||||
cleanedCips = cleanCipCodes(rawCips);
|
||||
}
|
||||
} catch {
|
||||
// best-effort fallback; continue
|
||||
}
|
||||
}
|
||||
|
||||
// Persist for the next page (keep raw list if we have it)
|
||||
const careerForStorage = {
|
||||
...career,
|
||||
soc_code : fullSoc,
|
||||
cip_code : rawCips // keep the raw list; page cleans them again if needed
|
||||
cip_code : rawCips
|
||||
};
|
||||
localStorage.setItem('selectedCareer', JSON.stringify(careerForStorage));
|
||||
|
||||
// ─── 5. Off we go ─────────────────────────────────────────────
|
||||
// Navigate with the robust base SOC + cleaned CIP prefixes
|
||||
navigate('/educational-programs', {
|
||||
state: {
|
||||
socCode : fullSoc,
|
||||
cipCodes : cleanedCips, // can be [], page handles it
|
||||
socCode : baseSoc,
|
||||
cipCodes : cleanedCips, // can be [] if absolutely nothing found
|
||||
careerTitle : career.title,
|
||||
userZip : userZipcode,
|
||||
userState : userState
|
||||
@ -729,7 +756,6 @@ const handleSelectForEducation = (career) => {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Filter logic for jobZone, Fit
|
||||
// ------------------------------------------------------
|
||||
|
@ -29,6 +29,7 @@ import parseFloatOrZero from '../utils/ParseFloatorZero.js';
|
||||
import { getFullStateName } from '../utils/stateUtils.js';
|
||||
import CareerCoach from "./CareerCoach.js";
|
||||
import ChatCtx from '../contexts/ChatCtx.js';
|
||||
import FinancialDisclaimer from './FinancialDisclaimer.js';
|
||||
|
||||
import { Button } from './ui/button.js';
|
||||
import { Pencil } from 'lucide-react';
|
||||
@ -78,6 +79,7 @@ async function createCareerProfileFromSearch(selCareer) {
|
||||
throw new Error('createCareerProfileFromSearch: selCareer.title is required');
|
||||
}
|
||||
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
* 1) Do we already have that title?
|
||||
* --------------------------------------------------------- */
|
||||
@ -1497,7 +1499,8 @@ const handleMilestonesCreated = useCallback(
|
||||
<InfoTooltip message="This projection uses the salary, expense, loan and contribution inputs you’ve set below. Click “Edit Simulation Inputs” to adjust them, or edit your financial information in Profile -> Financial Profile." />
|
||||
</h3>
|
||||
|
||||
|
||||
{/* Legal/assumptions disclaimer (static, matches simulator) */}
|
||||
<FinancialDisclaimer />
|
||||
{projectionData.length ? (
|
||||
<div>
|
||||
{/* Chart – now full width */}
|
||||
@ -1526,7 +1529,7 @@ const handleMilestonesCreated = useCallback(
|
||||
>
|
||||
Reset Zoom
|
||||
</Button>
|
||||
|
||||
|
||||
{loanPayoffMonth && hasStudentLoan && (
|
||||
<p className="font-semibold text-sm mt-2">
|
||||
Loan Paid Off:
|
||||
|
29
src/components/FinancialDisclaimer.js
Normal file
29
src/components/FinancialDisclaimer.js
Normal file
@ -0,0 +1,29 @@
|
||||
export default function FinancialDisclaimer() {
|
||||
return (
|
||||
<div className="mt-3 text-xs text-gray-600 border-t pt-3 leading-snug">
|
||||
<p>
|
||||
<strong>Important:</strong> These projections are educational estimates and <strong>not</strong> financial, tax,
|
||||
legal, or investment advice. Federal tax is approximated using the <strong>single‑filer standard deduction
|
||||
($13,850)</strong> and <strong>2023 federal brackets</strong>; state tax is a <strong>flat percentage by
|
||||
state</strong> from an internal table. Estimates <strong>exclude</strong> Social Security/Medicare (FICA),
|
||||
local taxes, itemized deductions, credits, AMT, capital‑gains rules, and self‑employment taxes. Actual outcomes
|
||||
vary.
|
||||
</p>
|
||||
|
||||
<details className="mt-2">
|
||||
<summary className="cursor-pointer select-none">Assumptions & limitations</summary>
|
||||
<ul className="list-disc pl-5 mt-2 space-y-1">
|
||||
<li><strong>Filing model:</strong> W‑2 wages, single filer, standard deduction only; no dependents, credits, or adjustments.</li>
|
||||
<li><strong>Federal tax calc:</strong> Monthly estimate = (annualized income − $13,850) through 2023 bands (10/12/22/24/32/35/37%), divided by 12.</li>
|
||||
<li><strong>State tax:</strong> Single flat rate per state from a fixed lookup; local/city taxes not modeled.</li>
|
||||
<li><strong>Payroll taxes:</strong> FICA is <strong>not</strong> included.</li>
|
||||
<li><strong>Education & loans:</strong> Tuition follows the chosen academic calendar; if deferring, tuition is added to loan principal and interest accrues monthly at the specified APR. No IDR/PSLF/forbearance nuances.</li>
|
||||
<li><strong>Income changes:</strong> Expected salary replaces current salary starting at/after the provided graduation date; other changes come from user‑defined milestones.</li>
|
||||
<li><strong>Contributions & withdrawals:</strong> Retirement/emergency contributions treated as post‑tax outflows. Retirement withdrawals treated as taxable ordinary income; no early‑withdrawal penalties/RMD rules modeled.</li>
|
||||
<li><strong>Returns:</strong> Retirement balances use the selected strategy (none / flat annual rate / random monthly range). No general inflation is applied unless you model it via milestones.</li>
|
||||
<li><strong>Data sources:</strong> Public datasets (e.g., BLS/NCES) may change without notice. COL adjustments not applied unless explicitly labeled.</li>
|
||||
</ul>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user