Polish change detection density
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-20 20:37:46 +02:00
parent c941b2619a
commit 335d5d1869
7 changed files with 255 additions and 70 deletions
+2
View File
@@ -10,6 +10,8 @@ Map workspace now surfaces the selected AOI, active layer and rendered feature s
QA/QC Results now separates persisted check summary, candidate/reference evidence, refresh/filter controls and result history into focused surfaces. Existing filters, refresh behavior, metric cards and stored result rendering are unchanged, with denser mobile grids for the same controls.
Change Detection now follows the same analysis workspace hierarchy: vector input controls, error/empty states, result metrics and warnings are separated into focused surfaces while the existing compare action and map overlay result flow remain unchanged.
AI Labs uses the same mobile-density baseline for Detection and Segmentation model cards, run forms, QA/result summaries and scroll-safe result tables. This keeps long model ids, tile paths and artifact paths from widening the workbench on phones.
Exports and System provider capabilities now use compact, mobile-safe cards for handoff actions, artifact filters and provider metadata. Long provider limitations, export ids and artifact paths wrap inside their cards instead of widening the workbench.
@@ -37,8 +37,8 @@ export function ChangeDetectionPanel({
onRun,
}: ChangeDetectionPanelProps): JSX.Element {
return (
<section className="panel">
<div className="panel-header">
<section className="panel change-detection-shell">
<div className="panel-header change-detection-heading">
<div>
<p className="eyebrow">Analysis</p>
<h2>Change Detection</h2>
@@ -48,80 +48,99 @@ export function ChangeDetectionPanel({
</button>
</div>
<div className="form-grid">
<label>
Source vector
<select value={sourceDatasetId} onChange={(event) => onSourceDatasetChange(event.target.value)}>
<option value="">Select source</option>
{vectorDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{datasetLabel(dataset)}
</option>
))}
</select>
</label>
<label>
Target vector
<select value={targetDatasetId} onChange={(event) => onTargetDatasetChange(event.target.value)}>
<option value="">Select target</option>
{vectorDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{datasetLabel(dataset)}
</option>
))}
</select>
</label>
<label>
IoU threshold
<input
max="1"
min="0"
step="0.05"
type="number"
value={iouThreshold}
onChange={(event) => onIouThresholdChange(Number(event.target.value))}
/>
</label>
<label className="checkbox-row">
<input
checked={includeUnchanged}
type="checkbox"
onChange={(event) => onIncludeUnchangedChange(event.target.checked)}
/>
Include unchanged geometry
</label>
<div className="change-detection-input-surface" aria-label="Change detection input controls">
<div className="form-grid">
<label>
Source vector
<select value={sourceDatasetId} onChange={(event) => onSourceDatasetChange(event.target.value)}>
<option value="">Select source</option>
{vectorDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{datasetLabel(dataset)}
</option>
))}
</select>
</label>
<label>
Target vector
<select value={targetDatasetId} onChange={(event) => onTargetDatasetChange(event.target.value)}>
<option value="">Select target</option>
{vectorDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{datasetLabel(dataset)}
</option>
))}
</select>
</label>
<label>
IoU threshold
<input
max="1"
min="0"
step="0.05"
type="number"
value={iouThreshold}
onChange={(event) => onIouThresholdChange(Number(event.target.value))}
/>
</label>
<label className="checkbox-row">
<input
checked={includeUnchanged}
type="checkbox"
onChange={(event) => onIncludeUnchangedChange(event.target.checked)}
/>
Include unchanged geometry
</label>
</div>
</div>
{error ? <p className="error">{error}</p> : null}
{!error && vectorDatasets.length < 2 ? <p className="muted">Upload at least two vector datasets to compare.</p> : null}
<div className="change-detection-state-stack">
{error ? (
<div className="result-state result-state-error">
<strong>Change detection failed.</strong>
<p>{error}</p>
</div>
) : null}
{!error && vectorDatasets.length < 2 ? (
<div className="result-state result-state-empty">
<strong>Not enough vector datasets</strong>
<p>Upload at least two vector datasets to compare.</p>
</div>
) : null}
</div>
{result ? (
<div className="summary-grid">
<div>
<span className="metric">{result.added_count}</span>
<span>Added</span>
</div>
<div>
<span className="metric">{result.removed_count}</span>
<span>Removed</span>
</div>
<div>
<span className="metric">{result.unchanged_count}</span>
<span>Unchanged</span>
</div>
<div>
<span className="metric">{result.geojson.features.length}</span>
<span>Map features</span>
<div className="change-detection-result-surface" aria-label="Change detection result summary">
<div className="summary-grid">
<div>
<span className="metric">{result.added_count}</span>
<span>Added</span>
</div>
<div>
<span className="metric">{result.removed_count}</span>
<span>Removed</span>
</div>
<div>
<span className="metric">{result.unchanged_count}</span>
<span>Unchanged</span>
</div>
<div>
<span className="metric">{result.geojson.features.length}</span>
<span>Map features</span>
</div>
</div>
</div>
) : null}
{result?.warnings.length ? (
<ul className="compact-list">
{result.warnings.map((warning) => (
<li key={warning}>{warning}</li>
))}
</ul>
<div className="change-detection-warning-surface">
<strong>Warnings</strong>
<ul className="compact-list">
{result.warnings.map((warning) => (
<li key={warning}>{warning}</li>
))}
</ul>
</div>
) : null}
</section>
)
+73 -3
View File
@@ -1465,6 +1465,71 @@ button.entity-card {
margin: 0;
}
.change-detection-shell {
display: grid;
gap: 0.75rem;
}
.change-detection-heading {
margin-bottom: 0;
}
.change-detection-input-surface,
.change-detection-result-surface,
.change-detection-warning-surface {
min-width: 0;
border: 1px solid var(--line);
border-radius: 8px;
padding: 0.72rem;
background: #ffffff;
}
.change-detection-input-surface {
background: linear-gradient(180deg, #ffffff, #f7fbf8);
}
.change-detection-state-stack {
display: grid;
gap: 0.55rem;
}
.change-detection-shell .form-grid {
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
gap: 0.62rem;
margin: 0;
}
.change-detection-shell .form-grid label {
min-width: 0;
}
.change-detection-shell .form-grid input,
.change-detection-shell .form-grid select {
width: 100%;
min-width: 0;
}
.change-detection-shell .summary-grid {
grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
gap: 0.55rem;
margin: 0;
}
.change-detection-warning-surface {
border-color: rgba(180, 83, 9, 0.24);
background: rgba(180, 83, 9, 0.08);
}
.change-detection-warning-surface strong {
display: block;
color: #92400e;
font-size: 0.88rem;
}
.change-detection-warning-surface .compact-list {
margin-top: 0.45rem;
}
.quality-results-shell .quality-summary-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -2798,12 +2863,16 @@ button.entity-card {
.quality-summary-surface,
.quality-evidence-surface,
.quality-control-surface,
.quality-history-surface {
.quality-history-surface,
.change-detection-input-surface,
.change-detection-result-surface,
.change-detection-warning-surface {
padding: 0.58rem;
}
.quality-results-shell .quality-summary-grid,
.quality-results-shell .quality-history-controls {
.quality-results-shell .quality-history-controls,
.change-detection-shell .form-grid {
grid-template-columns: repeat(auto-fit, minmax(8.25rem, 1fr));
gap: 0.5rem;
}
@@ -2811,7 +2880,8 @@ button.entity-card {
.quality-results-shell .quality-handoff-grid,
.quality-results-shell .quality-score-row,
.quality-results-shell .quality-metric-grid,
.quality-results-shell .metric-list {
.quality-results-shell .metric-list,
.change-detection-shell .summary-grid {
grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
gap: 0.5rem;
}