Independent, study-verified benchmarks and facility-calibrated financial projections for hospitals, imaging centers, and radiology groups. Built on evidence, not the vendor's ROI deck.
Independent by design
No vendor funding
No sponsored placement
No GPO referral fees
Methodology disclosed
Market problem
Why imaging technology decisions are hard to underwrite.
Medical imaging purchases are financially material and operationally sensitive. A scanner, AI platform, PACS/RIS module, workflow tool, or imaging software contract can affect throughput, staffing, reimbursement, denial rates, radiologist productivity, patient access, integration burden, and long-term capital planning.
01
ROI claims may overstate value.
A faster scan, shorter read time, or workflow improvement only creates financial value when demand, staffing, scheduling, reimbursement, and downstream capacity allow the facility to capture the gain.
02
Pricing is opaque.
Comparable buyers may pay different amounts for product configuration, implementation, annual support, integrations, service terms, add-ons, and enterprise licenses.
03
Performance is difficult to compare.
Products differ across real-world workflow fit, PACS/RIS/EHR compatibility, implementation burden, training needs, and site-specific value capture.
04
Evidence is fragmented.
Useful signals sit across clinical studies, regulatory materials, vendor documents, case studies, reimbursement data, buyer conversations, and public market sources.
Intelligence layers built around the buying decision.
The platform is structured to move from product claim to evidence-weighted, buyer-specific, procurement-ready economic view.
01
Evidence-weighted KPI benchmarks
Scan-time reduction, read-time impact, workflow efficiency, repeat-rate reduction, denial-rate impact, throughput potential, staffing impact, and implementation burden are normalized and weighted by evidence quality.
02
Facility-specific financial modeling
Operational gains are translated into downside, base-case, and upside scenarios only when a real monetization path exists.
03
Pricing and contract intelligence
Expected price ranges, pricing structures, hidden fees, implementation costs, support fees, integration costs, module bundling, and negotiation questions are organized into a procurement-ready view.
04
Integration and workflow risk scoring
PACS, RIS, EHR, modality infrastructure, reporting workflow, cybersecurity requirements, IT burden, training, implementation timeline, and disruption risk are evaluated before value is assumed.
05
Buyer-specific fit assessment
A hospital radiology department, outpatient imaging center, radiology group, and PE-backed platform do not evaluate value the same way. Facility context changes the answer.
06
Procurement-ready decision support
Outputs include product profiles, opportunity estimates, benchmark summaries, diligence checklists, pricing questions, vendor comparison tables, and finance-committee-ready summaries.
How the model works
From product claim to decision-grade economic view.
Imaging Benchmark does not treat time savings as money by default. Every estimate is routed through baseline, constraint, evidence, monetization, and realization logic.
01
Select the product or category.
Start with a scanner, AI platform, PACS/RIS module, workflow tool, or imaging software category.
KPI evidence grades, pricing provenance, integration risk, and monetization gates determine whether operational gains can become financial value.
04
Export a decision-ready view.
Use the output for vendor meetings, procurement review, capital committee, board discussion, or internal operating meetings.
Model discipline
What we will not assume.
×
No automatic monetization of time savings.
Time saved only matters financially when there is a capture mechanism.
×
No unweighted vendor claims.
A multi-site study, vendor case study, regulatory document, and buyer comment do not carry the same weight.
×
No ignored site-readiness costs.
DICOM conformance, middleware, interface builds, cybersecurity, training, and disruption costs stay in the model.
×
No paid influence on scoring.
Vendor participation, if allowed, must be labeled and cannot influence benchmarks, scoring, modeling, or editorial judgment.
CT Scanner Comparison
Narrow estimate ranges to your facility's operational reality
Complete a 5-minute facility profile to replace generic averages with implementation-adjusted ranges based on your revenue, technology stack, and demand levels. Produces committee-defensible projections.
Ranges implementation-adjusted to your facility operating profile
Your facility profile is partially complete. Estimate ranges below carry wider confidence intervals than necessary for committee-defensible outputs.
Data Sources, Methodology & Evidence Governance. Performance specifications sourced from manufacturer-published datasheets and FDA 510(k) summaries. Pricing ranges reflect list prices and peer-reported transaction data where available; actual negotiated prices vary by region, volume, and contract terms. Financial impact estimates are implementation-adjusted illustrative ranges derived from comparable-facility medians and user-supplied operating data. These are not guarantees of financial outcomes. Realization haircuts applied where operational dependencies are identified. Integration assessments based on vendor documentation and structured deployment reports. All claims carry evidence classification metadata. Data subject to change. See Methodology & Evidence Standards v2.1 for full disclosure.
Next step
Evaluate the economics before the vendor meeting.
Use Imaging Benchmark to pressure-test product claims, pricing assumptions, integration risk, and financial upside before committing capital or signing a contract.
Cookie Notice. This site uses essential cookies required for platform functionality. Optional analytics cookies help us understand usage patterns. No facility data is shared with third parties. See our Privacy Policy for details on data handling and retention.
same tab
return true;
}
// 2) Fall back to a hosted file URL from the registry (for products embedded later).
var url = PRODUCT_PROFILES[name];
if(url){ window.location.href = url; return true; }
return false;
}
function showPage(id){closeMobileMenu();document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));document.getElementById('page-'+id).classList.add('active');window.scrollTo(0,0);
const nl=document.getElementById('navLinks');
const ffLink=isLoggedIn&&!ffDone?'Facility Profile':'';
nl.innerHTML=navDesktopExtrasHTML();
if(id==='explorer')renderExplorer();
if(id==='comparison'){renderComparison();const fi=document.getElementById('ffIncentive');const fc=document.getElementById('ffCalibrated');const pb=document.getElementById('partialCalBanner');const ci=document.getElementById('ctxInterview');
if(fi)fi.style.display=currentTier>=1&&!ffDone&&getCalibrationPct()===0?'flex':'none';
if(fc)fc.style.display=currentTier>=1&&ffDone?'flex':'none';
if(pb){const pct=getCalibrationPct();pb.style.display=(currentTier>=1&&!ffDone&&pct>0)?'flex':'none';}
if(ci)ci.style.display=ffDone?'flex':'none';}
if(id==='fastfit'){checkSavedFF();renderFF();}}
function scrollToEl(id){document.getElementById(id)?.scrollIntoView({behavior:'smooth',block:'start'});}
function toggleMobileMenu(){document.getElementById('mobileMenu').classList.toggle('open');}
function closeMobileMenu(){document.getElementById('mobileMenu').classList.remove('open');}
function showToast(m){const t=document.getElementById('toast');t.textContent=m;t.classList.add('visible');setTimeout(()=>t.classList.remove('visible'),3000);}
/* Calibration badge removed */
function updateCalBadge(){}
/* ═══ P3b: Calibration percentage system ═══ */
function getCalibrationPct(){if(ffDone)return 100;if(!ffBilling)return 0;if(ffStep<=0)return 0;
/* TC: billing(0) → tc_modselect(1) → tc1(2) → tc2(3) → tc3(4) = 5 steps */
/* PC: billing(0) → pc1(1) → pc2(2) → pc3(3) = 4 steps */
/* Global: billing(0) → tc_modselect(1) → tc1(2) → tc2(3) → tc3(4) → pc1(5) → pc2(6) → pc3(7) = 8 steps */
const pctMap={
'TC':[0,25,35,55,72,88],
'PC':[0,35,55,72,88],
'Global':[0,15,25,35,45,55,68,80,92]};
const map=pctMap[ffBilling]||pctMap['TC'];
const idx=Math.min(ffStep,map.length-1);
return map[idx];}
function getNextCalFields(){const pct=getCalibrationPct();if(pct<=35)return'Complete volume and revenue baseline to narrow ranges.';if(pct<=55)return'Complete modality profiles to narrow ranges further.';if(pct<=72)return'Complete case mix distribution for more precise estimates.';if(pct<=88)return'Complete remaining fields for narrowest estimate ranges.';return'';}
function resumeFastFit(){showPage('fastfit');}
/* ═══ P3b: localStorage persistence ═══ */
function saveFFState(){try{localStorage.setItem('ib_ffStep',ffStep);localStorage.setItem('ib_ffBilling',ffBilling);localStorage.setItem('ib_ffData',JSON.stringify(ffData));localStorage.setItem('ib_ffModList',JSON.stringify(ffModList));localStorage.setItem('ib_ffSelectedMods',JSON.stringify(ffSelectedMods));localStorage.setItem('ib_ffDone',ffDone?'1':'0');localStorage.setItem('ib_ffRevPct',ffRevPct);if(window._regFacilityType)localStorage.setItem('ib_facilityType',window._regFacilityType);}catch(e){}}
function loadFFState(){try{const s=localStorage.getItem('ib_ffStep');if(s!==null){ffStep=parseInt(s)||0;ffBilling=localStorage.getItem('ib_ffBilling')||'';ffData=JSON.parse(localStorage.getItem('ib_ffData')||'{}');ffModList=JSON.parse(localStorage.getItem('ib_ffModList')||'[]');ffSelectedMods=JSON.parse(localStorage.getItem('ib_ffSelectedMods')||'[]');ffDone=localStorage.getItem('ib_ffDone')==='1';ffRevPct=parseInt(localStorage.getItem('ib_ffRevPct'))||0;const ft=localStorage.getItem('ib_facilityType');if(ft)window._regFacilityType=ft;return true;}return false;}catch(e){return false;}}
function checkSavedFF(){
const hasState=loadFFState();
if(ffDone){ffMode='dashboard';return;}
if(hasState&&ffBilling&&ffStep>0){ffMode='dashboard';return;}
/* First time or no real progress - show billing step */
ffMode='step';ffStep=0;}
function clearFFState(){try{['ib_ffStep','ib_ffBilling','ib_ffData','ib_ffModList','ib_ffSelectedMods','ib_ffDone','ib_ffRevPct','ib_facilityType'].forEach(k=>localStorage.removeItem(k));}catch(e){}}
/* ═══ SEARCH ═══ */
function handleSearch(q,sourceId){const r=document.getElementById(sourceId==='homeSearch'?'homeSearchResults':'searchResults');if(!r)return;if(q.length<2){r.classList.remove('visible');r.innerHTML='';return;}const allProducts=[];[...PC_CATS,...TC_CATS].forEach(cat=>{cat.products.forEach(p=>{allProducts.push({name:p.n,vendor:p.v,category:cat.label,catId:cat.id,isPC:!!PC_CATS.find(c=>c.id===cat.id)});});});const ql=q.toLowerCase();const matches=allProducts.filter(p=>p.name.toLowerCase().includes(ql)||p.vendor.toLowerCase().includes(ql)||p.category.toLowerCase().includes(ql)).slice(0,8);if(matches.length===0){r.innerHTML='
'+results.length+' product'+(results.length!==1?'s':'')+' found
'+results.map(m=>'
'+m.name+' '+m.vendor+''+m.category+'
').join('');}
/* ═══ REGISTRATION ═══ */
function openRegistration(){document.getElementById('regModal').classList.add('visible');document.getElementById('regStep1').style.display='block';document.getElementById('regStep2').style.display='none';document.getElementById('regConfirm').style.display='none';}
function checkEmailDomain(){const e=document.getElementById('regEmail').value.trim();const h=document.getElementById('emailHint');const p=['gmail.com','yahoo.com','hotmail.com','outlook.com','aol.com','icloud.com'];if(e.includes('@')){const d=e.split('@')[1]?.toLowerCase();if(p.some(x=>d===x)){h.style.color='var(--warm)';h.textContent='Personal email detected. Institutional email recommended for full access.';}else if(d){h.style.color='var(--green)';h.textContent='Institutional email recognized.';}}else{h.style.color='var(--light-muted)';h.textContent='Use your work email for full access to peer-specific benchmarks.';}}
async function goToStep2(){const e=document.getElementById('regEmail').value.trim();const p=document.getElementById('regPassword').value;const h=document.getElementById('emailHint');if(!e||!e.includes('@')){h.style.color='var(--red)';h.textContent='Please enter a valid email address.';return;}if(p.length<8){h.style.color='var(--red)';h.textContent='Password must be at least 8 characters.';return;}window._regEmail=e;window._regPw=p;
if(sbClient){h.style.color='var(--muted)';h.textContent='Checking your account…';const res=await sbSignIn(e,p);if(res&&!res.error&&res.data&&res.data.user){document.getElementById('regModal').classList.remove('visible');isLoggedIn=true;if(currentTier<1)currentTier=1;registeredEmail=e;await sbRestoreSession();updateNavAuth();showToast('Signed in');sbLogEvent('login',{});return;}}
document.getElementById('regStep1').style.display='none';document.getElementById('regStep2').style.display='block';}
const RI={radiologist:'Radiologist accounts include read-per-hour benchmarking, professional component modeling, and modality-specific productivity analysis.',csuite:'Finance and executive accounts include capital allocation modeling, committee-ready export formats, and 5-year realization projections.',it:'IT accounts include integration burden scoring, PACS/RIS compatibility matrices, and deployment timeline benchmarks.',other:'General accounts include full product comparison access, pricing intelligence, and implementation risk analysis.'};
const FI={hospital:'Hospital and health system accounts include pricing ranges drawn from comparable facilities.',outpatient:'Outpatient imaging center accounts include throughput benchmarks and utilization modeling from comparable sites.',practice:'Radiology group accounts include professional component benchmarking and per-read revenue modeling from comparable practices.'};
function updateCalibration(){const r=document.getElementById('regRole').value;const f=document.getElementById('regFacility').value;const ri=document.getElementById('roleImpact');if(r){ri.textContent=RI[r]||'';ri.style.color='var(--body)';}else ri.textContent='';const fi=document.getElementById('facilityImpact');if(f){fi.textContent=FI[f]||'';fi.style.color='var(--body)';}else fi.textContent='';const pv=document.getElementById('calibrationPreview');const dt=document.getElementById('calibrationDetails');if(r&&f){pv.style.display='block';const rl=document.getElementById('regRole').options[document.getElementById('regRole').selectedIndex].text;const fl=document.getElementById('regFacility').options[document.getElementById('regFacility').selectedIndex].text;dt.innerHTML='Role: '+rl+' Facility: '+fl;/* Store facility type for FastFit defaults */window._regFacilityType=f;}else pv.style.display='none';}
async function submitRegistration(){const e=document.getElementById('regEmail').value.trim();const r=document.getElementById('regRole').value;const f=document.getElementById('regFacility').value;const fn=document.getElementById('regFirstName').value.trim();const ln=document.getElementById('regLastName').value.trim();const co=document.getElementById('regCompany').value.trim();const nimp=document.getElementById('nameImpact');if(!fn||!ln||!co){if(nimp){nimp.style.color='var(--red)';nimp.textContent='Please enter your first name, last name, and organization.';}return;}if(!r){document.getElementById('roleImpact').textContent='Please select your role to continue.';document.getElementById('roleImpact').style.color='var(--red)';return;}if(!f){document.getElementById('facilityImpact').textContent='Please select your facility type to continue.';document.getElementById('facilityImpact').style.color='var(--red)';return;}
if(sbClient){const fi=document.getElementById('facilityImpact');const pw=window._regPw||document.getElementById('regPassword').value;fi.style.color='var(--muted)';fi.textContent='Creating your account…';const res=await sbSignUp(e,pw,{first_name:fn,last_name:ln,company:co,role:r,facility_type:f});if(res&&res.error){const msg=(res.error.message||'').toLowerCase();const isNet=res.error._network||msg.includes('failed to fetch')||msg.includes('network')||msg.includes('fetch')||msg.includes('load failed')||msg.includes('connect');if(msg.includes('already')||msg.includes('registered')||msg.includes('exists')){fi.style.color='var(--warm)';fi.textContent='An account with this email already exists. Use Log In instead.';return;}if(!isNet){fi.style.color='var(--red)';fi.textContent=res.error.message||'Could not create account.';return;}console.warn('Supabase unreachable; completing registration locally:',res.error.message);}else{try{await sbSaveProfileFields({role:r,facility_type:f});sbLogEvent('register',{role:r,facility:f});}catch(_){}}}
registeredEmail=e;window._regFacilityType=f;const rl=document.getElementById('regRole').options[document.getElementById('regRole').selectedIndex].text;const fl=document.getElementById('regFacility').options[document.getElementById('regFacility').selectedIndex].text;document.getElementById('regStep2').style.display='none';document.getElementById('regConfirm').style.display='block';document.getElementById('confirmEmailDisplay').textContent=e;document.getElementById('confirmCalibration').innerHTML='Name: '+fn+' '+ln+' Organization: '+co+' Role: '+rl+' Facility: '+fl;}
function resendEmail(){showToast('Verification resent to '+registeredEmail);}
function simulateVerification(){document.getElementById('regModal').classList.remove('visible');currentTier=1;isLoggedIn=true;/* Reset FastFit to clean state so user starts fresh */ffDone=false;ffStep=0;ffBilling='';ffData={};ffModList=[];ffSelectedMods=[];ffMode='step';clearFFState();updateNavAuth();showToast('Verified · full data access enabled');if(exCategory&&selectedProducts.length>=2){renderTierBtns();updateCompHeaders();showPage('comparison');}else{exScope=null;exCategory=null;exFocus=[];showPage('explorer');}}
document.getElementById('regModal').addEventListener('click',function(e){if(e.target===this)this.classList.remove('visible');});
/* ═══ EXPLORER ═══ */
/* P5: headline KPIs for product selection grid */
const productKpiMap={};(function buildKpiMap(){const throughputKpi={'Revolution Apex':'+14%','SOMATOM Force':'+22%','Aquilion ONE / PRISM':'+8%','Spectral CT 7500':'+11%','SOMATOM X.ceed':'+9%','Revolution Maxima':'+7%','Incisive CT':'+5%','Aquilion Serve SP':'+6%'};const defaultKpis=['+8%','+12%','+5%','+9%','+14%','+6%','+10%','+7%'];[...PC_CATS,...TC_CATS].forEach(cat=>{cat.products.forEach((p,i)=>{productKpiMap[p.n]=throughputKpi[p.n]||(defaultKpis[i%defaultKpis.length]+' throughput vs avg');});});})();
function renderExplorer(){const c=document.getElementById('explorerContent');let h='';
if(!exScope){h+=`
Efficiency range vs. category average (Source: manufacturer specifications & peer-reported data)
PERFORMANCE Source: manufacturer specifications
Key Advantage${keyAdv}
Throughput${throughput}
Dose Efficiency${dose}
PRICING Source: peer-reported transactions & list prices
List Price
${t>=1?listPrice:'Register to view'}
Peer-Reported Transactions
${peerPrices} ›
Est. Annual Financial Impact
${impact}
Illustrative estimate based on comparable-facility medians. Not a guarantee of results.
INTEGRATION Source: deployment data & vendor documentation
Compatibility${compat}
Timeline${timeline}
Installed Base${instBase}
`;});
if(t===0){h+=`
Pricing and financial data require registration
Register to view peer-reported pricing ranges. Complete a facility profile for estimate ranges specific to your operations.
`;}
container.innerHTML=h;
const fi=document.getElementById('ffIncentive');const fc=document.getElementById('ffCalibrated');const pb=document.getElementById('partialCalBanner');const ci=document.getElementById('ctxInterview');
if(fi)fi.style.display=(currentTier>=1&&!ffDone&&getCalibrationPct()===0)?'flex':'none';
if(fc)fc.style.display=(currentTier>=1&&ffDone)?'flex':'none';
if(pb){const pct=getCalibrationPct();pb.style.display=(currentTier>=1&&!ffDone&&pct>0)?'flex':'none';if(pct>0){const pctEl=document.getElementById('partialCalPct');if(pctEl)pctEl.textContent=pct+'%';const ptEl=document.getElementById('partialCalText');if(ptEl)ptEl.textContent='Your facility profile is '+pct+'% complete. Estimate ranges below are wider than necessary. Resume to narrow them.';}}
if(ci)ci.style.display=ffDone?'flex':'none';
document.getElementById('compSubline').innerHTML=t>=1?(ffDone?'Implementation-adjusted estimate ranges calibrated to your facility operating data.':'Evidence-classified performance metrics, peer-referenced pricing data, and integration burden assessments drawn from institutional benchmarks.'):'Evidence-classified performance metrics, peer-referenced pricing data, and integration burden assessments drawn from institutional benchmarks.';document.getElementById('actionsBar').style.display=t>=1?'flex':'none';}
function renderTierBtns(){}
function setTier(t){if(t>=1&¤tTier===0&&!registeredEmail){openRegistration();return;}if(t>=2&&!ffDone){showPage('fastfit');return;}currentTier=t;renderComparison();}
function setPriority(p){}
function toggleSec(k){const s=document.getElementById('sec_'+k);const ic=document.getElementById('icon_'+k);if(s){const v=s.style.display!=='none';s.style.display=v?'none':'';if(ic)ic.textContent=v?'▸':'▾';}}
function saveComparison(){if(!registeredEmail){openRegistration();return;}showToast('Comparison saved · access it anytime from your dashboard');savedComparisons.push({category:exCategory,products:[...selectedProducts],priority:currentPriority,date:new Date().toISOString()});}
function captureCurrentSystem(){const sel=document.getElementById('currentSysSelect');if(sel&&sel.value){showToast('Current system noted · comparative analysis available after profile completion');}}
function populateCurrentSystem(){const sel=document.getElementById('currentSysSelect');if(!sel)return;const cat=[...PC_CATS,...TC_CATS].find(c=>c.id===exCategory);if(!cat)return;let opts='';cat.products.forEach(p=>{opts+='';});opts+='';sel.innerHTML=opts;}
let savedComparisons=[];
/* ═══ FASTFIT ═══ */
let ffStep=0,ffBilling='',ffDone=false,ffModList=[],ffRevPct=0,ffData={};
let ffMode='dashboard';
/* Section completion checks */
function ffSectionDone(sec){
if(sec==='billing')return !!(ffBilling&&ffData.facilityType);
if(sec==='modalities')return ffSelectedMods.length>0;
if(sec==='volume')return !!(ffData.daysOpen&&ffData.monthlyRev);
if(sec==='profiles')return ffModList.length>0&&ffModList.every(m=>m.scanners&&m.scanTime&&m.daysPerWeek&&m.monthlyEnc);
if(sec==='casemix'){const keys=Object.keys(ffData).filter(k=>k.startsWith('cmv_'));return keys.length>0;}
if(sec==='reading')return !!(ffData.pcDays);
if(sec==='staffing')return !!(ffData.pcRev&&ffData.pcShifts&&ffData.pcReads);
if(sec==='readdist')return !!(ffData.pcrd_xray||ffData.pcrd_ct);
return false;}
function renderFFDashboard(){
const c=document.getElementById('ffContent');
const pct=getCalibrationPct();
const proj=calcProjectionRange();
const width=pct>=72?'Narrow':pct>=45?'Moderate':pct>=25?'Broad':'Wide';
const isTc=ffBilling==='TC'||ffBilling==='Global'||!ffBilling;
const isPc=ffBilling==='PC'||ffBilling==='Global';
function card(id,title,desc,done,stepTarget){
const status=done?'Complete':'Not started';
return`
${title}${status}
${desc}
`;}
let h=`
Complete the sections below in any order. Each section you finish narrows your projection ranges.
`;
/* Live projection indicator */
const projKnown=proj.known;
const projLowFmt=projKnown?formatDollar(proj.low):'Unknown';
const projHighFmt=projKnown?formatDollar(proj.high):'';
const projDisplay=projKnown?projLowFmt+' - '+projHighFmt:'Unknown: enter revenue data';
h+=`
Range precision
${width}
${pct}% of profile fields completed
`;
h+=`
`;
h+=card('billing','Facility & Billing','Facility type, billing model, and location.',ffSectionDone('billing'),0);
if(isTc||!ffBilling){
h+=card('modalities','Modality Inventory','Which modalities your facility operates.',ffSectionDone('modalities'),1);
h+=card('volume','Scanning Volume & Revenue','Days open and revenue baseline.',ffSectionDone('volume'),2);
h+=card('profiles','Operating Profiles','Scanner counts, scan times, and encounter volumes per modality.',ffSectionDone('profiles'),3);
h+=card('casemix','Exam Distribution','Volume and revenue distribution by case family.',false,4);}
if(isPc){
const pcOffset=isTc?5:1;
h+=card('reading','Reading Volume & Cases','Reading coverage days and case distribution.',ffSectionDone('reading'),pcOffset);
h+=card('staffing','Revenue & Staffing','Professional revenue, shifts, and reads per week.',ffSectionDone('staffing'),pcOffset+1);
h+=card('readdist','Read Distribution by Modality','How reads and revenue split across modalities.',ffSectionDone('readdist'),pcOffset+2);}
h+=`
`;
h+=`
`;
if(pct>=25)h+=``;
h+=`
`;
h+=`
More precise inputs produce narrower estimate ranges. Where unsure, use conservative figures.
`;
c.innerHTML='
'+h+'
';window.scrollTo(0,0);}
const ffModOpts=['CT','MRI','Ultrasound','X-Ray','Mammography','Nuclear / PET-CT'];
const tcFam={'CT':['Neuro/Head','Chest','Abdomen/Pelvis','MSK/Spine','Vascular (CTA/CTP)','Cardiac','Other CT'],'MRI':['Neuro/Brain','Spine','MSK','Abdomen/Pelvis','Breast','Cardiac/Vascular (MRA)','Other MRI'],'X-Ray':['Chest','Abdomen','Spine','MSK/Extremity','Pelvis/Hip','Head/Face','Other X-ray'],'Ultrasound':['Abdomen','Pelvis/OB-GYN','Vascular','Cardiac (Echo)','MSK/Soft Tissue','Small Parts','Other US'],'Mammography':['Screening','Diagnostic','Interventional/Guidance','Implant/Tech Views','Tomo/3D','Breast US Add-on','Other Mammo'],'Nuclear / PET-CT':['PET: Whole-body Oncology','PET: Head/Neck','PET: Neuro/Brain','NM/SPECT: Cardiac','NM/SPECT: Bone/MSK','NM/SPECT: Other','Other NM/PET']};
const states='Alabama,Alaska,Arizona,Arkansas,California,Colorado,Connecticut,Delaware,Florida,Georgia,Hawaii,Idaho,Illinois,Indiana,Iowa,Kansas,Kentucky,Louisiana,Maine,Maryland,Massachusetts,Michigan,Minnesota,Mississippi,Missouri,Montana,Nebraska,Nevada,New Hampshire,New Jersey,New Mexico,New York,North Carolina,North Dakota,Ohio,Oklahoma,Oregon,Pennsylvania,Rhode Island,South Carolina,South Dakota,Tennessee,Texas,Utah,Vermont,Virginia,Washington,West Virginia,Wisconsin,Wyoming'.split(',');
let ffSelectedMods=[];
const modDescriptions={'CT':'Computed tomography','MRI':'Magnetic resonance imaging','Ultrasound':'Diagnostic & point-of-care','X-Ray':'Digital radiography','Mammography':'2D and 3D breast imaging','Nuclear / PET-CT':'Molecular and hybrid imaging'};
const modDefaults={'CT':{scanners:1,scanTime:15,daysPerWeek:5,monthlyEnc:800,availMin:2400,usedMin:1800},'MRI':{scanners:1,scanTime:35,daysPerWeek:5,monthlyEnc:400,availMin:2400,usedMin:1920},'Ultrasound':{scanners:2,scanTime:25,daysPerWeek:5,monthlyEnc:600,availMin:4800,usedMin:3600},'X-Ray':{scanners:1,scanTime:8,daysPerWeek:5,monthlyEnc:1500,availMin:2400,usedMin:1200},'Mammography':{scanners:1,scanTime:15,daysPerWeek:5,monthlyEnc:500,availMin:2400,usedMin:1680},'Nuclear / PET-CT':{scanners:1,scanTime:30,daysPerWeek:5,monthlyEnc:200,availMin:2400,usedMin:1440}};
/* ═══ RANKING UI ═══ */
/* Converts a ranking array into percentage estimates using a power-law curve */
function rankToPcts(ranked,total){
if(ranked.length===0)return{};
const weights=ranked.map((_,i)=>Math.pow(0.6,i));
const wSum=weights.reduce((a,b)=>a+b,0);
const pcts={};
ranked.forEach((id,i)=>{pcts[id]=Math.round((weights[i]/wSum)*100);});
/* Ensure sum=100 */
const s=Object.values(pcts).reduce((a,b)=>a+b,0);
if(s!==100&&ranked.length>0){pcts[ranked[ranked.length-1]]+=(100-s);}
return pcts;
}
function buildRankBlock(id,label,hint,items,dataKey,onChange){
const rankKey='_rank_'+dataKey;
const ovKey='_pctov_'+dataKey;
const ranked=ffData[rankKey]||[];
if(!ffData[ovKey])ffData[ovKey]={};
const ov=ffData[ovKey];
const auto=rankToPcts(ranked,items.length);
const eff={};
ranked.forEach(rid=>{const o=ov[rid];eff[rid]=(o!==undefined&&o!=='')?parseInt(o):auto[rid];ffData[dataKey+'_'+rid]=eff[rid];});
let html='
';
return html;
}
function rankPctEdit(ev,dataKey,id,val){
if(ev&&ev.stopPropagation)ev.stopPropagation();
const ovKey='_pctov_'+dataKey;
if(!ffData[ovKey])ffData[ovKey]={};
let v=(''+val).trim();
if(v!==''){v=Math.min(100,Math.max(0,parseInt(v)||0));}
ffData[ovKey][id]=v;
if(v!=='')ffData[dataKey+'_'+id]=v;
saveFFState();
}
function toggleRank(dataKey,itemId){
const rankKey='_rank_'+dataKey;
if(!ffData[rankKey])ffData[rankKey]=[];
const arr=ffData[rankKey];
const idx=arr.indexOf(itemId);
if(idx>=0){arr.splice(idx);/* Remove this and everything after it */}
else{arr.push(itemId);}
/* Also write derived percentages into ffData for downstream use */
const pcts=rankToPcts(arr,10);
Object.keys(pcts).forEach(k=>{ffData[dataKey+'_'+k]=pcts[k];});
_ffNoScroll=true;
}
/* ═══ DOLLAR FORMATTING ═══ */
function formatDollar(val){
if(!val&&val!==0)return'';
const num=typeof val==='string'?parseInt(String(val).replace(/[^0-9.-]/g,'')):val;
if(isNaN(num))return val;
return'$'+num.toLocaleString('en-US');
}
function parseDollar(val){
if(!val)return 0;
return parseInt(String(val).replace(/[^0-9.-]/g,''))||0;
}
function autoDollarFormat(el){
const raw=el.value.replace(/[^0-9]/g,'');
if(raw){el.value='$'+parseInt(raw).toLocaleString('en-US');}
}
/* ═══ PROJECTION PREVIEW ═══ */
function getMonthlyRev(){
return parseDollar(ffData.monthlyRev);
}
function getSliderPct(){
return(ffRevPct||0)/100;
}
function getDemandMultiplier(){
/* Slightly increase range based on capacity/demand indicators */
let mult=1.0;
if(ffModList.length>0){
let totalEnc=0;let totalScanners=0;
ffModList.forEach(m=>{
totalEnc+=parseInt(m.monthlyEnc)||0;
totalScanners+=parseInt(m.scanners)||0;
});
if(totalEnc>3000)mult+=0.03;
if(totalEnc>5000)mult+=0.05;
if(totalScanners>4)mult+=0.02;
if(totalScanners>8)mult+=0.04;
/* Backlog indicators push range up */
ffModList.forEach(m=>{
const bp=parseInt(m.backlogPct)||0;
if(bp>30)mult+=0.01;
if(bp>60)mult+=0.02;
});
}
if(ffData.pcReads){const rds=parseInt(ffData.pcReads)||0;if(rds>2500)mult+=0.03;if(rds>4000)mult+=0.05;}
return mult;
}
function calcProjectionRange(){
const mr=getMonthlyRev();
if(!mr)return{low:null,high:null,known:false};
const sliderAdj=1+getSliderPct();
const demandMult=getDemandMultiplier();
const baseLow=Math.round(mr*0.07681*sliderAdj*demandMult);
const baseHigh=Math.round(mr*1.59871*sliderAdj*demandMult);
return{low:baseLow,high:baseHigh,known:true};
}
function projectionPreviewHTML(forceUnknown){
const proj=calcProjectionRange();
const pct=getCalibrationPct();
const width=pct>=72?'Narrow':pct>=45?'Moderate':pct>=25?'Broad':'Wide';
const widthColor=pct>=45?'var(--green)':'var(--warm)';
if(forceUnknown||!proj.known){
return'';
}
return'';
}
/* ═══ PROMINENT EXIT EARLY ═══ */
function exitEarlyHTML(){
const pct=getCalibrationPct();
return'';
return '
Save progress and view comparison now
Your comparison will use peer averages for remaining fields. You can return anytime to refine.
View Comparison →
';
}
/* P3a: removed tc_intro and pc_intro from steps */
/* ===================== Technology Baseline benchmark (per Methodology v1.4) ===================== */
var SCAN_MFRS=[['','Select manufacturer…'],['GE','GE HealthCare'],['Siemens','Siemens Healthineers'],['Philips','Philips'],['Canon','Canon Medical'],['Samsung','Samsung / NeuroLogica'],['Fujifilm','Fujifilm (incl. Hitachi)'],['Hologic','Hologic'],['Mindray','Mindray'],['UIH','United Imaging'],['Esaote','Esaote'],['Konica','Konica Minolta'],['Shimadzu','Shimadzu'],['Carestream','Carestream'],['Other','Other'],['Unknown','Unknown']];
var SCANNER_MODELS={
GE:{CT:['Revolution Apex Elite','Revolution Apex','Revolution CT','Revolution Maxima','Revolution Frontier','Revolution EVO','Revolution Ascend','Revolution ACTs','Discovery CT750 HD','Optima CT660','Optima CT540','BrightSpeed','LightSpeed VCT','LightSpeed Pro 16'],
MRI:['SIGNA Champion','SIGNA Hero','SIGNA Architect','SIGNA Premier','SIGNA Pioneer','SIGNA Artist','SIGNA Voyager','SIGNA Explorer','SIGNA Creator','Optima MR450w','Discovery MR750w','Discovery MR750','Signa HDxt'],
PET:['Omni Legend','Discovery MI','Discovery MI DR','Discovery IQ'],MG:['Senographe Pristina','Senographe Essential']},
Siemens:{CT:['SOMATOM Force','SOMATOM Drive','SOMATOM Definition Flash','SOMATOM Definition Edge','SOMATOM Definition AS','SOMATOM X.ceed','SOMATOM X.cite','SOMATOM Pro.Pulse','SOMATOM go.Top','SOMATOM go.All','SOMATOM go.Up','SOMATOM go.Now','SOMATOM Confidence RT Pro','SOMATOM Perspective','SOMATOM Emotion 16'],
MRI:['MAGNETOM Cima.X','MAGNETOM Terra.X','MAGNETOM Terra (7T)','MAGNETOM Vida','MAGNETOM Sola','MAGNETOM Lumina','MAGNETOM Altea','MAGNETOM Aera','MAGNETOM Skyra','MAGNETOM Sempra','MAGNETOM Amira','MAGNETOM Free.Max','MAGNETOM Free.Star','MAGNETOM Avanto'],
PET:['Biograph Vision Quadra','Biograph Vision','Biograph Horizon','Biograph mCT'],MG:['MAMMOMAT Revelation','MAMMOMAT Fusion','MAMMOMAT Inspiration']},
Philips:{CT:['Spectral CT 7500','CT 5300','IQon Spectral CT','Incisive CT','Ingenuity CT','Brilliance iCT','Brilliance CT 64','Access CT','MX 16-slice'],
MRI:['MR 7700','MR 5300','Ingenia Elition X','Ingenia Ambition X','Ingenia','Prodiva CX','Achieva','Multiva','Panorama HFO'],
PET:['Vereos Digital PET/CT','Ingenuity TF PET/CT'],MG:['MicroDose SI']},
Canon:{CT:['Aquilion ONE / INSIGHT Edition','Aquilion ONE GENESIS','Aquilion ONE PRISM','Aquilion Precision','Aquilion Prime SP','Aquilion Serve','Aquilion Lightning','Aquilion Exceed LB','Aquilion Start'],
MRI:['Vantage Galan 3T','Vantage Orian 1.5T','Vantage Fortian','Vantage Centurian','Vantage Titan 3T','Vantage Elan'],PET:['Cartesion Prime']},
Fujifilm:{CT:['SCENARIA View','SCENARIA','Supria'],MRI:['Echelon Synergy','Echelon Smart','Oasis (1.2T open)','Airis Vento','APERTO Lucent']},
Hologic:{MG:['3Dimensions','Selenia Dimensions','Genius AI / Clarity']},
Samsung:{CT:['BodyTom Elite','OmniTom Elite']},
UIH:{CT:['uCT 960+','uCT 780','uCT 768','uCT 530'],MRI:['uMR Omega','uMR 890','uMR 780','uMR 660'],PET:['uMI Panorama','uMI Vista','uMI 550']}
};
var MODALITY_FALLBACK=['CT','MRI','X-Ray','Ultrasound','PET/CT','Mammography','Nuclear Medicine','Other'];
function tcModalityKey(m){m=(m||'').toUpperCase();if(m.indexOf('PET')>=0)return 'PET';if(m.indexOf('MAMMO')>=0||m==='MG')return 'MG';if(m.indexOf('MR')>=0)return 'MRI';if(m.indexOf('CT')>=0)return 'CT';return '';}
function tcModels(mfr,modLabel){var k=tcModalityKey(modLabel);if(SCANNER_MODELS[mfr]&&k&&SCANNER_MODELS[mfr][k])return SCANNER_MODELS[mfr][k];return [];}
function tcReconOpts(modLabel){var k=tcModalityKey(modLabel);if(k==='MRI')return [['','Select…'],['dl','Deep-learning reconstruction'],['cs','Compressed-sensing acceleration'],['parallel','Parallel imaging only'],['conv','Conventional reconstruction'],['unknown','Unknown']];if(k==='CT')return [['','Select…'],['dl','Deep-learning reconstruction'],['adv_iter','Advanced iterative reconstruction'],['iter','Iterative reconstruction'],['fbp','Filtered back projection only'],['unknown','Unknown']];return [['','Select…'],['dl','Deep-learning / AI reconstruction'],['adv','Advanced iterative / modern'],['standard','Standard'],['basic','Basic / legacy'],['unknown','Unknown']];}
function tcBlankScanner(mod){return {modality:(mod||''),mfr:'',model:'',upgrade:'',recon:'',upgradeYear:'',instYear:'',cardiac:'',spectral:'',dose:'',detector:''};}
function tcEnsure(){if(!Array.isArray(ffData.scanners))ffData.scanners=[];if(ffData.scanners.length===0)ffData.scanners.push(tcBlankScanner(''));}
/* ---- Modality-conditional, tiered add-on definitions (same storage keys, modality-appropriate meaning) ---- */
function tcAddonDefs(modLabel){var k=tcModalityKey(modLabel);var YNU=[['','Select\u2026'],['yes','Yes'],['no','No'],['unknown','Unknown']];
if(k==='CT')return[
{key:'cardiac',label:'Cardiac package',opts:[['','Select\u2026'],['none','None'],['basic','Basic gating / calcium scoring'],['advanced','Advanced cardiac (single-beat / functional)'],['unknown','Unknown']]},
{key:'spectral',label:'Spectral capability',opts:[['','Select\u2026'],['none','None'],['dual','Dual-energy (dual-source / kV-switching)'],['photon','Photon-counting'],['unknown','Unknown']]},
{key:'dose',label:'Dose-reduction package',opts:[['','Select\u2026'],['none','None'],['iter','Iterative-recon dose package'],['dl','Deep-learning dose reduction'],['unknown','Unknown']]},
{key:'detector',label:'Detector upgrade since install',opts:YNU}];
if(k==='MRI')return[
{key:'cardiac',label:'Cardiac MR package',opts:[['','Select\u2026'],['none','None'],['basic','Basic cardiac sequences'],['advanced','Advanced CMR (perfusion / strain / 4D flow)'],['unknown','Unknown']]},
{key:'spectral',label:'Advanced sequence licenses',opts:[['','Select\u2026'],['none','None'],['standard','Standard advanced (spectroscopy / advanced DWI)'],['ai','AI-accelerated acquisition bundle'],['unknown','Unknown']]},
{key:'dose',label:'Acceleration package',opts:[['','Select\u2026'],['none','None'],['cs','Compressed sensing'],['dl','Deep-learning acceleration'],['unknown','Unknown']]},
{key:'detector',label:'Coil / gradient upgrade since install',opts:YNU}];
if(k==='PET')return[
{key:'cardiac',label:'Cardiac PET capability',opts:[['','Select\u2026'],['none','None'],['basic','Perfusion imaging'],['advanced','Quantitative flow (MBF / CFR)'],['unknown','Unknown']]},
{key:'spectral',label:'Detector technology',opts:[['','Select\u2026'],['analog','Analog PMT'],['digital','Digital SiPM'],['tof','Digital + time-of-flight'],['unknown','Unknown']]},
{key:'dose',label:'Low-count / fast-scan reconstruction',opts:[['','Select\u2026'],['none','None'],['iter','Advanced iterative'],['dl','AI low-count reconstruction'],['unknown','Unknown']]},
{key:'detector',label:'Detector upgrade since install',opts:YNU}];
if(k==='MG')return[
{key:'cardiac',label:'Tomosynthesis (DBT)',opts:[['','Select\u2026'],['none','2D only'],['dbt','DBT equipped'],['dbtai','DBT + AI reading support'],['unknown','Unknown']]},
{key:'spectral',label:'Contrast-enhanced mammography',opts:YNU},
{key:'dose',label:'Dose-optimization package',opts:YNU},
{key:'detector',label:'Detector upgrade since install',opts:YNU}];
return[
{key:'cardiac',label:'Specialty clinical package',opts:YNU},
{key:'spectral',label:'Advanced imaging modes',opts:YNU},
{key:'dose',label:'Dose / speed optimization package',opts:YNU},
{key:'detector',label:'Detector / hardware upgrade since install',opts:YNU}];}
function tcAddonNormalize(s){tcAddonDefs(s.modality).forEach(function(d){var codes=d.opts.map(function(o){return o[0];});if(s[d.key]&&codes.indexOf(String(s[d.key]))<0)s[d.key]='';});}
/* ---- Reconciliation against the scanner counts declared in the modality step ---- */
var TC_SEED_CAP=6;
function tcDeclaredCounts(){var d={};if(Array.isArray(ffModList))ffModList.forEach(function(m){var n=parseInt(m.scanners);if(m.modality&&n>=1)d[m.modality]=n;});return d;}
function tcProfiledCounts(){var p={};(ffData.scanners||[]).forEach(function(s){if(s.modality)p[s.modality]=(p[s.modality]||0)+1;});return p;}
function tcMinReq(mod){var d=tcDeclaredCounts()[mod];return d?Math.min(d,TC_SEED_CAP):0;}
function tcSeedFromDeclared(){tcEnsure();var d=tcDeclaredCounts();
var hadPlaceholder=(ffData.scanners.length===1&&!ffData.scanners[0].modality&&!ffData.scanners[0].mfr&&!ffData.scanners[0].model);
var p=tcProfiledCounts();var added=0;
Object.keys(d).forEach(function(mod){var need=Math.min(d[mod],TC_SEED_CAP);var have=p[mod]||0;for(var j=have;j0)ffData.scanners.splice(0,1);
if(ffData.scanners.length===0)ffData.scanners.push(tcBlankScanner(''));
ffData.scanners.forEach(tcAddonNormalize);}
function tcGoToCounts(){var i=getSteps().indexOf('tc2');if(i>=0){ffStep=i;saveFFState();renderFF();}}
function tcReconStripHTML(){var d=tcDeclaredCounts();var mods=Object.keys(d);if(!mods.length)return '';var p=tcProfiledCounts();var rows=mods.map(function(m){var dec=d[m],prof=p[m]||0,min=Math.min(dec,TC_SEED_CAP);var st;if(prof>dec)st=''+prof+' listed \u00b7 '+(prof-dec)+' above the declared count';else if(prof'+prof+' of '+min+' listed';else if(dec>TC_SEED_CAP)st=''+prof+' of '+dec+' listed (highest volume units)';else st=''+prof+' of '+dec+' listed';return m+': '+st;}).join(' \u00b7 ');
var note=mods.some(function(m){return d[m]>TC_SEED_CAP;})?' For fleets above '+TC_SEED_CAP+' units per modality, list the scanners that carry the most volume.':'';
return '
Fleet reconciliation'+rows+'.'+note+' Counts come from your modality profile. Edit the counts there if they have changed.
';}
function tcSet(i,k,v){tcEnsure();ffData.scanners[i][k]=v;saveFFState();updateBenchPanel('tc');}
function tcCardComplete(s){return !!(s&&s.modality&&s.mfr&&s.model&&s.upgrade);}
function tcFirstIncomplete(){for(var i=0;i<(ffData.scanners||[]).length;i++){if(!tcCardComplete(ffData.scanners[i]))return i;}return -1;}
function tcCardOpen(i){var m=window._tcOpen||{};if(Object.prototype.hasOwnProperty.call(m,i))return !!m[i];for(var k in m){if(m[k])return false;}return i===tcFirstIncomplete();}
function tcToggleCard(i){var cur=tcCardOpen(i);window._tcOpen={};window._tcOpen[i]=!cur;_ffNoScroll=true;renderFF();}
function tcNextUnit(i){window._tcOpen={};window._tcOpen[i]=false;var n=ffData.scanners.length;var j=-1;for(var k=i+1;k=0)window._tcOpen[j]=true;_ffNoScroll=true;renderFF();}
function tcToggleAddons(i){window._tcAddOpen=window._tcAddOpen||{};var s=ffData.scanners[i];var def=!!(s&&(s.cardiac||s.spectral||s.dose||s.detector));var cur=Object.prototype.hasOwnProperty.call(window._tcAddOpen,i)?!!window._tcAddOpen[i]:def;window._tcAddOpen[i]=!cur;_ffNoScroll=true;renderFF();}
function tcAddScannerFor(mod){tcEnsure();ffData.scanners.push(tcBlankScanner(mod));window._tcOpen={};window._tcOpen[ffData.scanners.length-1]=true;saveFFState();_ffNoScroll=true;renderFF();}
function tcSetR(i,k,v){tcEnsure();window._tcOpen=window._tcOpen||{};window._tcOpen[i]=true;var sc=ffData.scanners[i];sc[k]=v;if(k==='mfr'){var ml=tcModels(v,sc.modality);if(sc.model&&sc.model!=='other'&&sc.model!=='unknown'&&ml.indexOf(sc.model)<0)sc.model='';}if(k==='modality'){var ml2=tcModels(sc.mfr,v);if(sc.model&&sc.model!=='other'&&sc.model!=='unknown'&&ml2.indexOf(sc.model)<0)sc.model='';if(sc.recon&&!tcReconOpts(v).some(function(x){return x[0]===sc.recon;}))sc.recon='';tcAddonNormalize(sc);}saveFFState();_ffNoScroll=true;renderFF();}
function tcAddScanner(){tcEnsure();ffData.scanners.push(tcBlankScanner(''));window._tcOpen={};window._tcOpen[ffData.scanners.length-1]=true;saveFFState();_ffNoScroll=true;renderFF();}
function tcDelScanner(i){tcEnsure();var s=ffData.scanners[i];if(s&&s.modality){var cnt=tcProfiledCounts()[s.modality]||0;var min=tcMinReq(s.modality);if(cnt<=min&&min>0){showToast(s.modality+' is reconciled to your declared count. Adjust it in the modality step to remove this unit.');return;}}ffData.scanners.splice(i,1);window._tcOpen={};window._tcAddOpen={};if(ffData.scanners.length===0)ffData.scanners.push(tcBlankScanner(''));saveFFState();_ffNoScroll=true;renderFF();}
function tbOpts(items,cur){var o='';for(var k=0;k'+items[k][1]+'';}return o;}
/* ===================== Baseline calibration instrument =====================
Every input is scored: definitive = 1.0, Unknown = 0.35 (imputed from peer
medians), unanswered = 0. Weighted precision drives the width of the estimate
band this section contributes to the financial model, so the cost of a vague
answer is visible at the moment it is given. Evidence declarations classify
the baseline (evidence-referenced vs self-attested) and tighten the band. */
function bmScoreVal(v){if(v===''||v==null)return 0;if(String(v)==='unknown')return 0.35;return 1;}
function bmInventory(scope){var inv=[];
if(scope==='tc'){
inv.push([ffData.scannerAI,1]);inv.push([ffData.tcReplaceWindow,1]);
(ffData.scanners||[]).forEach(function(s){
inv.push([s.mfr,2]);inv.push([s.model,2]);
inv.push([s.upgrade,1.5]);
if(s.upgrade==='yes'||s.upgrade==='unknown')inv.push([s.recon,1.5]);
if(s.upgrade==='yes')inv.push([s.upgradeYear,1]);
if(s.model==='other'||s.model==='unknown')inv.push([s.instYear,1]);
tcAddonDefs(s.modality).forEach(function(d){inv.push([s[d.key],1]);});
});
}else{
inv.push([ffData.pcPacsFamily,2]);inv.push([ffData.pcPacsVer,2]);inv.push([ffData.pcDeploy,1.5]);inv.push([ffData.pcAIOrch,1.5]);
inv.push([(ffData.pcDeploy==='saas')?'completed':ffData.pcCloud,1]);
inv.push([ffData.pcVNA,1]);inv.push([ffData.pcRouting,1]);inv.push([ffData.pcRoadmap,1]);inv.push([ffData.pcContractEnd,1]);
inv.push([ffData.pcRIS,1.5]);inv.push([ffData.pcDictVendor,1.5]);inv.push([ffData.pcDictInt,1]);inv.push([ffData.pcWorklist,1]);
var ra=ffData.pcReadAIList||[];inv.push([ra.length===0?'':(ra.length===1&&ra[0]==='unknown'?'unknown':'set'),1.5]);
var ev=ffData.pcEvidenceList||[];inv.push([ev.length===0?'':(ev.indexOf('none')>=0?'unknown':'set'),1]);
}
return inv;}
function bmStats(scope){var inv=bmInventory(scope);var w=0,sc=0,nDef=0,nImp=0,nOpen=0;inv.forEach(function(x){var s=bmScoreVal(x[0]);w+=x[1];sc+=s*x[1];if(s===1)nDef++;else if(s>0)nImp++;else nOpen++;});var pct=w?Math.round(sc/w*100):0;
var ev=ffData.pcEvidenceList||[];var evid=ev.length>0&&ev.indexOf('none')<0;
var band=10+Math.round((100-pct)*0.35);if(scope==='pc'&&evid)band=Math.max(10,band-2);
return {pct:pct,band:band,nDef:nDef,nImp:nImp,nOpen:nOpen,evid:evid,evList:ev};}
function bmEvLabel(c){var m={contract:'contract / module list',arch:'architecture diagram',interface:'interface inventory',quote:'vendor quote',screenshot:'admin screenshot'};return m[c]||c;}
function updateBenchPanel(scope){var el=document.getElementById('ffxRail');if(el&&window._ffCurSid)el.innerHTML=ffRailHTML(window._ffCurSid);}
function renderTCBench(){
tcSeedFromDeclared();
window._tcOpen=window._tcOpen||{};window._tcAddOpen=window._tcAddOpen||{};
var acq=[['','Select\u2026'],['none','No acquisition-side AI'],['one','One tool in use'],['multi','Two or more tools in use'],['unknown','Unknown']];
var ynu=[['','Select\u2026'],['yes','Yes'],['no','No'],['unknown','Unknown']];
var uyr=[['','Select\u2026'],['0-1','0 to 1 years ago'],['2-3','2 to 3 years ago'],['4-5','4 to 5 years ago'],['6+','6+ years ago'],['unknown','Unknown']];
var age=[['','Select\u2026'],['0-1','0 to 1 years'],['2-3','2 to 3 years'],['4-5','4 to 5 years'],['6-7','6 to 7 years'],['8-9','8 to 9 years'],['10+','10+ years'],['unknown','Unknown']];
var rwin=[['','Select replacement window\u2026'],['lt12','Within 12 months'],['12-24','12 to 24 months'],['24-36','2 to 3 years'],['gt36','3+ years'],['none','No planned replacement'],['unknown','Unknown']];
var modList=(typeof ffSelectedMods!=='undefined'&&ffSelectedMods.length)?ffSelectedMods:MODALITY_FALLBACK;
var modItems=[['','Select modality\u2026']].concat(modList.map(function(m){return [m,m];}));
var totUnits=ffData.scanners.length;
var totDone=ffData.scanners.filter(tcCardComplete).length;
var h='
Add each scanner that carries meaningful volume. Model, upgrade state, and configuration decide which product claims apply to your fleet.
';
h+=tcReconStripHTML();
h+='
Scanner fleet'+totDone+' of '+totUnits+' configured
';
h+='
One unit is open at a time. Select any header to switch units.
';
function cardHTML(s,i,title){
var models=tcModels(s.mfr,s.modality);
var modelItems=[['','Select model\u2026']].concat(models.map(function(x){return [x,x];})).concat([['other','Other / not listed'],['unknown','Unknown']]);
var modelSel=s.mfr?(''):'';
var mfrName='';SCAN_MFRS.forEach(function(x){if(x[0]===s.mfr)mfrName=x[1];});
var modelName=(s.model==='other')?'Other model':(s.model==='unknown')?'Model unknown':(s.model||'');
var idBits=[mfrName,modelName].filter(function(x){return x;});
var subtitle=idBits.length?idBits.join(' \u00b7 '):'Awaiting configuration';
var complete=tcCardComplete(s);
var open=tcCardOpen(i);
var addSet=!!(s.cardiac||s.spectral||s.dose||s.detector);
var addOpen=Object.prototype.hasOwnProperty.call(window._tcAddOpen,i)?!!window._tcAddOpen[i]:addSet;
var c='
';
var anotherIncomplete=ffData.scanners.some(function(x,xi){return xi!==i&&!tcCardComplete(x);});
if(anotherIncomplete)c+='
Continue to next unit \u25b8
';
else c+='
Collapse this unit
';
c+='
';
return c;
}
var d=tcDeclaredCounts();
var groups=[];var seen={};
if(Array.isArray(ffModList))ffModList.forEach(function(m){if(m.modality&&!seen[m.modality]){seen[m.modality]=1;groups.push(m.modality);}});
ffData.scanners.forEach(function(s){if(s.modality&&!seen[s.modality]){seen[s.modality]=1;groups.push(s.modality);}});
groups.forEach(function(mod){
var idxs=[];ffData.scanners.forEach(function(s,i){if(s.modality===mod)idxs.push(i);});
if(!idxs.length)return;
var done=idxs.filter(function(i){return tcCardComplete(ffData.scanners[i]);}).length;
h+='
'+mod+' fleet'+done+' of '+idxs.length+' configured
';
idxs.forEach(function(si,gn){h+=cardHTML(ffData.scanners[si],si,mod+' Scanner '+(gn+1));});
var dec=d[mod]||0;
if(dec>idxs.length)h+='';
});
var un=[];ffData.scanners.forEach(function(s,i){if(!s.modality)un.push(i);});
if(un.length){h+='
Modality not setassign a modality to file these units
';}
var READ_AI_MULTI=[['none','None in use'],['aidoc','Aidoc'],['vizai','Viz.ai'],['rapidai','RapidAI'],['annalise','Annalise.ai'],['lunit','Lunit'],['qure','Qure.ai'],['gleamer','Gleamer'],['riverain','Riverain ClearRead'],['nuanceai','Nuance AI Marketplace'],['heartflow','HeartFlow'],['cleerly','Cleerly'],['icad','iCAD ProFound'],['transpara','ScreenPoint Transpara'],['other','Other'],['unknown','Unknown']];
var PC_EVID_MULTI=[['contract','Contract / module list'],['arch','Architecture diagram'],['interface','Interface inventory'],['quote','Vendor quote'],['screenshot','Admin screenshot'],['none','None available']];
var PC_CONTRACT=[['','Select term end\u2026'],['lt12','Within 12 months'],['12-24','12 to 24 months'],['24-36','2 to 3 years'],['gt36','3+ years'],['mtm','Month-to-month / lapsed'],['unknown','Unknown']];
/* ---- Conditional option builders: downstream choices adapt to upstream answers ---- */
var PACS_VER_BY={
sectra:[['legacy','IDS7 21.x or earlier (legacy)'],['prior','IDS7 22.x to 23.x (prior generation)'],['current','IDS7 24.x+ (current)'],['cloud','Sectra One Cloud']],
visage:[['legacy','Visage 7.1.x or earlier (legacy)'],['prior','Visage 7.2 to 7.5 (prior generation)'],['current','Visage 7.6+ (current)'],['cloud','Visage Cloud (AWS-hosted)']],
syngo:[['legacy','syngo Imaging / syngo.plaza (legacy)'],['prior','syngo.via (prior generation)'],['current','syngo Carbon (current)']],
synapse:[['legacy','Synapse 4.x or earlier (legacy)'],['prior','Synapse 5.x (prior generation)'],['current','Synapse 7.x (current)'],['cloud','Synapse Cloud']],
intelerad:[['legacy','IntelePACS pre-4.x (legacy)'],['prior','IntelePACS 4.x (prior generation)'],['current','IntelePACS current / Clario'],['cloud','Intelerad cloud-hosted']],
agfa:[['legacy','IMPAX 6.x (legacy)'],['prior','Enterprise Imaging 8.0 to 8.1 (prior)'],['current','Enterprise Imaging 8.2+ (current)'],['cloud','Enterprise Imaging cloud-managed']],
intellispace:[['legacy','iSite PACS (legacy)'],['prior','IntelliSpace PACS 4.4 (prior)'],['current','IntelliSpace 4.7+ / AI Manager (current)'],['cloud','Philips HealthSuite cloud']],
centricity:[['legacy','Centricity PACS (legacy)'],['prior','Universal Viewer 6.x (prior)'],['current','True PACS / current Universal Viewer']],
mckesson:[['legacy','Horizon Rad Station (legacy)'],['prior','Radiology 12.x (sunset)']],
merge:[['legacy','Merge PACS 7.x or earlier (legacy)'],['current','Merge PACS 8.x (current)'],['cloud','Merative cloud-hosted']],
carestream:[['legacy','Vue PACS 11.x (legacy)'],['prior','Vue PACS 12.1 (prior)'],['current','Vue PACS 12.2+ (current)']],
hyland:[['legacy','Acuo / NilRead earlier releases (legacy)'],['current','NilRead current release'],['cloud','Hyland cloud-hosted']],
ramsoft:[['legacy','PowerServer (on-prem product line)'],['cloud','OmegaAI (cloud-native)']],
paxera:[['prior','PaxeraCore (prior generation)'],['current','PaxeraUltima (current)'],['cloud','PaxeraHealth cloud']]
};
function pcVerOpts(fam){var base=PACS_VER_BY[fam];if(!base)return PACS_VER;return [['','Select version generation\u2026']].concat(base).concat([['unknown','Unknown']]);}
function pcDeployOpts(ver){
if(ver==='cloud')return [['','Select deployment model\u2026'],['saas','Vendor SaaS cloud'],['hybrid','Hybrid (cloud + local edge)'],['unknown','Unknown']];
if(ver==='legacy')return [['','Select deployment model\u2026'],['onprem','On-prem'],['private','Hosted private cloud'],['hybrid','Hybrid'],['unknown','Unknown']];
return PC_DEPLOY;}
function pcCloudOpts(dep){
if(dep==='onprem')return [['','Select cloud status\u2026'],['none','No plan'],['planned','Planned'],['progress','In progress'],['unknown','Unknown']];
if(dep==='hybrid')return [['','Select cloud status\u2026'],['planned','Planned'],['progress','In progress'],['completed','Completed (partial estate remains local)'],['unknown','Unknown']];
return PC_CLOUD;}
function pcOrchNativeOK(fam,ver){return (ver==='current'||ver==='cloud')&&fam!=='mckesson';}
function pcOrchOpts(fam,ver){if(pcOrchNativeOK(fam,ver))return PC_AIORCH;return PC_AIORCH.filter(function(x){return x[0]!=='native';});}
var PC_DICT_CONVENTIONAL=['ps360','mmodal','nvoq','augmedix'];
function pcDictIntOpts(vendor){if(PC_DICT_CONVENTIONAL.indexOf(vendor)>=0)return PC_DICTINT.filter(function(x){return x[0]!=='ainative';});return PC_DICTINT;}
/* Setter for fields that drive downstream questions: clears answers invalidated by the change, re-renders without scrolling */
function pcSetR(k,v){ffData[k]=v;
if(k==='pcPacsFamily'){ffData.pcPacsVer='';ffData.pcDeploy='';ffData.pcCloud='';if(ffData.pcAIOrch==='native')ffData.pcAIOrch='';var add=pcList('pcPacsAdditional');var j=add.indexOf(v);if(j>=0)add.splice(j,1);}
if(k==='pcPacsVer'){if(!pcDeployOpts(v).some(function(x){return x[0]===String(ffData.pcDeploy||'');}))ffData.pcDeploy='';if(ffData.pcAIOrch==='native'&&!pcOrchNativeOK(ffData.pcPacsFamily,v))ffData.pcAIOrch='';if(!ffData.pcDeploy)ffData.pcCloud='';}
if(k==='pcDeploy'){if(v==='saas'){ffData.pcCloud='completed';}else if(!pcCloudOpts(v).some(function(x){return x[0]===String(ffData.pcCloud||'');})){ffData.pcCloud='';}}
if(k==='pcDictVendor'){if(v==='none'){ffData.pcDictInt='manual';}else if(ffData.pcDictInt==='ainative'&&PC_DICT_CONVENTIONAL.indexOf(v)>=0){ffData.pcDictInt='';}}
saveFFState();_ffNoScroll=true;renderFF();}
function pcFG(req,label,key,items,rerender){var fn=rerender?'pcSetR':'pcSet';return '';}
function pcLocked(label,text){return '
'+text+'
';}
function pcNote(text){return '
'+text+'
';}
function pcWarn(text){return '
'+text+'
';}
function renderPCBench(){
pcMigrateLegacy();
var fam=ffData.pcPacsFamily||'',ver=ffData.pcPacsVer||'',dep=ffData.pcDeploy||'',dv=ffData.pcDictVendor||'';
var h='
Reading-side value is modeled against deployment state, not vendor name. Answer in order. Each answer tailors the next question to your platform, and inconsistent combinations are flagged before you continue.
';
h+='
PACS / viewer deployment state
';
h+=pcFG(true,'Primary PACS / viewer platform','pcPacsFamily',PACS_FAMILIES,true);
if(!fam){h+=pcNote('Select your PACS / viewer platform to unlock version, deployment, and configuration questions tailored to that platform.');}
else{
var addItems=PACS_FAMILIES.filter(function(x){return x[0]&&x[0]!==fam&&x[0]!=='unknown';});
h+='
'+pcChips('pcPacsAdditional',addItems,null)+'
Conditional questions below are keyed to the primary platform; additional platforms register estate complexity in the integration model.
';
if(ver==='cloud')h+=pcNote('Cloud-native edition selected. On-prem only deployment is not applicable and has been removed from the deployment options.');
if(ver==='legacy')h+=pcNote('Legacy generation selected. Vendor SaaS cloud is not offered for this generation and has been removed from the deployment options.');
if(ver&&dep){
var cloudCell=(dep==='saas')?pcLocked('Cloud migration status','Completed (set automatically for vendor SaaS deployments)'):pcFG(false,'Cloud migration status','pcCloud',pcCloudOpts(dep),false);
if(dep==='saas')ffData.pcCloud='completed';
h+='
';
if(!pcOrchNativeOK(fam,ver))h+=pcNote('\u201cNative PACS orchestration\u201d is hidden: '+(fam==='mckesson'?'this platform line is sunset and has no native AI orchestration.':'legacy / prior-generation platforms do not natively orchestrate AI. Upgrade-dependent capability.'));
h+='
';
var roadReq=(ver==='legacy'||fam==='mckesson');
h+='
'+pcFG(roadReq,'Active upgrade roadmap','pcRoadmap',PC_ROADMAP,false)+pcFG(false,'PACS contract / support term ends','pcContractEnd',PC_CONTRACT,false)+'
';
h+='
Roadmap and contract term time-weight the model: an upgrade-dependent capability is valued differently when the contract turns over inside the analysis window than when it is three years out.
';
if(roadReq)h+=pcWarn('Legacy or sunset platform generation detected. An upgrade roadmap answer is required so your baseline reflects planned state, not just installed state.');
h+='
Declaring at least one document type classifies this baseline as evidence-referenced and narrows its band (see readout above); these are the documents a verification pass would reference. \u201cNone available\u201d keeps the baseline self-attested with the widest peer-comparison bands.
';
}
}
h+='
Reporting, RIS & reading workflow
';
h+='
'+pcFG(true,'RIS / EHR platform','pcRIS',RIS_VENDORS,false)+pcFG(true,'Dictation / reporting vendor','pcDictVendor',DICT_VENDORS,true)+'
';
if(dv==='none'){ffData.pcDictInt='manual';h+=pcLocked('Dictation / reporting integration','None / manual (set automatically because no dictation vendor is in use)');}
else if(dv){h+=pcFG(true,'Dictation / reporting integration','pcDictInt',pcDictIntOpts(dv),false);if(PC_DICT_CONVENTIONAL.indexOf(dv)>=0)h+=pcNote('\u201cAI-native reporting\u201d is hidden: the selected dictation platform is a conventional speech system, not an AI-native reporting product.');}
else{h+=pcLocked('Dictation / reporting integration','Select a dictation / reporting vendor first. Integration options depend on the vendor');}
var raL=pcList('pcReadAIList');
h+='
';}
if(raL.indexOf('none')>=0&&['native','third','command'].indexOf(ffData.pcAIOrch)>=0)h+=pcWarn('Inconsistent: AI orchestration is set to a platform-level state, but reading-side AI is \u201cNone in use.\u201d Orchestration exists to route AI results. Adjust one of the two before continuing.');
return h;
}
function getSteps(){const tc=['billing','tc_modselect','tc1','tc2','tc_bench','tc3'];const pc=['billing','pc1','pc2','pc_bench','pc3'];if(ffBilling==='TC')return tc;if(ffBilling==='PC')return pc;return['billing','tc_modselect','tc1','tc2','tc_bench','tc3','pc1','pc2','pc_bench','pc3'];}
function stepLabel(){const s=getSteps()[ffStep];if(!s||s==='billing')return'FastFit Profile';if(s==='tc_modselect')return'TC: Technical Component';if(s.startsWith('tc'))return'TC: Technical Component';return'PC: Professional Component';}
function ffSectionBanner(){if(ffBilling!=='Global')return '';const s=getSteps()[ffStep];if(!s||s==='billing')return '';const isTC=(s==='tc_modselect'||s.indexOf('tc')===0);if(isTC)return '
Part 1 of 2 \u00b7 Technical Component (TC)
These questions cover the scanning side only, where your facility performs the scan. You will complete the reading (PC) side next.
';return '
Part 2 of 2 \u00b7 Professional Component (PC)
These questions cover the interpretation side only, where your physicians read and interpret studies.
';}
function ffShowErr(msg){let el=document.getElementById('ffError');if(!el){const c=document.getElementById('ffContent');if(!c)return;const div=document.createElement('div');div.id='ffError';div.className='ff-error visible';div.textContent=msg;const actions=c.querySelector('.ff-actions');if(actions)actions.parentNode.insertBefore(div,actions);else c.appendChild(div);}else{el.textContent=msg;el.classList.add('visible');}}
function pctWarn(total,label){if(Math.abs(total-100)>5){let el=document.getElementById('ffError');if(!el){const c=document.getElementById('ffContent');const div=document.createElement('div');div.id='ffError';div.className='ff-error visible';div.textContent=label+' must be within 95 to 105% of 100. Current total: '+Math.round(total)+'%.';const actions=c.querySelector('.ff-actions');if(actions)actions.parentNode.insertBefore(div,actions);else c.appendChild(div);}else{el.textContent=label+' must be within 95 to 105% of 100. Current total: '+Math.round(total)+'%.';el.classList.add('visible');}return false;}if(Math.abs(total-100)>0.5){return confirm(label+' total is '+Math.round(total)+'%. For best accuracy, adjust to 100%. Continue anyway?');}return true;}
function updateSlider(val){ffRevPct=val;const v=Math.round(val/2)*2;const zl=Math.abs(v)<=4?'Flat':v<=-10?'Significant decline':v<0?'Moderate decline':v<=10?'Moderate growth':'Aggressive growth';const zc=Math.abs(v)<=4?'var(--muted)':Math.abs(v)<=10?'var(--warm)':'var(--red)';const sv=document.getElementById('ffSliderColor');const zle=document.getElementById('ffZoneLabel');if(sv){sv.style.color=zc;sv.textContent=(v>=0?'+':'')+v+'%';}if(zle){zle.style.color=zc;zle.textContent=zl;}}
function updateSliderLive(val){
ffRevPct=val;
updateSlider(val);
updateLiveProjection();
}
function updateLiveProjection(){
/* Update the projection preview in-place without re-rendering the whole page */
const projBox=document.querySelector('.ff-projection-preview');
if(!projBox)return;
const proj=calcProjectionRange();
const pct=getCalibrationPct();
const width=pct>=72?'Narrow':pct>=45?'Moderate':pct>=25?'Broad':'Wide';
const widthColor=pct>=45?'var(--green)':'var(--warm)';
if(!proj.known){
projBox.innerHTML='';
}else{
projBox.innerHTML='';
}
}
function buildPctBlock(id,label,hint,whyText,items,dataKey,onChange){
const vals=items.map(it=>{const k=dataKey+'_'+it.id;return parseFloat(ffData[k])||it.def||0;});
const total=vals.reduce((a,b)=>a+b,0);
const remaining=Math.max(0,100-total);
const totalColor=Math.abs(total-100)<1?'var(--green)':total>100?'var(--red)':'var(--text)';
const barClass=Math.abs(total-100)<1?'done':total>100?'over':'';
let html=`
${label}
${Math.round(total)}% of 100%${remaining>0?' · '+Math.round(remaining)+'% remaining':''}
`;
return html;}
/* P3c: Case mix presets */
const caseMixPresets={
'ED-Heavy':{'CT':{'Neuro/Head':30,'Chest':20,'Abdomen/Pelvis':25,'MSK/Spine':5,'Vascular (CTA/CTP)':10,'Cardiac':5,'Other CT':5},'MRI':{'Neuro/Brain':35,'Spine':25,'MSK':15,'Abdomen/Pelvis':10,'Breast':5,'Cardiac/Vascular (MRA)':5,'Other MRI':5},'default':{0:30,1:25,2:20,3:10,4:5,5:5,6:5}},
'Outpatient-Dominant':{'CT':{'Neuro/Head':15,'Chest':10,'Abdomen/Pelvis':20,'MSK/Spine':25,'Vascular (CTA/CTP)':10,'Cardiac':15,'Other CT':5},'MRI':{'Neuro/Brain':15,'Spine':30,'MSK':25,'Abdomen/Pelvis':15,'Breast':5,'Cardiac/Vascular (MRA)':5,'Other MRI':5},'default':{0:15,1:15,2:20,3:25,4:10,5:10,6:5}},
'Balanced / Academic':{'CT':{'Neuro/Head':18,'Chest':15,'Abdomen/Pelvis':22,'MSK/Spine':12,'Vascular (CTA/CTP)':12,'Cardiac':14,'Other CT':7},'MRI':{'Neuro/Brain':20,'Spine':20,'MSK':20,'Abdomen/Pelvis':15,'Breast':10,'Cardiac/Vascular (MRA)':8,'Other MRI':7},'default':{0:18,1:18,2:18,3:15,4:12,5:12,6:7}}};
function applyCaseMixPreset(mod,presetName){const fams=tcFam[mod];if(!fams)return;const preset=caseMixPresets[presetName];if(!preset)return;const modData=preset[mod]||null;const modKey=mod.replace(/[^a-zA-Z0-9]/g,'_');fams.forEach((fam,i)=>{const famKey=fam.replace(/[^a-zA-Z0-9]/g,'_');let val;if(modData&&modData[fam]!==undefined){val=modData[fam];}else if(preset.default){val=preset.default[i]||Math.round(100/fams.length);}else{val=Math.round(100/fams.length);}ffData['cmv_'+modKey+'_'+famKey]=val;ffData['cmr_'+modKey+'_'+famKey]=val;});_ffNoScroll=true;renderFF();}
/* P3e: Accordion state for tc2 modality cards */
let expandedModIdx=0;
/* tc2 review model: benchmark defaults satisfy validation, so each modality must be
explicitly reviewed (edited or confirmed) before it counts as the user's own data. */
function tc2Required(m){return !!(m.scanners&&m.scanTime);}
function tc2FirstPending(){for(var i=0;i=0)window._tc2Open[j]=true;saveFFState();_ffNoScroll=true;renderFF();}
function tc2Confirm(i){ffModList[i].reviewed=true;window._tc2Ack=false;tc2NextMod(i);}
function tc2Touch(i){ffModList[i].reviewed=true;window._tc2Ack=false;saveFFState();tc2Pills();}
function tc2Remove(i){ffModList.splice(i,1);window._tc2Open={};window._tc2Ack=false;saveFFState();_ffNoScroll=true;renderFF();}
function tc2PillsHTML(){var rev=ffModList.filter(function(m){return m.reviewed&&tc2Required(m);}).length;var h='Modality review \u00b7 '+rev+' of '+ffModList.length+' reviewed
Open each modality and confirm or adjust it. The model should run on your numbers, not an average facility\u2019s.
';}
function tc2Pills(){var el=document.getElementById('tc2Pills');if(el)el.innerHTML=tc2PillsHTML();}
function tc2AckPanel(unrev,firstIdx){var old=document.getElementById('tc2Ack');if(old)old.parentNode.removeChild(old);var c=document.getElementById('ffContent');var div=document.createElement('div');div.id='tc2Ack';div.className='ffx-warn';div.style.padding='14px 16px';div.innerHTML='
'+unrev.length+' of '+ffModList.length+' modalities still use benchmark values
'+unrev.join(', ')+' have not been reviewed. You can continue, but those lines will run on industry averages instead of your facility, which lowers calibration.
';var actions=c.querySelector('.ff-actions');if(actions)actions.parentNode.insertBefore(div,actions);else c.appendChild(div);}
let ffCaseMixModIdx=0;
function toggleModAccordion(idx){var cur=tc2Open(idx);window._tc2Open={};window._tc2Open[idx]=!cur;_ffNoScroll=true;renderFF();}
/* Accuracy cue shown on each step */
function accuracyCue(){return'
More precise inputs produce narrower estimate ranges. Where unsure, use conservative figures.
`+
buildPctBlock('vol_'+mod,'Volume distribution','What share of your '+mod+' exams come from each family?','',volItems,'cmv_'+mod.replace(/[^a-zA-Z0-9]/g,'_'),'renderFF()')+
buildPctBlock('rev_'+mod,'Revenue distribution','What share of your '+mod+' revenue comes from each family?','',volItems,'cmr_'+mod.replace(/[^a-zA-Z0-9]/g,'_'),'renderFF()')+
`
`+navBottom;}
function addModality(){ffModList.push({modality:'',scanners:'',scanTime:'',daysPerWeek:'',availMin:'',usedMin:'',monthlyEnc:'',backlogPct:'',nextAppt:'',backfill:'',backlogAM:'',backlogPM:''});}
function applyModDefaults(idx){const m=ffModList[idx];const d=modDefaults[m.modality];if(!d)return;if(!m.scanners)m.scanners=d.scanners;if(!m.scanTime)m.scanTime=d.scanTime;if(!m.daysPerWeek)m.daysPerWeek=d.daysPerWeek;if(!m.monthlyEnc)m.monthlyEnc=d.monthlyEnc;if(!m.availMin)m.availMin=d.availMin;if(!m.usedMin)m.usedMin=d.usedMin;}
function toggleModSelect(mod){if(ffSelectedMods.includes(mod))ffSelectedMods=ffSelectedMods.filter(m=>m!==mod);else ffSelectedMods.push(mod);_ffNoScroll=true;renderFF();}
var FF_STEP_NAMES={billing:'Facility Profile',tc_modselect:'Modalities',tc1:'Volume & Revenue',tc2:'Operating Profile',tc_bench:'Scanner Fleet',tc3:'Case Mix',pc1:'Reading Volume',pc2:'Reading Economics',pc_bench:'Reading Platform',pc3:'Read Distribution'};
var FF_STEP_PURPOSE={billing:'Establishes the billing scope and geography the model calibrates against.',tc_modselect:'Defines which modalities the projection covers.',tc1:'Anchors the revenue base every projection is computed from.',tc2:'Sets throughput and capacity assumptions per modality.',tc_bench:'Determines which product claims apply to your installed base.',tc3:'Weights projected impact by your actual case mix.',pc1:'Sets the reading demand profile the model distributes work across.',pc2:'Anchors professional-side economics and capacity.',pc_bench:'Determines how reading-side products integrate with your deployed platform.',pc3:'Weights reading-side impact by where your volume and revenue sit.'};
function ffStepsStripHTML(steps,cur){var h='
'+(scope==='tc'
?'Financial impact depends on which scanners a product can actually affect. A product that reaches a third of your fleet is not modeled as if it reaches all of it. Model, upgrade state, and software configuration decide which performance claims apply.'
:'A reading-side product is modeled against your deployed platform generation, orchestration state, and reporting integration, not against a best case reference site.')+'
Unknown and unanswered inputs are imputed from peer medians, which widens the estimate band carried into every figure this section feeds.
';
return h;}
var pct=getCalibrationPct();
var h='
Projection readiness
Model calibration
'+pct+'%
Each completed section narrows the estimate band of your projection.
';
h+='
Why this matters
Every section narrows the estimate band of your projection. Definitive answers carry more weight than imputed ones.
';
return h;}
let _ffNoScroll=false;
function renderFF(){
if(_ffNoScroll)window._ffKeepY=window.scrollY;
/* Dashboard mode */
if(ffMode==='dashboard'){renderFFDashboard();return;}
const steps=getSteps();const total=steps.length;const c=document.getElementById('ffContent');const sid=steps[ffStep];window._ffCurSid=sid;let h='';
const back='';
const next='';
const finish='';
/* P3b: show calibration score after step 1 */
const calPct=getCalibrationPct();
const calScore=calPct>0?`
${calPct}%profile complete
`:'';
/* P3b: exit early as prominent block */
const exitEarly=exitEarlyHTML();
/* Peer median helper */
function peer(label){return'Peer median: '+label+'';}
/* Projection preview */
const projPreview=projectionPreviewHTML();
if(sid==='billing'){let savedFacility=window._regFacilityType||ffData.facilityType||'';if(['amc','community','idn','cah'].indexOf(savedFacility)>=0)savedFacility='hospital';if(savedFacility==='mobile')savedFacility='outpatient';h+=`
${stepLabel()}
These answers set the comparison group and billing scope your projection is calibrated against.
Technical Component (TC)
Your facility owns the scan: equipment, technologists, technical billing.
Professional Component (PC)
Your physicians interpret: reading, reporting, professional billing.
Select every modality your facility operates and schedules on site. X-Ray includes radiography and fluoroscopy; Nuclear / PET-CT includes gamma camera, SPECT, and PET.
Every modality below starts at industry benchmark values. Two required fields each; defaults cover the rest until you refine them.
${pills}${cardsHTML}
${back}${next}
${exitEarly}`;}
else if(sid==='tc_bench'){h+=`
${stepLabel()}
Scanner Fleet Configuration
`+renderTCBench()+`
${back}${next}
`;}
else if(sid==='tc3'){h+=projPreview+`
${stepLabel()}
Rank the case families from highest to lowest volume at your facility, or use a preset to start.
${renderCaseMix()}
${back}${(ffBilling==='TC'?finish:next)}
${exitEarly}`;}
else if(sid==='pc1'){
const ft=window._regFacilityType||ffData.facilityType||'hospital';
const csItems=[{id:'ed',label:'Emergency Department',desc:'STAT and urgent imaging reads'},{id:'ip',label:'Inpatient Hospital',desc:'Reads for admitted patients'},{id:'op',label:'Outpatient Diagnostic',desc:'Non-screening outpatient exams'},{id:'sc',label:'Screening',desc:'Preventive and protocol-driven imaging'},{id:'ot',label:'Other / Unknown',desc:'Reads not classified above'}];
const urItems=[{id:'stat',label:'STAT / ASAP',desc:'Interpreted within minutes'},{id:'urg',label:'Urgent Same Day',desc:'Required within hours'},{id:'rout',label:'Routine',desc:'Standard 24-48 hour turnaround'},{id:'scr',label:'Screening',desc:'Batch-processed or scheduled queue'}];
h+=projPreview+`
${stepLabel()}
Rank care settings and urgency levels from highest to lowest volume. Percentages are estimated from the order and can be refined later.
Include teleradiology and after hours coverage.
`;
h+=buildRankBlock('cs','Rank care settings by volume','Click each setting in order from highest volume to lowest. Optional: if skipped, a peer typical distribution for your facility type is applied.',csItems,'cs','_ffNoScroll=true;renderFF()');
h+=buildRankBlock('ur','Rank urgency levels by volume','Click each urgency level in order from most common to least common. Optional: if skipped, a peer typical distribution is applied.',urItems,'ur','_ffNoScroll=true;renderFF()');
h+=`
Rank your modalities from highest to lowest. Click each in order. We'll estimate the percentages.
`;
h+=buildRankBlock('pcReads','Rank modalities by read volume','Which modalities generate the most reads? Click highest-volume first.',pcModItems,'pcrd','_ffNoScroll=true;renderFF()');
h+=buildRankBlock('pcRev','Rank modalities by revenue','Which modalities generate the most revenue? Click highest-revenue first.',pcModItems,'pcrv','_ffNoScroll=true;renderFF()');
h+=`
${back}${finish}
${exitEarly}`;}
const shell='
Facility Calibration
'+ffStepsStripHTML(steps,ffStep)+'
Step '+(ffStep+1)+' of '+total+' \u00b7 '+(FF_STEP_NAMES[sid]||'')+'
'+ffSectionBanner()+'
'+(FF_STEP_PURPOSE[sid]?'
Why this step matters'+FF_STEP_PURPOSE[sid]+'
':'')+h+'
';
c.innerHTML=shell;if(_ffNoScroll){window.scrollTo(0,window._ffKeepY||window.scrollY);}else{window.scrollTo(0,0);}_ffNoScroll=false;window._ffKeepY=0;saveFFState();}
/* P3c: skip functions */
function skipCaseMix(){showToast('Using national averages. You can refine later for better precision');ffAdvance();}
function skipPcDistribution(){showToast('Using national averages. You can refine later for better precision');finishFastFit();}
/* P3b: Exit FastFit early with partial calibration */
function exitFFEarly(){saveFFState();currentTier=1;showPage('comparison');}
function ffAdvance(){const steps=getSteps();const sid=steps[ffStep];
function showFFError(msg){let el=document.getElementById('ffError');if(!el){const c=document.getElementById('ffContent');const div=document.createElement('div');div.id='ffError';div.className='ff-error visible';div.textContent=msg;const actions=c.querySelector('.ff-actions');if(actions)actions.parentNode.insertBefore(div,actions);else c.appendChild(div);return;}el.textContent=msg;el.classList.add('visible');}
function clearFFError(){const el=document.getElementById('ffError');if(el)el.classList.remove('visible');}
clearFFError();
if(sid==='billing'){if(!ffBilling){showFFError('Please select a billing model to continue.');return;}const ftSel=document.getElementById('ffFacilityType');if(ftSel&&!ftSel.value){showFFError('Please select your facility type.');return;}if(ftSel)ffData.facilityType=ftSel.value;const countryVal=(document.getElementById('ffCountry')?.value)||ffData.country||'US';ffData.country=countryVal;const zipVal=((document.getElementById('ffZip')?.value)||'').trim();if(!zipVal){showFFError('Please enter your zip code.');return;}if(countryVal==='US'&&!/^[0-9]{5}$/.test(zipVal)){showFFError('Please enter a valid 5 digit US zip code.');return;}ffData.zipCode=zipVal;
/* After billing, go to dashboard */
ffStep++;saveFFState();ffMode='dashboard';renderFF();return;}
if(sid==='tc_modselect'){if(ffSelectedMods.length===0){showFFError('Please select at least one modality your facility operates.');return;}ffModList=ffSelectedMods.map(mod=>{const existing=ffModList.find(m=>m.modality===mod);if(existing)return existing;const d=modDefaults[mod]||{};return{modality:mod,scanners:d.scanners||'',scanTime:d.scanTime||'',daysPerWeek:d.daysPerWeek||'',availMin:d.availMin||'',usedMin:d.usedMin||'',monthlyEnc:d.monthlyEnc||'',backlogPct:'',nextAppt:'',backfill:'',backlogAM:'',backlogPM:''};});}
if(sid==='tc1'){const d=document.getElementById('ffDaysOpen')?.value;const mr=document.getElementById('ffMonthlyRev')?.value;if(!d||!mr){showFFError('Please complete all fields before continuing.');return;}if(parseFloat(d)<1){showFFError('Days per year scans are performed must be at least 1.');return;}if(parseFloat(d)>366){showFFError('Days per year scans are performed cannot exceed 366.');return;}var _rev=parseDollar(mr);if(_rev>999999999){showFFError('Average monthly technical revenue looks too high. Please double check the amount.');return;}if(_rev>50000000&&!confirm('Monthly technical revenue over $50M is unusually high. Continue anyway?'))return;ffData.daysOpen=d;ffData.monthlyRev=mr;}
if(sid==='tc2'){if(ffModList.length===0){showFFError('Please configure at least one modality.');return;}let missing=false;ffModList.forEach(m=>{if(!m.modality||!m.scanners||!m.scanTime)missing=true;});if(missing){showFFError('Please enter scanners in service and average exam slot for every modality.');return;}let minErr='';ffModList.forEach(m=>{const av=(m.availMin===''||m.availMin==null)?null:parseFloat(m.availMin);const us=(m.usedMin===''||m.usedMin==null)?null:parseFloat(m.usedMin);if(av!=null&&av>10080)minErr=(m.modality||'A modality')+': Available scanner minutes per week cannot exceed 10,080.';else if(av!=null&&us!=null&&us>av)minErr=(m.modality||'A modality')+': Scanned minutes per week cannot exceed available minutes per week.';});if(minErr){showFFError(minErr);return;}let hardErr='';ffModList.forEach(m=>{const sc=parseInt(m.scanners),st=parseInt(m.scanTime);const nm=m.modality||'A modality';if(sc<1)hardErr=nm+': Scanners in service must be at least 1.';else if(sc>200)hardErr=nm+': Scanners in service above 200 is not supported. Please check.';else if(st<1)hardErr=nm+': Average exam slot must be at least 1 minute.';else if(st>480)hardErr=nm+': Average exam slot above 480 minutes (8 hours) is not supported. Please check.';});if(hardErr){showFFError(hardErr);return;}let warns=[];ffModList.forEach(m=>{const nm=m.modality||'modality';const sc=parseInt(m.scanners)||0,st=parseInt(m.scanTime)||0;if(sc>50)warns.push(nm+': '+sc+' scanners is unusually high.');if(st>120)warns.push(nm+': '+st+'-minute average exam slot is unusually long.');});if(warns.length&&!confirm('Some values look unusual:\n\n\u2022 '+warns.join('\n\u2022 ')+'\n\nContinue anyway?'))return;var _unrev=[];var _fi=-1;ffModList.forEach(function(m,mi){if(!m.reviewed){_unrev.push(m.modality||('Modality '+(mi+1)));if(_fi<0)_fi=mi;}});if(_unrev.length&&!window._tc2Ack){tc2AckPanel(_unrev,_fi);return;}var _oa=document.getElementById('tc2Ack');if(_oa)_oa.parentNode.removeChild(_oa);}if(sid==='pc1'){const d=document.getElementById('ffPCDays')?.value;if(!d){showFFError('Please enter the days per year with reading coverage.');return;}if(parseFloat(d)<1){showFFError('Reading coverage days must be at least 1.');return;}if(parseFloat(d)>366){showFFError('Reading coverage days cannot exceed 366.');return;}ffData.pcDays=d;/* Rankings are optional - if no ranking provided, use defaults */const ft=window._regFacilityType||ffData.facilityType||'hospital';const csDefaultsMap={amc:{ed:25,ip:32,op:28,sc:9,ot:6},community:{ed:25,ip:30,op:30,sc:10,ot:5},idn:{ed:25,ip:30,op:30,sc:10,ot:5},cah:{ed:30,ip:25,op:32,sc:8,ot:5},mobile:{ed:2,ip:3,op:65,sc:25,ot:5},hospital:{ed:25,ip:30,op:30,sc:10,ot:5},outpatient:{ed:2,ip:3,op:65,sc:25,ot:5},practice:{ed:5,ip:10,op:55,sc:22,ot:8}};const urDefaultsMap={amc:{stat:17,urg:26,rout:47,scr:10},community:{stat:15,urg:25,rout:50,scr:10},idn:{stat:15,urg:25,rout:50,scr:10},cah:{stat:12,urg:22,rout:54,scr:12},mobile:{stat:3,urg:10,rout:57,scr:30},hospital:{stat:15,urg:25,rout:50,scr:10},outpatient:{stat:3,urg:10,rout:57,scr:30},practice:{stat:5,urg:12,rout:55,scr:28}};const csDefs=csDefaultsMap[ft]||csDefaultsMap.hospital;const urDefs=urDefaultsMap[ft]||urDefaultsMap.hospital;if(!ffData['_rank_cs']||ffData['_rank_cs'].length===0){['ed','ip','op','sc','ot'].forEach(id=>{ffData['cs_'+id]=csDefs[id];});}if(!ffData['_rank_ur']||ffData['_rank_ur'].length===0){['stat','urg','rout','scr'].forEach(id=>{ffData['ur_'+id]=urDefs[id];});}}
if(sid==='pc2'){const rev=document.getElementById('ffPCRev')?.value;const sh=document.getElementById('ffPCShifts')?.value;const rd=document.getElementById('ffPCReads')?.value;if(!rev||!sh||!rd){showFFError('Please complete all fields before continuing.');return;}var _prev=parseDollar(rev);var _sh=parseInt(sh)||0;var _rd=parseInt(rd)||0;if(_prev>999999999){showFFError('Projected professional revenue looks too high. Please double check the amount.');return;}if(_sh>20000){showFFError('Radiologist clinical hours per week above 20,000 is not supported. Please check.');return;}if(_rd>200000){showFFError('Studies interpreted per week above 200,000 is not supported. Please check.');return;}var pw=[];if(_prev>50000000)pw.push('Professional revenue over $50M is unusually high.');if(_sh>5000)pw.push(_sh+' total radiologist hours per week is unusually high.');if(_sh>0&&_rd>0){var rps=_rd/_sh;if(rps<1||rps>60)pw.push('Studies per hour is about '+Math.round(rps)+' (studies divided by hours), outside the typical 1 to 60 range. Check both figures.');}if(pw.length&&!confirm('Some values look unusual:\n\n\u2022 '+pw.join('\n\u2022 ')+'\n\nContinue anyway?'))return;ffData.pcRev=rev;ffData.pcShifts=sh;ffData.pcReads=rd;}
if(sid==='tc_bench'){tcEnsure();var _bi=ffData.scanners.findIndex(function(s){return !s.modality||!s.mfr||!s.model;});if(_bi>=0){showFFError('Scanner '+(_bi+1)+': please select modality, manufacturer, and model (choose Unknown if unsure).');return;}var _dec=tcDeclaredCounts();var _prof=tcProfiledCounts();var _rb='';Object.keys(_dec).forEach(function(m){if(_rb)return;var dc=_dec[m],pf=_prof[m]||0,mn=Math.min(dc,TC_SEED_CAP);if(pf>dc)_rb=m+': '+pf+' scanners profiled but '+dc+' declared in your modality step. Remove '+(pf-dc)+' card'+((pf-dc)>1?'s':'')+' or update the declared count (link above the fleet).';else if(pf=0&&['native','third','command'].indexOf(ffData.pcAIOrch)>=0){showFFError('Inconsistent answers: AI orchestration is set to a platform-level state, but reading-side AI is \u201cNone in use.\u201d Select the tools you actually run, or set orchestration to \u201cNone\u201d / \u201cBasic result display.\u201d');return;}var _raTools=_ra.filter(function(x){return x!=='none'&&x!=='unknown';});if(_raTools.length>=2&&!ffData.pcReadAIPrimary){showFFError('You selected multiple reading-side AI tools. Please mark which one carries the highest read volume so they can be weighted in the model.');return;}}window._tc2Ack=false;ffStep++;saveFFState();renderFF();}
function finishFastFit(){const steps=getSteps();const sid=steps[ffStep];
if(sid==='tc3'){const activeMods=ffModList.filter(m=>m.modality&&tcFam[m.modality]).map(m=>m.modality);for(let mi=0;mi{const famKey=fam.replace(/[^a-zA-Z0-9]/g,'_');vT+=parseFloat(ffData['cmv_'+modKey+'_'+famKey])||0;rT+=parseFloat(ffData['cmr_'+modKey+'_'+famKey])||0;});if(Math.round(vT)!==100){ffShowErr(mod+' volume distribution must total exactly 100% (currently '+Math.round(vT)+'%).');return;}if(Math.round(rT)!==100){ffShowErr(mod+' revenue distribution must total exactly 100% (currently '+Math.round(rT)+'%).');return;}}}
if(sid==='pc3'){/* Rankings auto-derive percentages, no validation needed - just apply defaults if empty */
const pcMods=['xray','ct','mri','us','pet','mg','elast','other'];
const defaultPcts={xray:30,ct:25,mri:15,us:12,pet:3,mg:10,elast:2,other:3};
if(!ffData['_rank_pcrd']||ffData['_rank_pcrd'].length===0){pcMods.forEach(id=>{ffData['pcrd_'+id]=defaultPcts[id]||5;});}
if(!ffData['_rank_pcrv']||ffData['_rank_pcrv'].length===0){pcMods.forEach(id=>{ffData['pcrv_'+id]=defaultPcts[id]||5;});}}
ffDone=true;currentTier=2;saveFFState();sbSaveFacilityInputs();sbLogEvent('complete_registration',{});showCompletionSummary();}
/* P4: Compact completion screen, auto-redirect */
function showCompletionSummary(){const c=document.getElementById('ffContent');c.innerHTML=`
✓
Facility profile calibrated
Your estimate ranges now reflect implementation-adjusted operating data. Financial projections include realization haircuts for your facility context. Redirecting to your comparison...
`;
window.scrollTo(0,0);
setTimeout(()=>{skipToComparison();},2500);}
function skipToComparison(){showToast('Implementation-adjusted estimate ranges now active');showPage('comparison');}
renderMethodology();
function renderMethodology(){const el=document.getElementById('methSteps');if(!el)return;el.innerHTML=methSteps.map((s,i)=>`
${s.num}
${s.title}
`).join('');}
/* Load any saved FF state on page load */
function ffNegGuard(e){var el=e.target;if(!(el&&el.tagName==='INPUT'&&el.type==='number'))return;var raw=el.value;if(raw==='')return;if(/^[0-9]+$/.test(raw))return;var n=parseInt(raw,10);if(isNaN(n)){el.value='';return;}var mn=parseFloat(el.getAttribute('min'));if(n<0)n=(!isNaN(mn)&&mn>0)?mn:0;el.value=String(n);}
function ffKeyGuard(e){var el=e.target;if(el&&el.tagName==='INPUT'&&el.type==='number'){if(['e','E','+','-','.',','].indexOf(e.key)!==-1)e.preventDefault();}}
(function(){loadFFState();updateCalBadge();sbRestoreSession();loadSiteContent();var ffp=document.getElementById('page-fastfit');if(ffp){ffp.addEventListener('input',ffNegGuard,true);ffp.addEventListener('keydown',ffKeyGuard,true);}})();