From 8c7bcb4696640bd949f1c413b64c28034bbd3c22 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 10 Aug 2025 15:37:36 +0000 Subject: [PATCH] Finacial disclaimer and SOC fix between Explorer and Education pages --- .env | 2 +- src/components/CareerExplorer.js | 64 +++++++++++++++++++-------- src/components/CareerRoadmap.js | 7 ++- src/components/FinancialDisclaimer.js | 29 ++++++++++++ 4 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 src/components/FinancialDisclaimer.js diff --git a/.env b/.env index 2d66382..ac15385 100644 --- a/.env +++ b/.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 \ No newline at end of file diff --git a/src/components/CareerExplorer.js b/src/components/CareerExplorer.js index e88f2fe..9bdf272 100644 --- a/src/components/CareerExplorer.js +++ b/src/components/CareerExplorer.js @@ -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 // ------------------------------------------------------ diff --git a/src/components/CareerRoadmap.js b/src/components/CareerRoadmap.js index fcd496f..49ba3a2 100644 --- a/src/components/CareerRoadmap.js +++ b/src/components/CareerRoadmap.js @@ -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( - +{/* Legal/assumptions disclaimer (static, matches simulator) */} + {projectionData.length ? (
{/* Chart – now full width */} @@ -1526,7 +1529,7 @@ const handleMilestonesCreated = useCallback( > Reset Zoom - + {loanPayoffMonth && hasStudentLoan && (

Loan Paid Off:  diff --git a/src/components/FinancialDisclaimer.js b/src/components/FinancialDisclaimer.js new file mode 100644 index 0000000..e454b98 --- /dev/null +++ b/src/components/FinancialDisclaimer.js @@ -0,0 +1,29 @@ +export default function FinancialDisclaimer() { + return ( +

+

+ Important: These projections are educational estimates and not financial, tax, + legal, or investment advice. Federal tax is approximated using the single‑filer standard deduction + ($13,850) and 2023 federal brackets; state tax is a flat percentage by + state from an internal table. Estimates exclude Social Security/Medicare (FICA), + local taxes, itemized deductions, credits, AMT, capital‑gains rules, and self‑employment taxes. Actual outcomes + vary. +

+ +
+ Assumptions & limitations +
    +
  • Filing model: W‑2 wages, single filer, standard deduction only; no dependents, credits, or adjustments.
  • +
  • Federal tax calc: Monthly estimate = (annualized income − $13,850) through 2023 bands (10/12/22/24/32/35/37%), divided by 12.
  • +
  • State tax: Single flat rate per state from a fixed lookup; local/city taxes not modeled.
  • +
  • Payroll taxes: FICA is not included.
  • +
  • Education & loans: 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.
  • +
  • Income changes: Expected salary replaces current salary starting at/after the provided graduation date; other changes come from user‑defined milestones.
  • +
  • Contributions & withdrawals: Retirement/emergency contributions treated as post‑tax outflows. Retirement withdrawals treated as taxable ordinary income; no early‑withdrawal penalties/RMD rules modeled.
  • +
  • Returns: Retirement balances use the selected strategy (none / flat annual rate / random monthly range). No general inflation is applied unless you model it via milestones.
  • +
  • Data sources: Public datasets (e.g., BLS/NCES) may change without notice. COL adjustments not applied unless explicitly labeled.
  • +
+
+
+ ); +}