Complete the V1 analysis handoff
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-17 02:52:15 +02:00
parent d9bc8fdbed
commit 03c6154cac
9 changed files with 240 additions and 14 deletions
+2
View File
@@ -1079,6 +1079,8 @@ function App(): JSX.Element {
onRunOrthophotoAnalysis={mapOrthophotoAnalysis.run}
onSelectOrthophotoProduct={mapOrthophotoAnalysis.setSelectedProductKey}
onClearQualityEvidence={clearQualityEvidenceGeoJson}
onOpenAssistant={() => setActiveWorkspace('assistant')}
onOpenExports={() => setActiveWorkspace('exports')}
/>
) : null}
+62 -11
View File
@@ -625,8 +625,8 @@ function copyText(text: string): void {
fallbackCopyText(text)
}
function downloadJsonFile(filename: string, payload: unknown): void {
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/geo+json' })
function downloadJsonFile(filename: string, payload: unknown, contentType = 'application/json'): void {
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: contentType })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
@@ -716,6 +716,8 @@ interface MapWorkspaceProps {
onRunOrthophotoAnalysis: (bbox: VectorSelectionBBox) => Promise<boolean>
onSelectOrthophotoProduct: (productKey: string) => void
onClearQualityEvidence?: () => void
onOpenAssistant: () => void
onOpenExports: () => void
}
export function MapWorkspace({
@@ -797,6 +799,8 @@ export function MapWorkspace({
onRunOrthophotoAnalysis,
onSelectOrthophotoProduct,
onClearQualityEvidence,
onOpenAssistant,
onOpenExports,
}: MapWorkspaceProps): JSX.Element {
const [advancedMode, setAdvancedMode] = useState(false)
const [activeThemeId, setActiveThemeId] = useState<DataThemeId>(() => {
@@ -1135,7 +1139,7 @@ export function MapWorkspace({
if (!selectedFeatureGeoJson) {
return
}
downloadJsonFile(selectedFeatureFilename, selectedFeatureGeoJson)
downloadJsonFile(selectedFeatureFilename, selectedFeatureGeoJson, 'application/geo+json')
}
const copySelectedMapFeatureProperties = () => {
@@ -1192,22 +1196,46 @@ export function MapWorkspace({
if (!mapSelectionResult) {
return
}
downloadJsonFile(DEFAULT_AREA_SELECTION_FILENAME, mapSelectionResult.geojson)
downloadJsonFile(DEFAULT_AREA_SELECTION_FILENAME, mapSelectionResult.geojson, 'application/geo+json')
}
const copyAreaSelection = () => {
copyText(JSON.stringify(mapSelectionResult?.geojson ?? { type: 'FeatureCollection', features: [] }, null, 2))
}
const downloadActiveThemeSelection = () => {
const downloadActiveThemeResult = () => {
if (!activeSelectionResult) {
return
}
downloadJsonFile(`${activeTheme.id}-selection.geojson`, activeSelectionResult.geojson)
if (activeThemeDataset?.dataset_type === 'raster') {
downloadJsonFile(`${activeTheme.id}-analysis.json`, {
project_id: selectedProjectId,
area_id: selectedMapArea?.id ?? null,
area_name: selectedMapArea?.name ?? null,
theme: activeTheme,
dataset_id: activeThemeDataset.id,
dataset_name: activeThemeDataset.name,
source_name: activeThemeDataset.source_name,
result: activeSelectionResult,
})
return
}
downloadJsonFile(`${activeTheme.id}-selection.geojson`, activeSelectionResult.geojson, 'application/geo+json')
}
const copyActiveThemeSelection = () => {
copyText(JSON.stringify(activeSelectionResult?.geojson ?? { type: 'FeatureCollection', features: [] }, null, 2))
const copyActiveThemeResult = () => {
copyText(JSON.stringify(activeSelectionResult ?? {}, null, 2))
}
const downloadTemporalComparison = () => {
if (!temporalComparison) {
return
}
downloadJsonFile(`${activeTheme.id}-evolution-${temporalComparison.earlier.observed_at.slice(0, 10)}-${temporalComparison.later.observed_at.slice(0, 10)}.json`, temporalComparison)
}
const copyTemporalComparison = () => {
copyText(JSON.stringify(temporalComparison ?? {}, null, 2))
}
const saveAreaSelectionExport = () => {
@@ -1888,7 +1916,10 @@ export function MapWorkspace({
<strong>{theme.label}</strong>
<small>{dataset ? getDatasetSourceDisplayName(dataset) : 'Geen bron gekoppeld'}</small>
</span>
<b>{item ? resultMetricLabel(item.result) : dataset ? 'Niet bevraagd' : 'Bron ontbreekt'}</b>
<span className="geo-theme-result-value">
<b>{item ? resultMetricLabel(item.result) : dataset ? 'Niet bevraagd' : 'Bron ontbreekt'}</b>
{item?.result.summary ? <small>{item.result.summary.metric_label}</small> : null}
</span>
</div>
)
})}
@@ -1930,8 +1961,28 @@ export function MapWorkspace({
{analysisMode === 'current' ? (
<div className="geo-result-actions">
<button className="secondary-action" disabled={!activeSelectionResult || activeThemeDataset?.dataset_type === 'raster'} type="button" onClick={downloadActiveThemeSelection}>Download GeoJSON</button>
<button className="secondary-action" disabled={!activeSelectionResult} type="button" onClick={copyActiveThemeSelection}>Kopieer gegevens</button>
<button className="secondary-action" disabled={!activeSelectionResult} type="button" onClick={downloadActiveThemeResult}>
{activeThemeDataset?.dataset_type === 'raster' ? 'Download analyse' : 'Download GeoJSON'}
</button>
<button className="secondary-action" disabled={!activeSelectionResult} type="button" onClick={copyActiveThemeResult}>Kopieer gegevens</button>
</div>
) : null}
{analysisMode === 'evolution' && temporalComparison ? (
<div className="geo-result-actions">
<button className="secondary-action" type="button" onClick={downloadTemporalComparison}>Download vergelijking</button>
<button className="secondary-action" type="button" onClick={copyTemporalComparison}>Kopieer vergelijking</button>
</div>
) : null}
{activeSelectionResult || temporalComparison ? (
<div className="geo-result-next-actions" aria-label="Volgende stap">
<span>
<strong>Analyse klaar</strong>
<small>Stel een vraag over dit gebied of open je bewaarde resultaten.</small>
</span>
<button className="primary-action" type="button" onClick={onOpenAssistant}>Stel AI-vraag</button>
<button className="secondary-action" type="button" onClick={onOpenExports}>Open downloads</button>
</div>
) : null}
</>
+36 -2
View File
@@ -6459,6 +6459,17 @@ section {
text-align: right;
}
.geo-theme-result-value {
display: grid;
justify-items: end;
min-width: 8.5rem;
text-align: right;
}
.geo-theme-result-value small {
max-width: 13rem;
}
.geo-data-notice {
margin: 0;
border-left: 3px solid #b17a31;
@@ -6542,6 +6553,27 @@ section {
margin-top: auto;
}
.geo-result-next-actions {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
gap: 0.45rem;
align-items: center;
border-top: 1px solid #dfe7e3;
padding-top: 0.65rem;
}
.geo-result-next-actions > span {
display: grid;
gap: 0.12rem;
min-width: 0;
}
.geo-result-next-actions small {
color: #687570;
font-size: 0.66rem;
line-height: 1.35;
}
.geo-explorer-footer {
display: flex;
flex-wrap: wrap;
@@ -6581,7 +6613,8 @@ section {
.geo-results-panel > .geo-panel-heading,
.geo-results-panel > .error,
.geo-results-panel > .geo-data-notice,
.geo-results-panel > .geo-result-actions {
.geo-results-panel > .geo-result-actions,
.geo-results-panel > .geo-result-next-actions {
grid-column: 1 / -1;
}
@@ -6661,7 +6694,8 @@ section {
}
.geo-map-actions,
.geo-result-actions {
.geo-result-actions,
.geo-result-next-actions {
display: grid;
grid-template-columns: 1fr;
}