*! Package confreg v. 1.0
*! Support: Niels Henrik Bruun, niels.henrik.bruun@gmail.com
*! 2025-11-24 Created

*TODO Add -1 for inconclusive in test. Report P(inconclusive|C+) and P(inconclusive|C-) ?
*TODO noSPec and noSEns ?

program define confreg, rclass
    version 15.1
	// varlist truevals testvals groups
	syntax varlist(min=2 max=3) [if], /*
	*/[ /*
		*/id(passthru) /*
		*/Randomeffects(passthru) /*
		*/ADJustment(varlist fv) /*
		*/Coleq(string) /*
		*/Prevalence(numlist max=1 >0 <1) /*
		*/vce(passthru) /*
		*/STub(string) /*
		*/SCale(real 100) /*
	*/]

	tokenize `"`varlist'"'
	local truevals `1'
	local tstvals `2'
	local msrmnt `3'
	return clear
	if "`msrmnt'" == "" {
		tempvar msrmnt
		generate `msrmnt' = 1
	}
	qui levelsof `truevals', local(zero_one)
	if "`zero_one'" != "0 1" mata: _error("True values (1st variable) is not zero-one")
	qui levelsof `tstvals', local(zero_one)
	if "`zero_one'" != "0 1" mata: _error("Test values (2nd variable) is not zero-one")

	_se_sp_auc `if', tst(`tstvals') trv(`truevals') tp(`msrmnt') `vce' `randomeffects' `id'
	matrix se_sp_auc = r(se_sp_auc)
	if "`coleq'" != "" matrix coleq se_sp_auc = "`coleq'"
	matrix se_sp_auc_corr = r(se_sp_auc_corr)
	matlist se_sp_auc, twidth(32)

	if "`prevalence'" == "" {
		su `truevals' `if', mean
		local prevalence `r(mean)'
	}
	_acc_ppv_npv `tstvals' `if', prev(`prevalence') tp(`msrmnt') stub(`stub')
	matrix acc_ppv_npv = r(acc_ppv_npv)
	if "`coleq'" != "" matrix coleq acc_ppv_npv = "`coleq'"

	matrix confreg = acc_ppv_npv[1,1...]
	qui levelsof `msrmnt' `if', local(lvls)
	local n = 1
	foreach lvl in `lvls' {
		matrix confreg = confreg ///
			\ se_sp_auc[`n'..`=`n'+2', 1...] ///
			/*\ se_sp_auc[`n'..`=`n'+4', 1...]*/ ///
			\ acc_ppv_npv[`=`n'+1'..`=`n'+3', 1...]
		local n = `n' + 3
	}

	return matrix se_sp_auc = se_sp_auc
	return matrix se_sp_auc_corr = se_sp_auc_corr
	return matrix acc_ppv_npv = acc_ppv_npv
	return matrix confreg = confreg
	drop _est*
end

program define _se_sp_auc, rclass
	syntax [if], tst(varname) trv(varname) tp(varname) ///
		[id(varname) Randomeffects(string) adj(varlist) vce(passthru) ///
		stub(string) scale(real 100)]

	local fnd
	capture matrix drop totals
	qui levelsof `tp' `if', local(lvls)
	local notfirst 0
	foreach lvl in `lvls' {
		if !`notfirst++' == 1 local rnms "`:label (`tp') `lvl'':Sensitivity, P(TP|C+)"
		else local rnms "`rnms'" "`:label (`tp') `lvl'':Sensitivity, P(TP|C+)"
		local fnd `fnd' ( se`lvl' : `' _b[1.`trv'#`lvl'.`tp'])
		local rnms "`rnms'" "`:label (`tp') `lvl'':Specificity, P(TN|C-)"
		local fnd `fnd' ( sp`lvl' : (1 - _b[0.`trv'#`lvl'.`tp']))
		local rnms "`rnms'" "`:label (`tp') `lvl'':AUC, (sens+spec)/2"
		local fnd `fnd' (auc`lvl' : 0.5 * (_b[1.`trv'#`lvl'.`tp'] + 1 - _b[0.`trv'#`lvl'.`tp']))
		*local rnms "`rnms'" "`:label (`tp') `lvl'':LR+, sens/(1-spec)"
		*local fnd `fnd' (LRp`lvl' : _b[1.`trv'#`lvl'.`tp'] / _b[0.`trv'#`lvl'.`tp'])
		*local rnms "`rnms'" "`:label (`tp') `lvl'':LR-, (1-sens)/spec"
		*local fnd `fnd' (LRm`lvl' : (1 - _b[1.`trv'#`lvl'.`tp']) / (1 - _b[0.`trv'#`lvl'.`tp']))
		if "`if'" == "" qui su `trv' if `tp' == `lvl'
		else qui su `trv' `if' & `tp' == `lvl'
		matrix totals = nullmat(totals) \ r(sum) \ r(N) - r(sum) \ r(N)
	}
	if "`id'" != "" local id ||`id':
	qui mixed `tst' i.`trv'##i.`tp' `adj' `if', `vce' `randomeffects' `id'
	*qui mepoisson `tst' i.`trv'##i.`tp' `adj' `if', `vce' `id'
	qui margins `trv'#`tp', post
	qui nlcom `fnd', post
	qui _coef_table
	matrix _tbl = r(table)
	matrix se_sp_auc = totals, `scale' * (_tbl["b", 1...]', _tbl["ll", 1...]', _tbl["ul", 1...]')
	matrix colnames se_sp_auc = N p [`c(level)'% CI]
	matrix rownames se_sp_auc = "`rnms'"
	estimates store `stub'_se_sp_auc
	return matrix se_sp_auc = se_sp_auc
	qui estat vce, correlation
	matrix C = r(V)
	mata: C = st_matrix("C")
	mata: dc = 1 :/ sqrt(diagonal(C))
	mata: newC = lowertriangle(C :* (dc # dc'))
	mata: newC = newC :* (1 :/ (newC :> 1e-4))
	mata: st_replacematrix("C", newC)
	matrix colnames C = "`rnms'"
	matrix rownames C = "`rnms'"
	return matrix se_sp_auc_corr = C
end

program define _acc_ppv_npv, rclass
	syntax varname [if], prev(numlist max=1 >0 <1) tp(varname) [stub(string) scale(real 100)]

	estimates restore `stub'_se_sp_auc

	capture matrix drop totals
	local fnd
	qui levelsof `tp' `if', local(lvls)
	local rnms ":Prevalence, C+/N"
	foreach lvl in `lvls' {
		local rnms "`rnms'" "`:label (`tp') `lvl'':Accuracy, P(TP + TN)"
		local fnd `fnd' (acc`lvl': _b[se`lvl'] * `prev' + _b[sp`lvl'] * (1 - `prev'))
		local rnms "`rnms'" "`:label (`tp') `lvl'':PPV, P(TP|P+)"
		local fnd `fnd' (ppv`lvl': _b[se`lvl'] * `prev' / (_b[se`lvl'] * `prev' + (1 - _b[sp`lvl']) * (1 - `prev')))
		local rnms "`rnms'" "`:label (`tp') `lvl'':NPV, P(TN|P-)"
		local fnd `fnd' (npv`lvl': _b[sp`lvl'] * (1 - `prev') / (_b[sp`lvl'] * (1 - `prev') + (1 - _b[se`lvl']) * `prev'))
		if "`if'" == "" qui su `varlist' if `tp' == `lvl'
		else qui su `varlist' `if' & `tp' == `lvl'
		matrix totals = nullmat(totals) \ r(sum) \ r(N) - r(sum) \ r(N)
	}
	qui nlcom `fnd', post
	qui _coef_table
	matrix _tbl = r(table)
	matrix acc_ppv_npv = totals, `scale' * (_tbl["b", 1...]', _tbl["ll", 1...]', _tbl["ul", 1...]')
	matrix acc_ppv_npv = (., `prev', ., .) \ acc_ppv_npv
	matrix colnames acc_ppv_npv = N p [`c(level)'% CI]
	matrix rownames acc_ppv_npv = "`rnms'"
	estimates store `stub'_acc_ppv_npv
	return matrix acc_ppv_npv = acc_ppv_npv
end