sadar / scripts /build_pitch_pptx.py
Ruperth's picture
feat: polish mobile responsive layout across all dashboard screens
96bba34
Raw
History Blame Contribute Delete
29.1 kB
"""
Generate an editable .pptx of the SADAR 5-min pitch deck.
Output: docs/SADAR_pitch_5min.pptx
Slides: 11 (matches docs/SADAR_pitch_5min.html numbering).
Open in Google Slides via Drive -> right click -> Open with Google Slides.
Design: same color tokens, fonts, layout principles as the HTML deck,
adapted to the limits of python-pptx (native shapes + textboxes + images).
"""
from pathlib import Path
from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.enum.shapes import MSO_SHAPE
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.util import Inches, Pt, Emu
REPO = Path(__file__).resolve().parent.parent
OUT = REPO / "docs" / "SADAR_pitch_5min.pptx"
ASSETS = REPO / "docs" / "assets"
FRONTEND = REPO / "frontend" / "public"
SLIDE_W = Inches(13.333)
SLIDE_H = Inches(7.5)
C_BG = RGBColor(0x05, 0x08, 0x0D)
C_BG_DEEP = RGBColor(0x0A, 0x10, 0x14)
C_PANEL = RGBColor(0x0E, 0x1A, 0x1F)
C_EDGE = RGBColor(0x1A, 0x2F, 0x36)
C_TEXT = RGBColor(0xC1, 0xDA, 0xCD)
C_MUTED = RGBColor(0x5E, 0x7A, 0x78)
C_LABEL = RGBColor(0x7A, 0x9E, 0x99)
C_INFO = RGBColor(0x7F, 0xD1, 0xC6)
C_WARN = RGBColor(0xE6, 0xA2, 0x3C)
C_ALERT = RGBColor(0xE0, 0x80, 0x80)
C_WHITE = RGBColor(0xFF, 0xFF, 0xFF)
FONT_MONO = "Roboto Mono"
FONT_FALLBACK = "Consolas"
def in_(v):
return Inches(v)
def pt(v):
return Pt(v)
def make_prs():
prs = Presentation()
prs.slide_width = SLIDE_W
prs.slide_height = SLIDE_H
return prs
def blank_slide(prs, bg=C_BG):
blank_layout = prs.slide_layouts[6]
slide = prs.slides.add_slide(blank_layout)
bg_rect = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SLIDE_W, SLIDE_H)
bg_rect.line.fill.background()
bg_rect.fill.solid()
bg_rect.fill.fore_color.rgb = bg
return slide
def add_rect(slide, x, y, w, h, fill, line=None):
rect = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, y, w, h)
rect.fill.solid()
rect.fill.fore_color.rgb = fill
if line is None:
rect.line.fill.background()
else:
rect.line.color.rgb = line
rect.line.width = Emu(6350)
return rect
def add_text(
slide, x, y, w, h, text,
size=18, color=C_TEXT, bold=False, italic=False,
font=FONT_MONO, align=PP_ALIGN.LEFT,
spacing=0.02, anchor=MSO_ANCHOR.TOP, uppercase=False,
):
tb = slide.shapes.add_textbox(x, y, w, h)
tf = tb.text_frame
tf.word_wrap = True
tf.margin_left = tf.margin_right = Emu(0)
tf.margin_top = tf.margin_bottom = Emu(0)
tf.vertical_anchor = anchor
lines = text.split("\n") if isinstance(text, str) else text
for i, line in enumerate(lines):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.alignment = align
run = p.add_run()
run.text = line.upper() if uppercase else line
run.font.name = font
run.font.size = pt(size)
run.font.bold = bold
run.font.italic = italic
run.font.color.rgb = color
return tb
def add_topbar(slide, ctx_text, status_left="SADAR", right_pairs=None, status_color=None):
add_rect(slide, 0, 0, SLIDE_W, in_(0.42), C_BG_DEEP)
add_rect(slide, 0, in_(0.42), SLIDE_W, Emu(12700), C_EDGE)
left_y = in_(0.10)
left_text = f"{status_left} // {ctx_text}"
add_text(
slide, in_(0.5), left_y, in_(8.0), in_(0.30),
left_text, size=10, color=C_MUTED, uppercase=True,
spacing=0.22,
)
if right_pairs:
right_str = " // ".join(right_pairs)
tb = add_text(
slide, in_(5.0), left_y, in_(7.8), in_(0.30),
right_str, size=10,
color=status_color or C_INFO,
uppercase=True, spacing=0.22, align=PP_ALIGN.RIGHT,
)
return tb
def add_footer(slide, crumbs, controls=False):
add_rect(slide, 0, SLIDE_H - in_(0.55), SLIDE_W, Emu(12700), C_EDGE)
add_rect(slide, 0, SLIDE_H - in_(0.54), SLIDE_W, in_(0.54), C_BG_DEEP)
logo_path = FRONTEND / "sadar-mark.png"
if logo_path.exists():
try:
slide.shapes.add_picture(
str(logo_path), in_(0.4), SLIDE_H - in_(0.45), height=in_(0.35),
)
except Exception:
pass
if controls:
ctrl_x = in_(5.4)
ctrl_y = SLIDE_H - in_(0.42)
ctrl_w = in_(0.55)
ctrl_h = in_(0.30)
ctrls = [("PAUSE", False), ("1X", True), ("10X", False), ("30X", False), ("60X", False)]
for i, (lbl, active) in enumerate(ctrls):
rect = add_rect(
slide, ctrl_x + i * (ctrl_w + in_(0.08)), ctrl_y,
ctrl_w, ctrl_h, C_BG_DEEP,
line=(C_INFO if active else C_EDGE),
)
tb = slide.shapes.add_textbox(rect.left, rect.top, rect.width, rect.height)
tf = tb.text_frame
tf.margin_left = tf.margin_right = Emu(0)
tf.margin_top = tf.margin_bottom = Emu(0)
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.CENTER
run = p.add_run()
run.text = lbl
run.font.name = FONT_MONO
run.font.size = pt(8)
run.font.color.rgb = C_INFO if active else C_MUTED
add_text(
slide, SLIDE_W - in_(4.8), SLIDE_H - in_(0.42),
in_(4.4), in_(0.30),
crumbs, size=10, color=C_INFO,
uppercase=True, spacing=0.22, align=PP_ALIGN.RIGHT,
)
def add_kicker(slide, x, y, text, color=C_INFO):
add_text(
slide, x, y, in_(8.0), in_(0.3),
f"// {text}", size=11, color=color, uppercase=True, spacing=0.30,
)
def add_bg_image(slide, path, opacity_hint=0.4):
if not path.exists():
return
try:
pic = slide.shapes.add_picture(str(path), 0, in_(0.45), width=SLIDE_W)
if pic.height > SLIDE_H - in_(1.0):
pic.height = SLIDE_H - in_(1.0)
pic.width = int(pic.height * (16 / 9))
pic.left = int((SLIDE_W - pic.width) / 2)
pic.top = int((SLIDE_H - pic.height) / 2)
veil = add_rect(slide, 0, 0, SLIDE_W, SLIDE_H, C_BG)
veil.fill.transparency = 0.35
except Exception:
pass
# ---------------- SLIDES ----------------
def slide_01_portada(prs):
slide = blank_slide(prs)
add_topbar(
slide, "SYSTEM ONLINE // LEMD ACC",
right_pairs=["v1.0", "LIVE"], status_color=C_INFO,
)
add_text(
slide, 0, in_(2.3), SLIDE_W, in_(1.4),
"SADAR", size=110, color=C_TEXT, bold=True, font=FONT_MONO,
align=PP_ALIGN.CENTER, spacing=0.04,
)
add_text(
slide, 0, in_(3.9), SLIDE_W, in_(0.5),
"Smart Anomaly Detection for Aviation Routes",
size=22, color=C_INFO, font=FONT_MONO, align=PP_ALIGN.CENTER,
spacing=0.18, uppercase=False,
)
add_text(
slide, 0, in_(4.5), SLIDE_W, in_(0.5),
"Vigilancia inteligente del espacio aereo",
size=18, color=C_TEXT, font=FONT_MONO, align=PP_ALIGN.CENTER,
spacing=0.04,
)
add_text(
slide, 0, in_(6.0), SLIDE_W, in_(0.4),
"aviacion comercial // datos abiertos // open-source",
size=11, color=C_MUTED, uppercase=True, font=FONT_MONO,
align=PP_ALIGN.CENTER, spacing=0.22,
)
add_footer(slide, "SADAR // 01 / 11", controls=True)
def slide_02_hook(prs):
slide = blank_slide(prs)
add_bg_image(slide, FRONTEND / "bg-mid.jpg")
add_topbar(
slide, "CASO HISTORICO // 31 JUL 2017 // LEMD ACC",
right_pairs=["ALERT", "SQUAWK 7500"],
status_color=C_ALERT,
)
add_text(
slide, in_(0.7), in_(1.1), in_(7.2), in_(3.6),
"Un avion transmite el\ncodigo de secuestro\nsobre Madrid durante\ntreinta segundos.\nY aterriza como si nada.",
size=38, color=C_TEXT, font=FONT_MONO, bold=True, spacing=0.02,
)
add_text(
slide, in_(0.7), in_(5.0), in_(7.2), in_(1.2),
"驴Real o glitch?\nEsa pregunta la responde SADAR.",
size=20, color=C_INFO, font=FONT_MONO,
)
panel_x = in_(8.4)
panel_y = in_(0.9)
panel_w = in_(4.4)
panel_h = in_(5.8)
add_rect(slide, panel_x, panel_y, panel_w, panel_h, C_PANEL, line=C_EDGE)
add_rect(slide, panel_x, panel_y, panel_w, in_(0.5), C_BG_DEEP, line=C_EDGE)
add_text(
slide, panel_x + in_(0.2), panel_y + in_(0.1),
in_(3.0), in_(0.3), "SELECTED FLIGHT",
size=10, color=C_LABEL, uppercase=True, spacing=0.24,
)
add_text(
slide, panel_x + panel_w - in_(1.4), panel_y + in_(0.1),
in_(1.2), in_(0.3), "TRACK",
size=10, color=C_INFO, uppercase=True, spacing=0.24,
align=PP_ALIGN.RIGHT,
)
rows = [
("CALLSIGN", "IBE2845", C_TEXT, True),
("OPERATOR", "Iberia", C_TEXT, False),
("DATE", "31 JUL 2017", C_TEXT, False),
("SQUAWK", "7500", C_ALERT, True),
("MEANING", "unlawful interference", C_ALERT, False),
("DURATION", "~ 30 s", C_WARN, False),
("DIST LEMD", "0.8 NM", C_TEXT, False),
("OUTCOME", "landed nominally", C_INFO, False),
]
row_y = panel_y + in_(0.6)
row_h = in_(0.62)
for (lbl, val, val_color, big) in rows:
add_text(
slide, panel_x + in_(0.25), row_y + in_(0.18),
in_(2.0), in_(0.3), lbl,
size=10, color=C_LABEL, uppercase=True, spacing=0.18,
)
add_text(
slide, panel_x + in_(2.2), row_y + in_(0.10),
in_(2.0), in_(0.4), val,
size=20 if big else 14, color=val_color, bold=big,
font=FONT_MONO, align=PP_ALIGN.RIGHT,
)
row_y += row_h
add_text(
slide, panel_x, panel_y + panel_h + in_(0.15),
panel_w, in_(0.6),
"La alerta salto en torre. Las bocinas sonaron.\nTreinta segundos despues, todo en silencio.",
size=11, color=C_MUTED, italic=True, font=FONT_MONO, spacing=0.04,
)
add_footer(slide, "SADAR // 02 / 11 // EL GLITCH")
def slide_03_problema(prs):
slide = blank_slide(prs)
add_topbar(
slide, "PROBLEM SPACE // ONE-CLASS LEARNING",
right_pairs=["LABELED DATA", "4 / 19.000"], status_color=C_INFO,
)
add_kicker(slide, in_(0.7), in_(0.95), "el problema")
add_text(
slide, in_(0.7), in_(1.4), in_(7.0), in_(1.6),
"驴Como se detecta lo raro\ncuando no hay ejemplos de lo raro?",
size=36, color=C_TEXT, bold=True, font=FONT_MONO, spacing=0.02,
)
bullets = [
"Los secuestros no se repiten miles de veces.\nNo hay catalogo para ensenarle a la IA.",
"SADAR no aprende que es malo.\nAprende que es normal.",
"19.000 vuelos sobre Madrid le ensenan\ncomo es uno bueno. El resto lo deduce.",
]
y = in_(3.5)
for b in bullets:
add_text(
slide, in_(0.7), y, in_(0.4), in_(0.6),
">", size=22, color=C_INFO, bold=True, font=FONT_MONO,
)
add_text(
slide, in_(1.1), y, in_(6.8), in_(1.2),
b, size=17, color=C_TEXT, font=FONT_MONO, spacing=0.04,
)
y += in_(1.05)
map_path = ASSETS / "eda" / "trajectories_map.png"
if map_path.exists():
try:
pic = slide.shapes.add_picture(
str(map_path),
in_(8.4), in_(1.3), width=in_(4.5),
)
if pic.height > in_(5.0):
pic.height = in_(5.0)
pic.width = int(pic.height * (pic.width / pic.height))
add_text(
slide, in_(8.4), in_(1.3) + pic.height + in_(0.1),
in_(4.5), in_(0.4),
"Trayectorias normales y eventos de emergencia sobre LEMD",
size=10, color=C_MUTED, font=FONT_MONO, italic=True,
align=PP_ALIGN.CENTER, spacing=0.04,
)
except Exception:
pass
add_footer(slide, "SADAR // 03 / 11 // PROBLEMA")
def slide_04_idea(prs):
slide = blank_slide(prs)
add_topbar(
slide, "SYSTEM DESIGN // CORE PRINCIPLE",
right_pairs=["MODE", "UNSUPERVISED"], status_color=C_INFO,
)
add_kicker(slide, in_(0.7), in_(0.95), "la idea SADAR")
add_text(
slide, in_(0.7), in_(1.5), in_(12.0), in_(1.0),
"Aprender lo normal. Alertar lo demas.",
size=44, color=C_TEXT, bold=True, font=FONT_MONO, spacing=0.02,
)
box_y = in_(3.2)
box_w = in_(3.6)
box_h = in_(2.2)
gap = in_(0.4)
start_x = (SLIDE_W - (3 * box_w + 2 * gap)) / 2
boxes = [
("APRENDE DE", "19.000 vuelos reales\nde Madrid-Barajas"),
("MODELO", "Aprende como es\nun vuelo normal"),
("SALIDA", "Alerta si algo\nno encaja"),
]
for i, (head, body) in enumerate(boxes):
x = start_x + i * (box_w + gap)
add_rect(slide, x, box_y, box_w, box_h, C_PANEL, line=C_EDGE)
add_rect(slide, x, box_y, in_(0.06), box_h, C_INFO)
add_text(
slide, x + in_(0.3), box_y + in_(0.4), box_w - in_(0.6), in_(0.4),
head, size=12, color=C_INFO, uppercase=True,
spacing=0.24, font=FONT_MONO,
)
add_text(
slide, x + in_(0.3), box_y + in_(1.0), box_w - in_(0.6), in_(1.1),
body, size=18, color=C_TEXT, font=FONT_MONO, spacing=0.02,
)
add_text(
slide, in_(0.7), box_y + box_h + in_(0.5), in_(12.0), in_(0.8),
"Como un controlador con 19.000 aproximaciones en la cabeza:\ncuando una no encaja, se la nota al instante.",
size=16, color=C_MUTED, italic=True, font=FONT_MONO,
align=PP_ALIGN.CENTER, spacing=0.04,
)
add_footer(slide, "SADAR // 04 / 11 // IDEA")
def slide_screenshot(prs, idx, img_path, ctx_text, right_pairs, right_color,
tag_text, tag_alert, caption_lines, crumbs, controls=True):
slide = blank_slide(prs)
add_bg_image(slide, FRONTEND / "bg-mid.jpg")
add_topbar(slide, ctx_text, right_pairs=right_pairs, status_color=right_color)
if img_path.exists():
try:
pic = slide.shapes.add_picture(
str(img_path), 0, 0, width=in_(11.5),
)
max_h = in_(5.2)
if pic.height > max_h:
ratio = max_h / pic.height
pic.height = max_h
pic.width = int(pic.width * ratio)
pic.left = int((SLIDE_W - pic.width) / 2)
pic.top = in_(1.05)
except Exception:
pic = None
else:
pic = None
tag_color = C_ALERT if tag_alert else C_INFO
tag_x = in_(0.5)
tag_y = in_(0.85)
add_rect(slide, tag_x, tag_y, in_(0.06), in_(0.6),
tag_color)
add_rect(slide, tag_x + in_(0.06), tag_y, in_(5.5), in_(0.6),
C_PANEL, line=C_EDGE)
add_text(
slide, tag_x + in_(0.25), tag_y + in_(0.13),
in_(5.2), in_(0.4), tag_text,
size=13, color=tag_color if tag_alert else C_TEXT,
uppercase=True, spacing=0.20, font=FONT_MONO, bold=True,
)
cap_w = in_(5.2)
cap_h = in_(1.2)
cap_x = SLIDE_W - cap_w - in_(0.5)
cap_y = SLIDE_H - in_(1.95)
add_rect(slide, cap_x, cap_y, cap_w, cap_h, C_PANEL, line=C_EDGE)
add_rect(slide, cap_x + cap_w - in_(0.06), cap_y, in_(0.06), cap_h,
tag_color)
tb = slide.shapes.add_textbox(cap_x + in_(0.3), cap_y + in_(0.15),
cap_w - in_(0.6), cap_h - in_(0.3))
tf = tb.text_frame
tf.word_wrap = True
tf.margin_left = tf.margin_right = Emu(0)
tf.margin_top = tf.margin_bottom = Emu(0)
for i, (txt, color) in enumerate(caption_lines):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.alignment = PP_ALIGN.RIGHT
run = p.add_run()
run.text = txt
run.font.name = FONT_MONO
run.font.size = pt(15)
run.font.color.rgb = color
add_footer(slide, crumbs, controls=controls)
def slide_05_radar(prs):
slide_screenshot(
prs, 5, ASSETS / "img1.png",
"RANGE 40 NM // LEMD ACC // TRACKS 25",
right_pairs=["STATUS", "NOMINAL"], right_color=C_INFO,
tag_text="OPERACION NORMAL", tag_alert=False,
caption_lines=[
("Lo que ve un operador en torre:", C_TEXT),
("veinticinco vuelos vigilados a la vez", C_INFO),
],
crumbs="SADAR // 05 / 11 // RADAR",
controls=True,
)
def slide_06_simulador(prs):
slide_screenshot(
prs, 6, ASSETS / "img2.png",
"SIMULATOR // LEMD ACC // SDR001 INJECTED",
right_pairs=["ALERT", "LATENCY 0s"], right_color=C_ALERT,
tag_text="ANOMALIA INYECTADA // < 2 MIN",
tag_alert=True,
caption_lines=[
("Inyectamos una maniobra rara", C_TEXT),
("y el sistema avisa antes de los 2 minutos", C_INFO),
],
crumbs="SADAR // 06 / 11 // SIMULADOR",
controls=True,
)
def slide_07_resultados(prs):
slide = blank_slide(prs)
add_topbar(
slide, "SYSTEM METRICS // VALIDATION",
right_pairs=["LATENCY", "< 2 MIN"], status_color=C_INFO,
)
add_kicker(slide, in_(0.7), in_(0.95), "resultados")
add_text(
slide, in_(0.7), in_(1.45), in_(12.0), in_(0.8),
"El sistema funciona y avisa pronto.",
size=36, color=C_TEXT, bold=True, font=FONT_MONO,
)
kpis = [
("LATENCIA HASTA LA ALERTA", "< 2 min",
"desde que algo se sale del patron\nhasta que SADAR lo dice"),
("VUELOS REALES APRENDIDOS", "19.000",
"trayectorias completas de\nMadrid-Barajas (OpenSky)"),
("EMERGENCIAS REALES DETECTADAS", "4",
"emergencias del historico\ndetectadas correctamente"),
]
y = in_(2.8)
box_h = in_(2.4)
box_w = in_(3.9)
gap = in_(0.3)
start_x = (SLIDE_W - (3 * box_w + 2 * gap)) / 2
for i, (lbl, val, cap) in enumerate(kpis):
x = start_x + i * (box_w + gap)
add_rect(slide, x, y, box_w, box_h, C_PANEL, line=C_EDGE)
add_rect(slide, x, y, in_(0.06), box_h, C_INFO)
add_text(
slide, x + in_(0.3), y + in_(0.3),
box_w - in_(0.6), in_(0.4),
lbl, size=10, color=C_LABEL, uppercase=True,
spacing=0.22, font=FONT_MONO,
)
add_text(
slide, x + in_(0.3), y + in_(0.9),
box_w - in_(0.6), in_(1.0),
val, size=44, color=C_INFO, bold=True, font=FONT_MONO,
)
add_text(
slide, x + in_(0.3), y + in_(1.7),
box_w - in_(0.6), in_(0.8),
cap, size=11, color=C_MUTED, font=FONT_MONO, spacing=0.02,
)
add_text(
slide, in_(0.7), in_(5.5), in_(12.0), in_(0.8),
"En pruebas con maniobras anomalas inyectadas, el modelo\nlevanta la alerta antes de que pase nada.",
size=15, color=C_TEXT, font=FONT_MONO, spacing=0.02,
align=PP_ALIGN.CENTER,
)
add_text(
slide, in_(0.7), in_(6.3), in_(12.0), in_(0.4),
"En la linea de iniciativas espanolas como FARO o ISOBAR (CRIDA-ENAIRE, programa europeo SESAR).",
size=10, color=C_MUTED, italic=True, font=FONT_MONO,
align=PP_ALIGN.CENTER, spacing=0.04,
)
add_footer(slide, "SADAR // 07 / 11 // RESULTADOS")
def slide_08_casos(prs):
slide = blank_slide(prs)
add_topbar(
slide, "DEPLOYMENT SCENARIOS // 04 USE CASES",
right_pairs=["STATUS", "PRODUCTION READY"], status_color=C_INFO,
)
add_kicker(slide, in_(0.7), in_(0.95), "casos de uso")
add_text(
slide, in_(0.7), in_(1.4), in_(12.0), in_(0.8),
"Donde SADAR aporta valor hoy.",
size=32, color=C_TEXT, bold=True, font=FONT_MONO,
)
cases = [
("01", "Vigilancia ADS-B publica",
"Redes abiertas (OpenSky, FlightAware) sin acceso a planes de vuelo. SADAR anade una capa de deteccion automatica sobre datos publicos.",
"periodismo 路 ONGs 路 transparencia"),
("02", "Aeropuertos sin ATM propietario",
"Aeropuertos secundarios y regiones sin sistemas avanzados de vigilancia. Open-source y replicable con datos ADS-B locales.",
"aeropuertos regionales 路 mercados emergentes"),
("03", "Observatorios del espacio aereo",
"Reguladores y organismos independientes. Vision transversal sin necesidad de acceso a datos privados de cada operador.",
"AESA 路 observatorios 路 auditoria"),
("04", "Investigacion y academia",
"Plataforma base reproducible y abierta para investigar nuevos metodos de deteccion de anomalias en trayectorias.",
"universidades 路 centros de I+D"),
]
grid_x = in_(0.7)
grid_y = in_(2.5)
cell_w = in_(6.0)
cell_h = in_(1.95)
gap = in_(0.3)
for i, (num, title, body, who) in enumerate(cases):
col, row = i % 2, i // 2
x = grid_x + col * (cell_w + gap)
y = grid_y + row * (cell_h + in_(0.25))
add_rect(slide, x, y, cell_w, cell_h, C_PANEL, line=C_EDGE)
add_rect(slide, x, y, in_(0.06), cell_h, C_INFO)
add_text(
slide, x + in_(0.3), y + in_(0.2),
in_(1.0), in_(0.3), num,
size=10, color=C_INFO, uppercase=True,
spacing=0.24, font=FONT_MONO,
)
add_text(
slide, x + in_(0.3), y + in_(0.55),
cell_w - in_(0.6), in_(0.5),
title, size=18, color=C_TEXT, bold=True, font=FONT_MONO,
)
add_text(
slide, x + in_(0.3), y + in_(1.05),
cell_w - in_(0.6), in_(0.7),
body, size=11, color=C_TEXT, font=FONT_MONO, spacing=0.02,
)
add_text(
slide, x + in_(0.3), y + cell_h - in_(0.35),
cell_w - in_(0.6), in_(0.3),
who, size=9, color=C_LABEL, uppercase=True,
spacing=0.18, font=FONT_MONO,
)
add_text(
slide, in_(0.7), SLIDE_H - in_(1.0), in_(12.0), in_(0.4),
"SADAR no compite con la torre. Vigila lo que la torre no mira: el despues, lo publico, lo pequeno.",
size=12, color=C_MUTED, italic=True, font=FONT_MONO,
align=PP_ALIGN.CENTER, spacing=0.04,
)
add_footer(slide, "SADAR // 08 / 11 // CASOS DE USO")
def slide_09_como(prs):
slide = blank_slide(prs)
add_topbar(
slide, "ARCHITECTURE // MODEL ANATOMY",
right_pairs=["SIZE", "222 KB"], status_color=C_INFO,
)
add_kicker(slide, in_(0.7), in_(0.95), "como esta hecho")
add_text(
slide, in_(0.7), in_(1.4), in_(12.0), in_(1.5),
"Un modelo. Entrenado solo con vuelos normales.\nAprende como es un vuelo normal;\nsi algo no encaja, lo dice.",
size=24, color=C_TEXT, font=FONT_MONO, spacing=0.02,
)
nodes = [
("01 路 DATOS", "3,4 millones de puntos GPS\nde 19.000 vuelos reales"),
("02 路 MODELO", "Aprende lo normal\nsin ver una sola anomalia"),
("03 路 ALERTA", "Aviso en\nmenos de 2 minutos"),
]
nx = in_(0.7)
ny = in_(4.0)
nw = (SLIDE_W - in_(1.4) - in_(0.6)) / 3
nh = in_(1.4)
for i, (lbl, body) in enumerate(nodes):
x = nx + i * (nw + in_(0.3))
add_rect(slide, x, ny, nw, nh, C_PANEL, line=C_EDGE)
add_rect(slide, x, ny, in_(0.06), nh, C_INFO)
add_text(
slide, x + in_(0.3), ny + in_(0.2),
nw - in_(0.6), in_(0.3),
lbl, size=11, color=C_LABEL, uppercase=True,
spacing=0.22, font=FONT_MONO,
)
add_text(
slide, x + in_(0.3), ny + in_(0.65),
nw - in_(0.6), in_(0.7),
body, size=14, color=C_TEXT, font=FONT_MONO, spacing=0.02,
)
bullets = [
"Aprende sin etiquetas: nadie le dice que es un secuestro.",
"Mira 7 senales por punto: posicion, altitud, velocidad, rumbo, ascenso o descenso.",
"El modelo entero ocupa 222 KB. Cabe en un correo electronico.",
]
by = in_(5.6)
for b in bullets:
add_text(
slide, in_(0.7), by, in_(0.4), in_(0.4),
">", size=14, color=C_INFO, bold=True, font=FONT_MONO,
)
add_text(
slide, in_(1.1), by, in_(11.5), in_(0.4),
b, size=13, color=C_TEXT, font=FONT_MONO, spacing=0.02,
)
by += in_(0.42)
add_footer(slide, "SADAR // 09 / 11 // COMO")
def slide_10_cierre(prs):
slide = blank_slide(prs)
add_bg_image(slide, FRONTEND / "bg-mid.jpg")
add_topbar(
slide, "SESSION END // SUMMARY",
right_pairs=["RECORDING STOPPED"], status_color=C_MUTED,
)
add_kicker(slide, in_(0.7), in_(0.95), "cierre")
add_text(
slide, in_(0.7), in_(1.8), in_(12.0), in_(2.4),
"SADAR vigila lo que\nnadie esta vigilando.",
size=64, color=C_TEXT, bold=True, font=FONT_MONO, spacing=0.02,
)
add_text(
slide, in_(0.7), in_(4.6), in_(12.0), in_(1.2),
"Donde no llega la torre. Donde no hay plan de vuelo.\nDonde no hay licencia de software.\nY avisa antes de los dos minutos.",
size=22, color=C_TEXT, font=FONT_MONO, spacing=0.02,
)
add_text(
slide, in_(0.7), SLIDE_H - in_(1.6), in_(12.0), in_(0.8),
"Honestidad tecnica: sin planes de vuelo oficiales, sin eventos catastroficos reales.\nLos 4 vuelos con codigo de emergencia se usaron para validar, nunca para entrenar.",
size=11, color=C_MUTED, italic=True, font=FONT_MONO, spacing=0.02,
)
add_footer(slide, "SADAR // 10 / 11 // CIERRE")
def slide_11_qr(prs):
slide = blank_slide(prs)
add_topbar(
slide, "DEMO ACCESS // SCAN TO CONNECT",
right_pairs=["ONLINE"], status_color=C_INFO,
)
add_kicker(slide, in_(0.7), in_(0.95), "acceso")
add_text(
slide, in_(0.7), in_(1.5), in_(7.0), in_(2.5),
"Escanea\npara probar el\nsimulador en vivo.",
size=46, color=C_TEXT, bold=True, font=FONT_MONO, spacing=0.02,
)
panel_x = in_(0.7)
panel_y = in_(4.3)
panel_w = in_(6.6)
panel_h = in_(2.4)
add_rect(slide, panel_x, panel_y, panel_w, panel_h, C_PANEL, line=C_EDGE)
add_rect(slide, panel_x, panel_y, panel_w, in_(0.5), C_BG_DEEP, line=C_EDGE)
add_text(
slide, panel_x + in_(0.25), panel_y + in_(0.1),
in_(3.0), in_(0.3), "RECURSOS",
size=10, color=C_LABEL, uppercase=True, spacing=0.24,
)
add_text(
slide, panel_x + panel_w - in_(1.4), panel_y + in_(0.1),
in_(1.2), in_(0.3), "PUBLIC",
size=10, color=C_INFO, uppercase=True, spacing=0.24,
align=PP_ALIGN.RIGHT,
)
rows = [
("DEMO", "sadar.demo"),
("CODIGO", "github.com/sadar"),
("ARTICULO", "medium / sadar"),
("DATOS", "opensky-network.org"),
]
ry = panel_y + in_(0.65)
for lbl, val in rows:
add_text(
slide, panel_x + in_(0.3), ry,
in_(2.0), in_(0.35), lbl,
size=11, color=C_LABEL, uppercase=True, spacing=0.18,
)
add_text(
slide, panel_x + in_(2.5), ry - in_(0.02),
in_(4.0), in_(0.4), val,
size=14, color=C_INFO, font=FONT_MONO, align=PP_ALIGN.RIGHT,
)
ry += in_(0.40)
qr_path = ASSETS / "qr-placeholder.png"
if qr_path.exists():
try:
qr_w = in_(4.0)
qr_x = SLIDE_W - qr_w - in_(0.7)
qr_y = in_(1.5)
add_rect(slide, qr_x - in_(0.15), qr_y - in_(0.15),
qr_w + in_(0.3), qr_w + in_(0.3),
C_PANEL, line=C_EDGE)
add_rect(slide, qr_x - in_(0.15), qr_y - in_(0.15),
in_(0.06), qr_w + in_(0.3), C_INFO)
slide.shapes.add_picture(
str(qr_path), qr_x, qr_y, width=qr_w, height=qr_w,
)
add_text(
slide, qr_x, qr_y + qr_w + in_(0.2),
qr_w, in_(0.4),
"scan 路 play 路 inject",
size=12, color=C_INFO, uppercase=True,
spacing=0.28, font=FONT_MONO, align=PP_ALIGN.CENTER,
)
add_text(
slide, qr_x - in_(0.5), qr_y + qr_w + in_(0.9),
qr_w + in_(1.0), in_(0.6),
"路 Gracias 路",
size=24, color=C_INFO, uppercase=True,
spacing=0.32, font=FONT_MONO, align=PP_ALIGN.CENTER,
)
except Exception:
pass
add_footer(slide, "SADAR // 11 / 11 // FIN")
def main():
prs = make_prs()
slide_01_portada(prs)
slide_02_hook(prs)
slide_03_problema(prs)
slide_04_idea(prs)
slide_05_radar(prs)
slide_06_simulador(prs)
slide_07_resultados(prs)
slide_08_casos(prs)
slide_09_como(prs)
slide_10_cierre(prs)
slide_11_qr(prs)
OUT.parent.mkdir(parents=True, exist_ok=True)
prs.save(OUT)
print(f"OK -> {OUT}")
if __name__ == "__main__":
main()