feat(ui): refine governed workbench and dual-screen flows

This commit is contained in:
Jens
2026-08-30 06:00:28 +02:00
parent 80a2d1654d
commit a0884d64c9
48 changed files with 1360 additions and 184 deletions
+196 -47
View File
@@ -44,7 +44,9 @@ def register_fonts() -> tuple[str, str]:
FONT, FONT_BOLD = register_fonts()
def image_cover(c: canvas.Canvas, path: Path, x: float, y: float, w: float, h: float) -> None:
def image_cover(
c: canvas.Canvas, path: Path, x: float, y: float, w: float, h: float
) -> None:
with Image.open(path) as image:
iw, ih = image.size
scale = max(w / iw, h / ih)
@@ -53,7 +55,14 @@ def image_cover(c: canvas.Canvas, path: Path, x: float, y: float, w: float, h: f
clip = c.beginPath()
clip.rect(x, y, w, h)
c.clipPath(clip, stroke=0, fill=0)
c.drawImage(ImageReader(str(path)), x + (w - draw_w) / 2, y + (h - draw_h) / 2, draw_w, draw_h, mask="auto")
c.drawImage(
ImageReader(str(path)),
x + (w - draw_w) / 2,
y + (h - draw_h) / 2,
draw_w,
draw_h,
mask="auto",
)
c.restoreState()
@@ -75,19 +84,39 @@ def footer(c: canvas.Canvas, number: int) -> None:
c.line(42, 27, W - 42, 27)
c.setFillColor(MUTED)
c.setFont(FONT, 7)
c.drawString(42, 14, "GeoIntel - evidence-first GeoAI for Belgium and the Belgian North Sea")
c.drawString(
42, 14, "GeoIntel - evidence-first GeoAI for Belgium and the Belgian North Sea"
)
c.drawRightString(W - 42, 14, f"{number:02d}")
def paragraph(c: canvas.Canvas, text: str, x: float, y: float, w: float, size: float = 10, color=INK, leading: float | None = None) -> float:
style = ParagraphStyle("body", fontName=FONT, fontSize=size, leading=leading or size * 1.45, textColor=color, alignment=TA_LEFT)
def paragraph(
c: canvas.Canvas,
text: str,
x: float,
y: float,
w: float,
size: float = 10,
color=INK,
leading: float | None = None,
) -> float:
style = ParagraphStyle(
"body",
fontName=FONT,
fontSize=size,
leading=leading or size * 1.45,
textColor=color,
alignment=TA_LEFT,
)
p = Paragraph(text, style)
_, ph = p.wrap(w, H)
p.drawOn(c, x, y - ph)
return y - ph
def metric(c: canvas.Canvas, x: float, y: float, value: str, label: str, width: float) -> None:
def metric(
c: canvas.Canvas, x: float, y: float, value: str, label: str, width: float
) -> None:
c.setFillColor(PALE)
c.roundRect(x, y, width, 55, 9, fill=1, stroke=0)
c.setFillColor(TEAL)
@@ -112,7 +141,16 @@ def draw_cover(c: canvas.Canvas) -> None:
c.drawString(48, H - 171, "Van kaartlaag naar")
c.setFillColor(MINT)
c.drawString(48, H - 198, "aantoonbaar inzicht.")
paragraph(c, "Een kaartgerichte GeoAI-workbench waarin officiële bronnen, GIS, PyTorch en kwaliteitsbewijs één reproduceerbare keten vormen.", 48, H - 228, 315, 11, HexColor("#c6ddd7"), 17)
paragraph(
c,
"Een kaartgerichte GeoAI-workbench waarin officiële bronnen, GIS, PyTorch en kwaliteitsbewijs één reproduceerbare keten vormen.",
48,
H - 228,
315,
11,
HexColor("#c6ddd7"),
17,
)
c.setFillColor(HexColor("#173b36"))
c.roundRect(48, 47, 290, 48, 9, fill=1, stroke=0)
c.setFillColor(white)
@@ -124,27 +162,56 @@ def draw_cover(c: canvas.Canvas) -> None:
def draw_challenge(c: canvas.Canvas) -> None:
c.setFillColor(white); c.rect(0, 0, W, H, fill=1, stroke=0)
c.setFillColor(white)
c.rect(0, 0, W, H, fill=1, stroke=0)
title(c, "01 / Productvraag", "Niet alleen zien, maar kunnen verdedigen")
y = paragraph(c, "Geoportalen tonen vaak lagen en losse metingen. GeoIntel maakt van bron, gebied, analyse en bewijs één operationele workflow.", 42, H - 128, 330, 13, INK, 19)
y = paragraph(
c,
"Geoportalen tonen vaak lagen en losse metingen. GeoIntel maakt van bron, gebied, analyse en bewijs één operationele workflow.",
42,
H - 128,
330,
13,
INK,
19,
)
cards = [
("01", "Context behouden", "Autoriteit, meetmoment, CRS en dekking blijven zichtbaar."),
(
"01",
"Context behouden",
"Autoriteit, meetmoment, CRS en dekking blijven zichtbaar.",
),
("02", "AI begrenzen", "Modelversie, confidence en validatiescope reizen mee."),
("03", "Kwaliteit bewijzen", "Objectbewijs en fouten zijn vóór export inspecteerbaar."),
(
"03",
"Kwaliteit bewijzen",
"Objectbewijs en fouten zijn vóór export inspecteerbaar.",
),
]
for index, (num, head, body) in enumerate(cards):
cy = y - 42 - index * 78
c.setFillColor(PALE); c.roundRect(42, cy - 54, 330, 64, 10, fill=1, stroke=0)
c.setFillColor(TEAL); c.setFont(FONT_BOLD, 8); c.drawString(56, cy - 6, num)
c.setFillColor(INK); c.setFont(FONT_BOLD, 11); c.drawString(86, cy - 7, head)
c.setFillColor(PALE)
c.roundRect(42, cy - 54, 330, 64, 10, fill=1, stroke=0)
c.setFillColor(TEAL)
c.setFont(FONT_BOLD, 8)
c.drawString(56, cy - 6, num)
c.setFillColor(INK)
c.setFont(FONT_BOLD, 11)
c.drawString(86, cy - 7, head)
paragraph(c, body, 86, cy - 18, 266, 8.2, MUTED, 12)
image_cover(c, ASSETS / "geointel-landing-hero.png", 410, 53, 385, 415)
footer(c, 2)
def draw_experience(c: canvas.Canvas) -> None:
c.setFillColor(HexColor("#f5faf8")); c.rect(0, 0, W, H, fill=1, stroke=0)
title(c, "02 / Experience design", "De kaart blijft het werkblad", "Rustige hiërarchie, zichtbare herkomst en bewijs op het moment dat het nodig is.")
c.setFillColor(HexColor("#f5faf8"))
c.rect(0, 0, W, H, fill=1, stroke=0)
title(
c,
"02 / Experience design",
"De kaart blijft het werkblad",
"Rustige hiërarchie, zichtbare herkomst en bewijs op het moment dat het nodig is.",
)
image_cover(c, ASSETS / "geointel-workbench-map.png", 42, 73, 485, 360)
image_cover(c, ASSETS / "geointel-workbench-quality.png", 547, 73, 248, 360)
metric(c, 42, 440, "1", "ruimtelijke context", 160)
@@ -154,10 +221,21 @@ def draw_experience(c: canvas.Canvas) -> None:
def draw_ai(c: canvas.Canvas) -> None:
c.setFillColor(INK); c.rect(0, 0, W, H, fill=1, stroke=0)
c.setFillColor(MINT); c.setFont(FONT_BOLD, 8); c.drawString(42, H - 44, "03 / PYTORCH + NVIDIA")
c.setFillColor(white); c.setFont(FONT_BOLD, 25); c.drawString(42, H - 78, "Van bronpixel naar gecontroleerde detectie")
c.setFillColor(HexColor("#b8d4cd")); c.setFont(FONT, 9); c.drawString(42, H - 99, "Elke schakel bewaart de context die nodig is om het resultaat opnieuw te beoordelen.")
c.setFillColor(INK)
c.rect(0, 0, W, H, fill=1, stroke=0)
c.setFillColor(MINT)
c.setFont(FONT_BOLD, 8)
c.drawString(42, H - 44, "03 / PYTORCH + NVIDIA")
c.setFillColor(white)
c.setFont(FONT_BOLD, 25)
c.drawString(42, H - 78, "Van bronpixel naar gecontroleerde detectie")
c.setFillColor(HexColor("#b8d4cd"))
c.setFont(FONT, 9)
c.drawString(
42,
H - 99,
"Elke schakel bewaart de context die nodig is om het resultaat opnieuw te beoordelen.",
)
stages = [
("01", "Orthofoto", "CRS + dekking"),
("02", "Beeldtegels", "overlap + tile-id"),
@@ -166,20 +244,40 @@ def draw_ai(c: canvas.Canvas) -> None:
("05", "QA-bewijs", "precision + IoU"),
]
x0, gap, bw = 42, 13, 139
c.setStrokeColor(HexColor("#4bb99f")); c.setLineWidth(2); c.line(95, 285, 746, 285)
c.setStrokeColor(HexColor("#4bb99f"))
c.setLineWidth(2)
c.line(95, 285, 746, 285)
for i, (num, head, body) in enumerate(stages):
x = x0 + i * (bw + gap)
c.setFillColor(HexColor("#12443d")); c.roundRect(x, 205, bw, 160, 12, fill=1, stroke=0)
c.setFillColor(MINT); c.setFont(FONT_BOLD, 8); c.drawString(x + 14, 340, num)
c.setFillColor(white); c.setFont(FONT_BOLD, 10); c.drawString(x + 14, 295, head)
c.setFillColor(HexColor("#9fc9bf")); c.setFont(FONT, 7.4); c.drawString(x + 14, 275, body)
c.setFillColor(MINT if i < 3 else AMBER); c.circle(x + bw / 2, 230, 6, fill=1, stroke=0)
paragraph(c, "Fail-closed runtime: wanneer CUDA vereist maar niet beschikbaar is, of wanneer een lokaal model ontbreekt, toont GeoIntel een expliciete niet-geconfigureerde toestand. Productie-AI wordt nooit gesimuleerd.", 42, 166, 735, 11, HexColor("#d7ebe6"), 17)
c.setFillColor(HexColor("#12443d"))
c.roundRect(x, 205, bw, 160, 12, fill=1, stroke=0)
c.setFillColor(MINT)
c.setFont(FONT_BOLD, 8)
c.drawString(x + 14, 340, num)
c.setFillColor(white)
c.setFont(FONT_BOLD, 10)
c.drawString(x + 14, 295, head)
c.setFillColor(HexColor("#9fc9bf"))
c.setFont(FONT, 7.4)
c.drawString(x + 14, 275, body)
c.setFillColor(MINT if i < 3 else AMBER)
c.circle(x + bw / 2, 230, 6, fill=1, stroke=0)
paragraph(
c,
"Fail-closed runtime: wanneer CUDA vereist maar niet beschikbaar is, of wanneer een lokaal model ontbreekt, toont GeoIntel een expliciete niet-geconfigureerde toestand. Productie-AI wordt nooit gesimuleerd.",
42,
166,
735,
11,
HexColor("#d7ebe6"),
17,
)
footer(c, 4)
def draw_architecture(c: canvas.Canvas) -> None:
c.setFillColor(white); c.rect(0, 0, W, H, fill=1, stroke=0)
c.setFillColor(white)
c.rect(0, 0, W, H, fill=1, stroke=0)
title(c, "04 / Architectuur", "Eén keten, expliciete verantwoordelijkheden")
nodes = [
(55, 270, 130, 66, "React + MapLibre", "kaartwerkruimte"),
@@ -190,37 +288,79 @@ def draw_architecture(c: canvas.Canvas) -> None:
(570, 90, 150, 66, "PostGIS + artifacts", "resultaat + provenance"),
]
for x, y, w, h, head, body in nodes:
c.setFillColor(PALE if "PyTorch" not in head else HexColor("#0d5047")); c.roundRect(x, y, w, h, 10, fill=1, stroke=0)
c.setFillColor(white if "PyTorch" in head else INK); c.setFont(FONT_BOLD, 9); c.drawString(x + 12, y + 39, head)
c.setFillColor(HexColor("#b9d8d0") if "PyTorch" in head else MUTED); c.setFont(FONT, 7.2); c.drawString(x + 12, y + 21, body)
c.setStrokeColor(TEAL); c.setLineWidth(1.5)
arrows = [((185,303),(220,303)),((350,303),(385,388)),((350,303),(385,218)),((535,388),(645,336)),((535,218),(570,303)),((645,270),(645,156))]
for (x1,y1),(x2,y2) in arrows:
c.line(x1,y1,x2,y2); c.circle(x2,y2,2.5,fill=1,stroke=0)
paragraph(c, "PostGIS bewaart querybare geometrie en lifecycle-records. Grote raster-, mask- en modelbestanden blijven versioned artifacts; de database bewaart hun identiteit, checksum en provenance.", 55, 150, 460, 9.5, MUTED, 15)
c.setFillColor(PALE if "PyTorch" not in head else HexColor("#0d5047"))
c.roundRect(x, y, w, h, 10, fill=1, stroke=0)
c.setFillColor(white if "PyTorch" in head else INK)
c.setFont(FONT_BOLD, 9)
c.drawString(x + 12, y + 39, head)
c.setFillColor(HexColor("#b9d8d0") if "PyTorch" in head else MUTED)
c.setFont(FONT, 7.2)
c.drawString(x + 12, y + 21, body)
c.setStrokeColor(TEAL)
c.setLineWidth(1.5)
arrows = [
((185, 303), (220, 303)),
((350, 303), (385, 388)),
((350, 303), (385, 218)),
((535, 388), (645, 336)),
((535, 218), (570, 303)),
((645, 270), (645, 156)),
]
for (x1, y1), (x2, y2) in arrows:
c.line(x1, y1, x2, y2)
c.circle(x2, y2, 2.5, fill=1, stroke=0)
paragraph(
c,
"PostGIS bewaart querybare geometrie en lifecycle-records. Grote raster-, mask- en modelbestanden blijven versioned artifacts; de database bewaart hun identiteit, checksum en provenance.",
55,
150,
460,
9.5,
MUTED,
15,
)
footer(c, 5)
def draw_outcomes(c: canvas.Canvas) -> None:
image_cover(c, PUBLIC / "geointel-dark-case-study-cover.png", 0, 0, W, H)
c.setFillColor(HexColor("#041d19")); c.rect(0, 0, W, H, fill=1, stroke=0)
c.setFillColor(MINT); c.setFont(FONT_BOLD, 8); c.drawString(42, H - 44, "05 / RESULTAAT")
c.setFillColor(white); c.setFont(FONT_BOLD, 27); c.drawString(42, H - 82, "Een professionele GeoAI-workbench")
c.setFillColor(HexColor("#c0dad4")); c.setFont(FONT, 10); c.drawString(42, H - 104, "Nationaal/maritiem van scope, lokaal controleerbaar in bewijs.")
c.setFillColor(HexColor("#041d19"))
c.rect(0, 0, W, H, fill=1, stroke=0)
c.setFillColor(MINT)
c.setFont(FONT_BOLD, 8)
c.drawString(42, H - 44, "05 / RESULTAAT")
c.setFillColor(white)
c.setFont(FONT_BOLD, 27)
c.drawString(42, H - 82, "Een professionele GeoAI-workbench")
c.setFillColor(HexColor("#c0dad4"))
c.setFont(FONT, 10)
c.drawString(
42, H - 104, "Nationaal/maritiem van scope, lokaal controleerbaar in bewijs."
)
outcomes = [
("Map-first", "De gebruiker blijft in dezelfde ruimtelijke context."),
("Evidence-first", "QA en provenance zijn onderdeel van het resultaat."),
("GPU-ready", "NVIDIA CUDA is expliciet geconfigureerd en gevalideerd."),
("Accessible motion", "Animatie verduidelijkt toestand en respecteert reduced motion."),
(
"Accessible motion",
"Animatie verduidelijkt toestand en respecteert reduced motion.",
),
]
for i, (head, body) in enumerate(outcomes):
x = 42 + (i % 2) * 375
y = 330 - (i // 2) * 105
c.setFillColor(HexColor("#123f38")); c.roundRect(x, y, 350, 78, 11, fill=1, stroke=0)
c.setFillColor(MINT); c.setFont(FONT_BOLD, 11); c.drawString(x + 16, y + 48, head)
c.setFillColor(HexColor("#123f38"))
c.roundRect(x, y, 350, 78, 11, fill=1, stroke=0)
c.setFillColor(MINT)
c.setFont(FONT_BOLD, 11)
c.drawString(x + 16, y + 48, head)
paragraph(c, body, x + 16, y + 38, 316, 8, HexColor("#b8d2cc"), 12)
c.setFillColor(white); c.setFont(FONT_BOLD, 11); c.drawString(42, 58, "GeoIntel · Jens / ITWorx.tech")
c.setFillColor(HexColor("#8fb9af")); c.setFont(FONT, 8); c.drawString(42, 42, "Belgium and the Belgian North Sea · v1.0.0")
c.setFillColor(white)
c.setFont(FONT_BOLD, 11)
c.drawString(42, 58, "GeoIntel · Jens / ITWorx.tech")
c.setFillColor(HexColor("#8fb9af"))
c.setFont(FONT, 8)
c.drawString(42, 42, "Belgium and the Belgian North Sea · v1.0.0")
def build() -> None:
@@ -228,8 +368,17 @@ def build() -> None:
c = canvas.Canvas(str(OUTPUT), pagesize=PAGE, pageCompression=1)
c.setTitle("GeoIntel - Evidence-first GeoAI case study")
c.setAuthor("Jens / ITWorx.tech")
c.setSubject("Portfolio case study for the GeoIntel Belgium and Belgian North Sea workbench")
for draw in (draw_cover, draw_challenge, draw_experience, draw_ai, draw_architecture, draw_outcomes):
c.setSubject(
"Portfolio case study for the GeoIntel Belgium and Belgian North Sea workbench"
)
for draw in (
draw_cover,
draw_challenge,
draw_experience,
draw_ai,
draw_architecture,
draw_outcomes,
):
draw(c)
c.showPage()
c.save()