import { useState } from 'react'
import { CheckCircle2, Download, ShieldAlert, ShieldCheck } from 'lucide-react'
import {
useAdverseImpact,
useApproveCurationProposal,
useCurationProposals,
useVerifyCurationProposal,
} from '../lib/queries'
import { api } from '../lib/api'
import { Badge, Panel } from '../components/ui'
import { flagRatio } from '../lib/format'
import { useAuth } from '../lib/useAuth'
import type { AnalysisSection, CurationProposalView, ImpactRow } from '../lib/types'
export function Compliance() {
const report = useAdverseImpact()
const { principal } = useAuth()
const isOperator = principal?.role === 'operator'
async function downloadExport() {
const text = await api.adverseImpactExport()
const url = URL.createObjectURL(new Blob([text], { type: 'text/plain' }))
const a = document.createElement('a')
a.href = url
a.download = 'adverse_impact_report.txt'
a.click()
URL.revokeObjectURL(url)
}
return (
Adverse Impact and AEDT Compliance
Four fifths analysis over recorded decisions. The platform supplies this data for an
independent audit run by the deploying employer.
{report.isLoading &&
Loading the report.
}
{report.isError && (
The adverse impact report is unavailable. It is read only by the compliance role and requires
the compliance database connection. Detail: {(report.error as Error).message}
)}
{report.data && (
<>
>
)}
{/* Teaching curation is a PLATFORM surface: it writes the global skill ontology every tenant is
scored against, so amendment 6 moved it behind the operator plane and its endpoints now answer
404 to everyone else. Rendering it for a tenant is what the reported defect looked like, a panel
full of controls that refuse. The rule is that a platform surface is ABSENT for a tenant, not
inactive, so the whole panel is omitted rather than disabled. The 404 at the API is the
enforcement; this only stops the interface offering a journey that ends nowhere. */}
{isOperator && }
)
}
// CurationProposals surfaces the WS8 teaching curation proposals and the two step human in the loop gate.
// Approve is the first step and stages an inert ontology edge that changes no score. Verify is a second,
// distinct step that activates the edge for future scores. Nothing is ever auto applied, and neither step
// touches the engine math or any already served score.
function CurationProposals() {
const proposals = useCurationProposals()
const approve = useApproveCurationProposal()
const verify = useVerifyCurationProposal()
// verified tracks the proposal ids the user has just verified this session, so the row reads as activated
// immediately rather than only after the list refreshes. approvedThisSession lets a freshly approved
// proposal show the Verify action before the pending list drops it.
const [approvedThisSession, setApprovedThisSession] = useState([])
const [verified, setVerified] = useState>({})
async function onApprove(p: CurationProposalView) {
const res = await approve.mutateAsync(p.id)
setApprovedThisSession((prev) => [...prev.filter((x) => x.id !== p.id), { ...p, status: res.status }])
}
async function onVerify(id: string) {
await verify.mutateAsync(id)
setVerified((prev) => ({ ...prev, [id]: true }))
}
const pending = proposals.data ?? []
// Show the still pending proposals plus any approved this session that are not yet verified, so both steps
// are visible in one list without a page reload.
const approvedIds = new Set(approvedThisSession.map((p) => p.id))
const rows = [
...pending.filter((p) => !approvedIds.has(p.id)),
...approvedThisSession,
]
return (
{proposals.isError && (
The curation surface is unavailable. It is gated to the compliance or operator role and requires the
migrator database connection. Detail: {(proposals.error as Error).message}