Add detection operator profiles
This commit is contained in:
@@ -10,6 +10,7 @@ import type {
|
||||
YoloPreflightResponse,
|
||||
} from '../../types'
|
||||
import type { DetectionCalibrationRunRow } from '../../hooks/useDetectionWorkflow'
|
||||
import { DETECTION_OPERATOR_PROFILES, type DetectionOperatorProfile } from './detectionProfiles'
|
||||
|
||||
interface CalibrationRow {
|
||||
analysisRunId: string
|
||||
@@ -80,6 +81,7 @@ interface DetectionLabProps {
|
||||
onSetCalibrationThresholdText: (value: string) => void
|
||||
onRunCalibration: () => void
|
||||
onOpenCalibrationEvidence: (qualityCheckId: string) => void
|
||||
onApplyOperatorProfile: (profile: DetectionOperatorProfile) => void
|
||||
}
|
||||
|
||||
export function DetectionLab({
|
||||
@@ -135,6 +137,7 @@ export function DetectionLab({
|
||||
onSetCalibrationThresholdText,
|
||||
onRunCalibration,
|
||||
onOpenCalibrationEvidence,
|
||||
onApplyOperatorProfile,
|
||||
}: DetectionLabProps): JSX.Element {
|
||||
const selectedDetectionModel = detectionModels.find((model) => model.model_id === selectedDetectionModelId) ?? null
|
||||
const selectedModelAsset = modelAssets.find((asset) => asset.model_asset_id === selectedModelAssetId) ?? null
|
||||
@@ -145,9 +148,6 @@ export function DetectionLab({
|
||||
const detectionModelUiRunnable = detectionModelReady && selectedDetectionModelId !== 'manual-fixture-detector'
|
||||
const detectionHasExplicitModelAsset =
|
||||
selectedDetectionModelId !== 'yolo-configured' || modelAssets.length === 0 || selectedModelAssetId.length > 0
|
||||
const benchmarkCandidateAsset = modelAssets.find(
|
||||
(asset) => asset.model_asset_id === 'geointel-building-yolov8s-hardneg160r4e50-pt',
|
||||
)
|
||||
const calibrationRows = buildCalibrationRows(detectionRuns, qualityChecks)
|
||||
const bestF1Candidate = bestCalibrationRow(calibrationRows, 'f1')
|
||||
const bestPrecisionCandidate = bestCalibrationRow(calibrationRows, 'precision')
|
||||
@@ -251,12 +251,62 @@ export function DetectionLab({
|
||||
{selectedModelAsset ? 'asset selected' : 'no explicit asset'}
|
||||
</span>
|
||||
</div>
|
||||
{benchmarkCandidateAsset ? (
|
||||
<div className="model-asset-guidance">
|
||||
<strong>Operator profiles</strong>
|
||||
<p>
|
||||
Candidate profiles apply a local model asset and confidence threshold only after an explicit click.
|
||||
Candidate only - not default-approved while the promotion recommendation remains none.
|
||||
</p>
|
||||
</div>
|
||||
<div className="operator-profile-grid" aria-label="Configured YOLO operator profiles">
|
||||
{DETECTION_OPERATOR_PROFILES.map((profile) => {
|
||||
const profileAsset = modelAssets.find((asset) => asset.model_asset_id === profile.modelAssetId)
|
||||
const profileSelected =
|
||||
selectedModelAssetId === profile.modelAssetId &&
|
||||
Math.abs(detectionConfidenceThreshold - profile.confidenceThreshold) < 0.0001
|
||||
return (
|
||||
<div
|
||||
className={profileSelected ? 'operator-profile-card operator-profile-card-selected' : 'operator-profile-card'}
|
||||
key={profile.id}
|
||||
>
|
||||
<div className="operator-profile-card-header">
|
||||
<strong>{profile.displayName}</strong>
|
||||
<span className={profile.defaultApproved ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
{profile.defaultApproved ? 'default-approved' : 'Candidate only - not default-approved'}
|
||||
</span>
|
||||
</div>
|
||||
<p>{profile.description}</p>
|
||||
<div className="operator-profile-metrics">
|
||||
<span>threshold {profile.confidenceThreshold.toFixed(2)}</span>
|
||||
<span>precision {profile.precision.toFixed(3)}</span>
|
||||
<span>recall {profile.recall.toFixed(3)}</span>
|
||||
<span>F1 {profile.f1.toFixed(3)}</span>
|
||||
<span>max background FP {profile.maxBackgroundDetections}</span>
|
||||
</div>
|
||||
<div className="entity-meta">
|
||||
<span>asset: {profile.modelAssetId}</span>
|
||||
<span>promotionRecommendation: {profile.promotionRecommendation}</span>
|
||||
<span>available: {profileAsset ? 'yes' : 'not mounted'}</span>
|
||||
</div>
|
||||
<p className="field-guidance">{profile.limitationMessage}</p>
|
||||
<button
|
||||
className="secondary-action"
|
||||
type="button"
|
||||
onClick={() => onApplyOperatorProfile(profile)}
|
||||
disabled={!profileAsset}
|
||||
>
|
||||
Apply profile
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{selectedModelAsset ? (
|
||||
<div className="model-asset-guidance">
|
||||
<strong>Current benchmark candidate</strong>
|
||||
<strong>Selected model asset status</strong>
|
||||
<p>
|
||||
{benchmarkCandidateAsset.display_name} is available for deliberate evaluation. Recommended starting threshold: 0.25.
|
||||
Keep it operator-selected until hard-negative false positives are reduced.
|
||||
{selectedModelAsset.display_name} is operator-selected. Keep local candidates inactive until persisted
|
||||
promotion evidence explicitly recommends default activation.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -469,7 +519,7 @@ export function DetectionLab({
|
||||
/>
|
||||
{selectedDetectionModelId === 'yolo-configured' ? (
|
||||
<span className="field-guidance">
|
||||
Recommended starting threshold: 0.25 for the current local YOLOv8s benchmark candidate.
|
||||
Use an operator profile for the current local YOLOv8s candidate, or enter a threshold manually for calibration.
|
||||
</span>
|
||||
) : null}
|
||||
</label>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
export interface DetectionOperatorProfile {
|
||||
id: string
|
||||
displayName: string
|
||||
modelAssetId: string
|
||||
confidenceThreshold: number
|
||||
defaultApproved: boolean
|
||||
promotionRecommendation: 'none' | 'promote_candidate'
|
||||
precision: number
|
||||
recall: number
|
||||
f1: number
|
||||
maxBackgroundDetections: number
|
||||
description: string
|
||||
limitationMessage: string
|
||||
}
|
||||
|
||||
export const DETECTION_OPERATOR_PROFILES: DetectionOperatorProfile[] = [
|
||||
{
|
||||
id: 'balanced-review',
|
||||
displayName: 'Balanced review',
|
||||
modelAssetId: 'geointel-building-yolov8s-aoi1024bg512r3e50-pt',
|
||||
confidenceThreshold: 0.15,
|
||||
defaultApproved: false,
|
||||
promotionRecommendation: 'none',
|
||||
precision: 0.636639,
|
||||
recall: 0.424258,
|
||||
f1: 0.5074022485589402,
|
||||
maxBackgroundDetections: 103,
|
||||
description: 'Best positive-AOI F1 profile for deliberate operator review of the inactive AOI1024 model asset.',
|
||||
limitationMessage:
|
||||
'Candidate only because false-positive pressure still blocks default promotion on the background/hard-negative gate.',
|
||||
},
|
||||
{
|
||||
id: 'conservative-review',
|
||||
displayName: 'Conservative review',
|
||||
modelAssetId: 'geointel-building-yolov8s-aoi1024bg512r3e50-pt',
|
||||
confidenceThreshold: 0.35,
|
||||
defaultApproved: false,
|
||||
promotionRecommendation: 'none',
|
||||
precision: 0.840006,
|
||||
recall: 0.202135,
|
||||
f1: 0.32086574003576274,
|
||||
maxBackgroundDetections: 55,
|
||||
description: 'Higher-precision profile for demos or review sessions where fewer false positives matter more than recall.',
|
||||
limitationMessage:
|
||||
'Candidate only because false-positive pressure remains visible; use it deliberately and inspect persisted QA evidence.',
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user