feat(ui): refine governed workbench and dual-screen flows
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user