Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / frontend/src/screens/Compliance.tsx |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | frontend/src/screens/Compliance.tsx |
| Original SHA-256 | baec2f51a2b196f8dbe9f02430d367b4a83a49457fd598577e0da2e24d32fdb1 |
| Git blob SHA | 10dbbfff53ef52db108d1883f78c9c92a39d1269 |
| Reading-copy SHA-256 | baec2f51a2b196f8dbe9f02430d367b4a83a49457fd598577e0da2e24d32fdb1 |
Recovered source transcript
Download complete source text ↓265 lines · 11.3 KB
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 (
<div className="space-y-4">
<Panel className="flex flex-wrap items-center justify-between gap-3 p-4">
<div>
<div className="flex items-center gap-2 text-base font-bold">
<ShieldCheck size={18} style={{ color: 'var(--color-gold)' }} /> Adverse Impact and AEDT Compliance
</div>
<p className="mt-0.5 text-xs text-faint">
Four fifths analysis over recorded decisions. The platform supplies this data for an
independent audit run by the deploying employer.
</p>
</div>
<button
onClick={downloadExport}
disabled={!report.data}
className="flex items-center gap-2 rounded-lg border border-line/70 px-3 py-2 text-sm font-semibold text-muted hover:text-ink disabled:opacity-50"
>
<Download size={15} /> Export summary
</button>
</Panel>
{report.isLoading && <Panel><p className="text-sm text-muted">Loading the report.</p></Panel>}
{report.isError && (
<Panel>
<p className="text-sm text-warn">
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}
</p>
</Panel>
)}
{report.data && (
<>
<Panel>
<dl className="grid grid-cols-1 gap-2 text-sm sm:grid-cols-2">
<Meta k="Generated" v={report.data.generated_at} />
<Meta k="Data covered" v={`${report.data.data_from || 'not available'} to ${report.data.data_to || 'not available'}`} />
<Meta k="Source" v={report.data.source} wide />
<Meta k="Method" v={report.data.method} wide />
</dl>
</Panel>
<Section section={report.data.selection} />
<Section section={report.data.screening} />
</>
)}
{/* 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 />}
</div>
)
}
// 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<CurationProposalView[]>([])
const [verified, setVerified] = useState<Record<string, boolean>>({})
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 (
<Panel
title="Teaching curation proposals"
hint="Two step human gate. Approve stages an inert ontology edge. Verify, a separate action, activates it for future scores. Nothing is auto applied, and no already served score and no engine weight is ever changed."
>
{proposals.isError && (
<p className="text-sm text-warn">
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}
</p>
)}
{!proposals.isError && rows.length === 0 && (
<p className="text-sm text-faint">No pending curation proposals.</p>
)}
{rows.length > 0 && (
<table className="w-full text-sm">
<thead>
<tr className="text-left text-[11px] uppercase tracking-wider text-faint">
<th className="py-1 font-semibold">Kind</th>
<th className="py-1 font-semibold">Substitute skill</th>
<th className="py-1 font-semibold">For required skill</th>
<th className="py-1 text-right font-semibold">Strength</th>
<th className="py-1 text-right font-semibold">Evidence</th>
<th className="py-1 text-right font-semibold">Action</th>
</tr>
</thead>
<tbody>
{rows.map((p) => {
const isApproved = p.status === 'approved' || approvedIds.has(p.id)
const isVerified = verified[p.id]
return (
<tr key={p.id} className="border-t border-line/50">
<td className="py-1.5">{p.kind.replace(/_/g, ' ')}</td>
<td className="py-1.5 font-mono text-[11px] text-muted">{p.from_skill_id.slice(0, 8)}</td>
<td className="py-1.5 font-mono text-[11px] text-muted">{p.to_skill_id.slice(0, 8)}</td>
<td className="py-1.5 text-right tabular-nums">{p.proposed_strength || 'n a'}</td>
<td className="py-1.5 text-right tabular-nums text-muted">
{p.evidence_successes} of {p.evidence_observations}
</td>
<td className="py-1.5 text-right">
{isVerified ? (
<Badge color="var(--color-gold)" subtle>
<CheckCircle2 size={12} /> Verified, live
</Badge>
) : isApproved ? (
<button
onClick={() => onVerify(p.id)}
disabled={verify.isPending}
className="rounded-lg border border-line/70 px-3 py-1 text-xs font-semibold text-ink hover:bg-line/30 disabled:opacity-50"
>
Verify, the second step
</button>
) : (
<button
onClick={() => onApprove(p)}
disabled={approve.isPending}
className="rounded-lg border border-line/70 px-3 py-1 text-xs font-semibold text-muted hover:text-ink disabled:opacity-50"
>
Approve, stage inert
</button>
)}
</td>
</tr>
)
})}
</tbody>
</table>
)}
</Panel>
)
}
function Meta({ k, v, wide }: { k: string; v: string; wide?: boolean }) {
return (
<div className={wide ? 'sm:col-span-2' : ''}>
<dt className="text-[11px] font-bold uppercase tracking-wider text-muted">{k}</dt>
<dd className="text-ink">{v}</dd>
</div>
)
}
function Section({ section }: { section: AnalysisSection }) {
return (
<Panel
title={section.stage}
hint={`${section.total_n} analyzed, ${section.unknown_count} unknown category, ${section.no_demographics_count} with no consented demographics`}
>
<div className="space-y-4">
<Cut title="By sex" rows={section.by_gender} />
<Cut title="By race or ethnicity" rows={section.by_race_ethnicity} />
<Cut title="Intersectional (sex by race or ethnicity)" rows={section.intersectional} />
</div>
</Panel>
)
}
function Cut({ title, rows }: { title: string; rows: ImpactRow[] }) {
return (
<div>
<div className="mb-1.5 text-[11px] font-bold uppercase tracking-wider text-muted">{title}</div>
{rows.length === 0 ? (
<p className="text-sm text-faint">No data.</p>
) : (
<table className="w-full text-sm">
<thead>
<tr className="text-left text-[11px] uppercase tracking-wider text-faint">
<th className="py-1 font-semibold">Group</th>
<th className="py-1 text-right font-semibold">Selected</th>
<th className="py-1 text-right font-semibold">Rate</th>
<th className="py-1 text-right font-semibold">Impact ratio</th>
<th className="py-1 text-right font-semibold">Status</th>
</tr>
</thead>
<tbody>
{rows.map((r) => (
<tr key={r.group} className="border-t border-line/50">
<td className="py-1.5">{r.group}</td>
<td className="py-1.5 text-right tabular-nums text-muted">
{r.selected} of {r.considered}
</td>
<td className="py-1.5 text-right tabular-nums">{r.rate}</td>
<td
className="py-1.5 text-right tabular-nums font-semibold"
style={{ color: r.flagged ? 'var(--color-risk)' : flagRatio(r.impact_ratio) }}
>
{r.impact_ratio}
</td>
<td className="py-1.5 text-right">
{r.flagged ? (
<Badge color="var(--color-risk)" subtle>
<ShieldAlert size={12} /> Below 0.80
</Badge>
) : (
<span className="text-faint">ok</span>
)}
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
)
}