many revisions, getting closer
This commit is contained in:
834
When_the_Data_Surprised_Us/create_presentation.py
Normal file
834
When_the_Data_Surprised_Us/create_presentation.py
Normal file
@@ -0,0 +1,834 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Create PowerPoint: When the Data Surprised Us
|
||||
Counterintuitive Findings from the Field - 45 minute internal presentation
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pptx import Presentation
|
||||
from pptx.util import Inches, Pt
|
||||
from pptx.dml.color import RGBColor
|
||||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
||||
from pptx.enum.shapes import MSO_SHAPE
|
||||
|
||||
# Create presentation
|
||||
prs = Presentation()
|
||||
prs.slide_width = Inches(13.333)
|
||||
prs.slide_height = Inches(7.5)
|
||||
|
||||
# Color scheme - Engaging, puzzle-like feel
|
||||
DEEP_PURPLE = RGBColor(48, 25, 88) # Mystery color
|
||||
BRIGHT_TEAL = RGBColor(0, 168, 168) # Reveal color
|
||||
QUESTION_YELLOW = RGBColor(255, 200, 0) # Question accent
|
||||
DARK_GRAY = RGBColor(51, 51, 51)
|
||||
MED_GRAY = RGBColor(128, 128, 128)
|
||||
LIGHT_GRAY = RGBColor(235, 235, 235)
|
||||
WHITE = RGBColor(255, 255, 255)
|
||||
CORRECT_GREEN = RGBColor(46, 139, 87) # For good results
|
||||
WRONG_RED = RGBColor(178, 34, 34) # For bad results
|
||||
|
||||
|
||||
def add_title_slide(title, subtitle=""):
|
||||
"""Title slide with mystery theme"""
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
|
||||
bg = slide.shapes.add_shape(
|
||||
MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height
|
||||
)
|
||||
bg.fill.solid()
|
||||
bg.fill.fore_color.rgb = DEEP_PURPLE
|
||||
bg.line.fill.background()
|
||||
|
||||
# Question marks decoration
|
||||
for i, pos in enumerate([(1, 1), (11, 1.5), (2, 5.5), (10, 5)]):
|
||||
q = slide.shapes.add_textbox(
|
||||
Inches(pos[0]), Inches(pos[1]), Inches(1.5), Inches(1.5)
|
||||
)
|
||||
tf = q.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = "?"
|
||||
p.font.size = Pt(72)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = RGBColor(80, 50, 120) # Slightly lighter purple
|
||||
|
||||
title_box = slide.shapes.add_textbox(
|
||||
Inches(0.5), Inches(2.5), Inches(12.333), Inches(1.5)
|
||||
)
|
||||
tf = title_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = title
|
||||
p.font.size = Pt(52)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = WHITE
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
|
||||
if subtitle:
|
||||
sub_box = slide.shapes.add_textbox(
|
||||
Inches(0.5), Inches(4.3), Inches(12.333), Inches(0.8)
|
||||
)
|
||||
tf = sub_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = subtitle
|
||||
p.font.size = Pt(26)
|
||||
p.font.color.rgb = BRIGHT_TEAL
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
|
||||
return slide
|
||||
|
||||
|
||||
def add_puzzle_intro_slide(puzzle_num, title):
|
||||
"""Puzzle section introduction"""
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
|
||||
bg = slide.shapes.add_shape(
|
||||
MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height
|
||||
)
|
||||
bg.fill.solid()
|
||||
bg.fill.fore_color.rgb = DEEP_PURPLE
|
||||
bg.line.fill.background()
|
||||
|
||||
# Puzzle number
|
||||
num_box = slide.shapes.add_textbox(
|
||||
Inches(0.5), Inches(2.5), Inches(12.333), Inches(1)
|
||||
)
|
||||
tf = num_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = f"PUZZLE {puzzle_num}"
|
||||
p.font.size = Pt(28)
|
||||
p.font.color.rgb = QUESTION_YELLOW
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
|
||||
# Title
|
||||
title_box = slide.shapes.add_textbox(
|
||||
Inches(0.5), Inches(3.3), Inches(12.333), Inches(1.5)
|
||||
)
|
||||
tf = title_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = title
|
||||
p.font.size = Pt(48)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = WHITE
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
|
||||
return slide
|
||||
|
||||
|
||||
def add_setup_slide(title, content_lines):
|
||||
"""Setup slide for puzzle - the scenario"""
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
|
||||
# Header
|
||||
header = slide.shapes.add_shape(
|
||||
MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, Inches(1.0)
|
||||
)
|
||||
header.fill.solid()
|
||||
header.fill.fore_color.rgb = DEEP_PURPLE
|
||||
header.line.fill.background()
|
||||
|
||||
title_box = slide.shapes.add_textbox(
|
||||
Inches(0.5), Inches(0.2), Inches(12), Inches(0.7)
|
||||
)
|
||||
tf = title_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = title
|
||||
p.font.size = Pt(32)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = WHITE
|
||||
|
||||
# Content
|
||||
content_box = slide.shapes.add_textbox(
|
||||
Inches(0.8), Inches(1.4), Inches(11.5), Inches(5.8)
|
||||
)
|
||||
tf = content_box.text_frame
|
||||
tf.word_wrap = True
|
||||
|
||||
for i, line in enumerate(content_lines):
|
||||
if i == 0:
|
||||
p = tf.paragraphs[0]
|
||||
else:
|
||||
p = tf.add_paragraph()
|
||||
p.text = line
|
||||
p.font.size = Pt(24)
|
||||
p.font.color.rgb = DARK_GRAY
|
||||
p.space_after = Pt(12)
|
||||
|
||||
return slide
|
||||
|
||||
|
||||
def add_guess_slide(question):
|
||||
"""Interactive question slide"""
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
|
||||
bg = slide.shapes.add_shape(
|
||||
MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height
|
||||
)
|
||||
bg.fill.solid()
|
||||
bg.fill.fore_color.rgb = QUESTION_YELLOW
|
||||
bg.line.fill.background()
|
||||
|
||||
q_box = slide.shapes.add_textbox(
|
||||
Inches(1), Inches(2.5), Inches(11.333), Inches(2.5)
|
||||
)
|
||||
tf = q_box.text_frame
|
||||
tf.word_wrap = True
|
||||
p = tf.paragraphs[0]
|
||||
p.text = question
|
||||
p.font.size = Pt(40)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = DEEP_PURPLE
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
|
||||
# "What do you think?" prompt
|
||||
prompt_box = slide.shapes.add_textbox(
|
||||
Inches(1), Inches(5.5), Inches(11.333), Inches(0.8)
|
||||
)
|
||||
tf = prompt_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = "Take a guess..."
|
||||
p.font.size = Pt(24)
|
||||
p.font.italic = True
|
||||
p.font.color.rgb = DEEP_PURPLE
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
|
||||
return slide
|
||||
|
||||
|
||||
def add_reveal_slide(title, reveal_data, is_surprising=True):
|
||||
"""The big reveal slide"""
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
|
||||
# Header - teal for reveal
|
||||
header = slide.shapes.add_shape(
|
||||
MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, Inches(1.0)
|
||||
)
|
||||
header.fill.solid()
|
||||
header.fill.fore_color.rgb = BRIGHT_TEAL
|
||||
header.line.fill.background()
|
||||
|
||||
title_box = slide.shapes.add_textbox(
|
||||
Inches(0.5), Inches(0.2), Inches(12), Inches(0.7)
|
||||
)
|
||||
tf = title_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = "THE REVEAL: " + title
|
||||
p.font.size = Pt(28)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = WHITE
|
||||
|
||||
# Reveal content
|
||||
reveal_box = slide.shapes.add_textbox(
|
||||
Inches(1), Inches(1.5), Inches(11.333), Inches(5.5)
|
||||
)
|
||||
tf = reveal_box.text_frame
|
||||
tf.word_wrap = True
|
||||
|
||||
for i, line in enumerate(reveal_data):
|
||||
if i == 0:
|
||||
p = tf.paragraphs[0]
|
||||
else:
|
||||
p = tf.add_paragraph()
|
||||
p.text = line
|
||||
p.font.size = Pt(26)
|
||||
p.font.color.rgb = DARK_GRAY
|
||||
p.space_after = Pt(16)
|
||||
|
||||
return slide
|
||||
|
||||
|
||||
def add_comparison_slide(
|
||||
title, left_title, left_data, left_result, right_title, right_data, right_result
|
||||
):
|
||||
"""Side-by-side comparison with results"""
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
|
||||
# Header
|
||||
header = slide.shapes.add_shape(
|
||||
MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, Inches(1.0)
|
||||
)
|
||||
header.fill.solid()
|
||||
header.fill.fore_color.rgb = BRIGHT_TEAL
|
||||
header.line.fill.background()
|
||||
|
||||
title_box = slide.shapes.add_textbox(
|
||||
Inches(0.5), Inches(0.2), Inches(12), Inches(0.7)
|
||||
)
|
||||
tf = title_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = title
|
||||
p.font.size = Pt(28)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = WHITE
|
||||
|
||||
# Left side
|
||||
left_box = slide.shapes.add_shape(
|
||||
MSO_SHAPE.ROUNDED_RECTANGLE, Inches(0.5), Inches(1.3), Inches(6), Inches(4.5)
|
||||
)
|
||||
left_box.fill.solid()
|
||||
left_box.fill.fore_color.rgb = LIGHT_GRAY
|
||||
left_box.line.color.rgb = CORRECT_GREEN if left_result == "GOOD" else WRONG_RED
|
||||
left_box.line.width = Pt(4)
|
||||
|
||||
left_title_box = slide.shapes.add_textbox(
|
||||
Inches(0.8), Inches(1.5), Inches(5.5), Inches(0.6)
|
||||
)
|
||||
tf = left_title_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = left_title
|
||||
p.font.size = Pt(22)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = DARK_GRAY
|
||||
|
||||
left_data_box = slide.shapes.add_textbox(
|
||||
Inches(0.8), Inches(2.2), Inches(5.5), Inches(2.8)
|
||||
)
|
||||
tf = left_data_box.text_frame
|
||||
tf.word_wrap = True
|
||||
for i, line in enumerate(left_data):
|
||||
if i == 0:
|
||||
p = tf.paragraphs[0]
|
||||
else:
|
||||
p = tf.add_paragraph()
|
||||
p.text = line
|
||||
p.font.size = Pt(18)
|
||||
p.font.color.rgb = DARK_GRAY
|
||||
p.space_after = Pt(6)
|
||||
|
||||
left_result_box = slide.shapes.add_textbox(
|
||||
Inches(0.8), Inches(5.2), Inches(5.5), Inches(0.5)
|
||||
)
|
||||
tf = left_result_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = left_result
|
||||
p.font.size = Pt(24)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = CORRECT_GREEN if left_result == "GOOD" else WRONG_RED
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
|
||||
# Right side
|
||||
right_box = slide.shapes.add_shape(
|
||||
MSO_SHAPE.ROUNDED_RECTANGLE, Inches(6.833), Inches(1.3), Inches(6), Inches(4.5)
|
||||
)
|
||||
right_box.fill.solid()
|
||||
right_box.fill.fore_color.rgb = LIGHT_GRAY
|
||||
right_box.line.color.rgb = CORRECT_GREEN if right_result == "GOOD" else WRONG_RED
|
||||
right_box.line.width = Pt(4)
|
||||
|
||||
right_title_box = slide.shapes.add_textbox(
|
||||
Inches(7.1), Inches(1.5), Inches(5.5), Inches(0.6)
|
||||
)
|
||||
tf = right_title_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = right_title
|
||||
p.font.size = Pt(22)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = DARK_GRAY
|
||||
|
||||
right_data_box = slide.shapes.add_textbox(
|
||||
Inches(7.1), Inches(2.2), Inches(5.5), Inches(2.8)
|
||||
)
|
||||
tf = right_data_box.text_frame
|
||||
tf.word_wrap = True
|
||||
for i, line in enumerate(right_data):
|
||||
if i == 0:
|
||||
p = tf.paragraphs[0]
|
||||
else:
|
||||
p = tf.add_paragraph()
|
||||
p.text = line
|
||||
p.font.size = Pt(18)
|
||||
p.font.color.rgb = DARK_GRAY
|
||||
p.space_after = Pt(6)
|
||||
|
||||
right_result_box = slide.shapes.add_textbox(
|
||||
Inches(7.1), Inches(5.2), Inches(5.5), Inches(0.5)
|
||||
)
|
||||
tf = right_result_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = right_result
|
||||
p.font.size = Pt(24)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = CORRECT_GREEN if right_result == "GOOD" else WRONG_RED
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
|
||||
return slide
|
||||
|
||||
|
||||
def add_lesson_slide(lesson_text):
|
||||
"""Key lesson from the puzzle"""
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
|
||||
bg = slide.shapes.add_shape(
|
||||
MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height
|
||||
)
|
||||
bg.fill.solid()
|
||||
bg.fill.fore_color.rgb = DEEP_PURPLE
|
||||
bg.line.fill.background()
|
||||
|
||||
# "LESSON" label
|
||||
label_box = slide.shapes.add_textbox(
|
||||
Inches(0.5), Inches(2.3), Inches(12.333), Inches(0.5)
|
||||
)
|
||||
tf = label_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = "THE LESSON"
|
||||
p.font.size = Pt(18)
|
||||
p.font.color.rgb = BRIGHT_TEAL
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
|
||||
# Lesson text
|
||||
lesson_box = slide.shapes.add_textbox(
|
||||
Inches(1), Inches(3), Inches(11.333), Inches(2.5)
|
||||
)
|
||||
tf = lesson_box.text_frame
|
||||
tf.word_wrap = True
|
||||
p = tf.paragraphs[0]
|
||||
p.text = f'"{lesson_text}"'
|
||||
p.font.size = Pt(36)
|
||||
p.font.italic = True
|
||||
p.font.color.rgb = WHITE
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
|
||||
return slide
|
||||
|
||||
|
||||
def add_content_slide(title, bullets):
|
||||
"""Standard content slide"""
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
|
||||
header = slide.shapes.add_shape(
|
||||
MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, Inches(1.0)
|
||||
)
|
||||
header.fill.solid()
|
||||
header.fill.fore_color.rgb = DEEP_PURPLE
|
||||
header.line.fill.background()
|
||||
|
||||
title_box = slide.shapes.add_textbox(
|
||||
Inches(0.5), Inches(0.2), Inches(12), Inches(0.7)
|
||||
)
|
||||
tf = title_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = title
|
||||
p.font.size = Pt(32)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = WHITE
|
||||
|
||||
content_box = slide.shapes.add_textbox(
|
||||
Inches(0.8), Inches(1.4), Inches(11.5), Inches(5.8)
|
||||
)
|
||||
tf = content_box.text_frame
|
||||
tf.word_wrap = True
|
||||
|
||||
for i, bullet in enumerate(bullets):
|
||||
if i == 0:
|
||||
p = tf.paragraphs[0]
|
||||
else:
|
||||
p = tf.add_paragraph()
|
||||
p.text = f" {bullet}"
|
||||
p.font.size = Pt(22)
|
||||
p.font.color.rgb = DARK_GRAY
|
||||
p.space_after = Pt(14)
|
||||
|
||||
return slide
|
||||
|
||||
|
||||
def add_stat_slide(stat, description):
|
||||
"""Big statistic reveal"""
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
|
||||
bg = slide.shapes.add_shape(
|
||||
MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height
|
||||
)
|
||||
bg.fill.solid()
|
||||
bg.fill.fore_color.rgb = BRIGHT_TEAL
|
||||
bg.line.fill.background()
|
||||
|
||||
stat_box = slide.shapes.add_textbox(
|
||||
Inches(0.5), Inches(2), Inches(12.333), Inches(2)
|
||||
)
|
||||
tf = stat_box.text_frame
|
||||
p = tf.paragraphs[0]
|
||||
p.text = stat
|
||||
p.font.size = Pt(96)
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = WHITE
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
|
||||
desc_box = slide.shapes.add_textbox(
|
||||
Inches(1), Inches(4.3), Inches(11.333), Inches(1.5)
|
||||
)
|
||||
tf = desc_box.text_frame
|
||||
tf.word_wrap = True
|
||||
p = tf.paragraphs[0]
|
||||
p.text = description
|
||||
p.font.size = Pt(28)
|
||||
p.font.color.rgb = WHITE
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
|
||||
return slide
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# BUILD THE PRESENTATION
|
||||
# ============================================================================
|
||||
|
||||
# Slide 1: Title
|
||||
add_title_slide(
|
||||
"When the Data Surprised Us", "Counterintuitive Findings from the Field"
|
||||
)
|
||||
|
||||
# Slide 2: Rules
|
||||
add_content_slide(
|
||||
"The Rules of the Game",
|
||||
[
|
||||
"1. I'll show you a scenario",
|
||||
"2. You guess what happened",
|
||||
"3. I reveal the data",
|
||||
"4. We discuss why it matters",
|
||||
"",
|
||||
"Fair warning: Some of these will mess with your head.",
|
||||
"That's the point.",
|
||||
],
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# PUZZLE 1: The Beautiful Wreck
|
||||
# ============================================================================
|
||||
|
||||
add_puzzle_intro_slide("1", "The Beautiful Wreck")
|
||||
|
||||
add_setup_slide(
|
||||
"The Setup: Two Cars After Frontal Crash Tests",
|
||||
[
|
||||
"Same test. Same speed. Same barrier.",
|
||||
"",
|
||||
"CAR A: Destroyed. Front end pushed back almost to firewall.",
|
||||
" Hood crumpled like paper. Looks catastrophic.",
|
||||
"",
|
||||
"CAR B: Relatively intact. Minimal visible damage.",
|
||||
" Structure held. Passenger compartment looks good.",
|
||||
],
|
||||
)
|
||||
|
||||
add_guess_slide("Which car would you rather be in during a crash?")
|
||||
|
||||
add_comparison_slide(
|
||||
"The Reveal: Appearances Deceive",
|
||||
"CAR A (Destroyed)",
|
||||
[
|
||||
"HIC: 485",
|
||||
"Chest deflection: 38mm",
|
||||
"Femur load: 4.2 kN",
|
||||
"",
|
||||
"All measurements well within limits",
|
||||
],
|
||||
"GOOD",
|
||||
"CAR B (Intact)",
|
||||
[
|
||||
"HIC: 890",
|
||||
"Chest deflection: 58mm",
|
||||
"Femur load: 9.1 kN",
|
||||
"",
|
||||
"Multiple measurements near limits",
|
||||
],
|
||||
"MARGINAL",
|
||||
)
|
||||
|
||||
add_content_slide(
|
||||
"Why This Happens",
|
||||
[
|
||||
"Crush zones are SUPPOSED to crush",
|
||||
"That's their job - absorb energy by deforming",
|
||||
"",
|
||||
"Car A: Crumpled beautifully, extended deceleration pulse",
|
||||
" More time to slow down = less force on occupant",
|
||||
"",
|
||||
"Car B: Too stiff, didn't crumple enough",
|
||||
" Short, sharp deceleration = wall of g-forces",
|
||||
"",
|
||||
"Looking at the car tells you nothing about safety",
|
||||
],
|
||||
)
|
||||
|
||||
add_lesson_slide(
|
||||
"Never judge a crash by looking at the car. Judge it by looking at the data."
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# PUZZLE 2: The Survivor's Paradox
|
||||
# ============================================================================
|
||||
|
||||
add_puzzle_intro_slide("2", "The Survivor's Paradox")
|
||||
|
||||
add_setup_slide(
|
||||
"The Setup: The Rollover Problem",
|
||||
[
|
||||
"Rollovers are only 3% of crashes",
|
||||
"But they account for 33% of fatalities",
|
||||
"",
|
||||
"The obvious solution: Stronger roofs!",
|
||||
"",
|
||||
"In 2009, NHTSA doubled the roof crush requirement:",
|
||||
"From 1.5× vehicle weight to 3× vehicle weight",
|
||||
],
|
||||
)
|
||||
|
||||
add_guess_slide("How much did stronger roofs reduce rollover fatalities?")
|
||||
|
||||
add_reveal_slide(
|
||||
"Essentially Unchanged",
|
||||
[
|
||||
"Despite DOUBLING roof strength requirements...",
|
||||
"",
|
||||
"Rollover fatalities: Barely moved",
|
||||
"",
|
||||
"How is that possible?",
|
||||
"Wasn't roof crush the problem?",
|
||||
],
|
||||
)
|
||||
|
||||
add_content_slide(
|
||||
"The Data Revealed the Real Problem",
|
||||
[
|
||||
"70%+ of rollover fatalities involve EJECTION",
|
||||
"",
|
||||
"Occupants weren't killed by roof crushing",
|
||||
"They were killed by being thrown from the vehicle",
|
||||
"",
|
||||
"Once outside: Ground. Other cars. Poles.",
|
||||
"The vehicle itself rolling over them.",
|
||||
"",
|
||||
"Roof crush was secondary. Ejection was primary.",
|
||||
],
|
||||
)
|
||||
|
||||
add_content_slide(
|
||||
"The Real Solution",
|
||||
[
|
||||
"Side curtain airbags with rollover sensors",
|
||||
"Stay inflated for 5-6 seconds through multiple rolls",
|
||||
"Keep occupants INSIDE the vehicle",
|
||||
"",
|
||||
"Result: 41% reduction in ejection",
|
||||
"",
|
||||
"The data pointed to a completely different solution",
|
||||
"than the intuitive one",
|
||||
],
|
||||
)
|
||||
|
||||
add_lesson_slide(
|
||||
"The obvious solution isn't always the right solution. Let the data tell you what's actually happening."
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# PUZZLE 3: The Luxury Failure
|
||||
# ============================================================================
|
||||
|
||||
add_puzzle_intro_slide("3", "The Luxury Failure")
|
||||
|
||||
add_setup_slide(
|
||||
"The Setup: Premium Brands",
|
||||
[
|
||||
"BMW. Mercedes-Benz. Audi. Lexus.",
|
||||
"",
|
||||
"Most expensive cars on the road",
|
||||
"Most prestigious brands in the world",
|
||||
"Decades of safety engineering",
|
||||
"",
|
||||
"All passed moderate overlap test with flying colors",
|
||||
"",
|
||||
"2012: IIHS introduces small overlap test",
|
||||
"25% overlap instead of 40%. Rigid barrier.",
|
||||
],
|
||||
)
|
||||
|
||||
add_guess_slide("How did the luxury brands perform?")
|
||||
|
||||
add_content_slide(
|
||||
"The Results",
|
||||
[
|
||||
"BMW 5-Series: MARGINAL",
|
||||
"Mercedes C-Class: MARGINAL",
|
||||
"Audi A4: MARGINAL",
|
||||
"Lexus ES 350: POOR",
|
||||
"",
|
||||
"Only 3 of 13 midsize cars earned 'Good'",
|
||||
"",
|
||||
"The most expensive vehicles on the road",
|
||||
"couldn't pass a new crash test",
|
||||
],
|
||||
)
|
||||
|
||||
add_content_slide(
|
||||
"What the Data Showed",
|
||||
[
|
||||
"Crash forces bypassed main structural rails",
|
||||
"Wheels pushed back into footwells",
|
||||
"Firewalls collapsed",
|
||||
"Steering columns displaced into occupants",
|
||||
"",
|
||||
"Dummy data: Elevated HIC, critical femur loads,",
|
||||
"feet trapped in crushed metal",
|
||||
"",
|
||||
"Engineered for the 40% test. Vulnerable at 25%.",
|
||||
],
|
||||
)
|
||||
|
||||
add_lesson_slide(
|
||||
"Engineering to pass a test is not the same as engineering for safety."
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# PUZZLE 4: The 46.2g Man
|
||||
# ============================================================================
|
||||
|
||||
add_puzzle_intro_slide("4", "The 46.2g Man")
|
||||
|
||||
add_setup_slide(
|
||||
"The Setup: 1954",
|
||||
[
|
||||
"The jet age is dawning. Pilots flying faster than ever.",
|
||||
"Many are dying - not from crashes, from ejections.",
|
||||
"",
|
||||
"Medical authorities believed:",
|
||||
"'Humans cannot survive more than 18g of deceleration'",
|
||||
"",
|
||||
"If true, high-speed ejection was a death sentence.",
|
||||
"",
|
||||
"One researcher decided to test this assumption.",
|
||||
"Not with dummies. With himself.",
|
||||
],
|
||||
)
|
||||
|
||||
add_content_slide(
|
||||
"Colonel John Paul Stapp",
|
||||
[
|
||||
"Air Force flight surgeon",
|
||||
"The bravest - or craziest - researcher in safety history",
|
||||
"",
|
||||
"Strapped himself into a rocket sled",
|
||||
"Ran test after test, pushing higher g-forces",
|
||||
"",
|
||||
"December 10, 1954:",
|
||||
"The ultimate test",
|
||||
],
|
||||
)
|
||||
|
||||
add_stat_slide(
|
||||
"46.2g", "632 mph to zero in 1.4 seconds\nMore than 2.5× the 'fatal' threshold"
|
||||
)
|
||||
|
||||
add_content_slide(
|
||||
"He Survived",
|
||||
[
|
||||
"Walked away - temporarily blinded",
|
||||
"Burst blood vessels in his eyes",
|
||||
"But alive and conscious",
|
||||
"",
|
||||
"Proved humans could survive far more than believed",
|
||||
"Revolutionized aviation safety",
|
||||
"Made ejection seats viable at higher speeds",
|
||||
"",
|
||||
"Laid the foundation for automotive crash testing:",
|
||||
"How do we design so occupants never exceed their limits?",
|
||||
],
|
||||
)
|
||||
|
||||
add_lesson_slide("Assumptions are not data. Test it. Measure it. Know for certain.")
|
||||
|
||||
# ============================================================================
|
||||
# PUZZLE 5: The Airbag Arms Race
|
||||
# ============================================================================
|
||||
|
||||
add_puzzle_intro_slide("5", "The Airbag Arms Race")
|
||||
|
||||
add_setup_slide(
|
||||
"The Setup: Airbag History",
|
||||
[
|
||||
"1970: Airbags first proposed",
|
||||
"1984: First automatic restraint requirements",
|
||||
"1998: Airbags mandatory in all vehicles",
|
||||
"2000s: 'Advanced' airbags with occupant classification",
|
||||
"",
|
||||
"Each generation more sophisticated:",
|
||||
"More sensors. More stages. More intelligence.",
|
||||
],
|
||||
)
|
||||
|
||||
add_guess_slide(
|
||||
"How much faster did airbag technology advance after mandates vs. before?"
|
||||
)
|
||||
|
||||
add_reveal_slide(
|
||||
"Regulation Created Innovation",
|
||||
[
|
||||
"Pre-mandate (1970-1984): Minimal innovation",
|
||||
"Why invest in something not required?",
|
||||
"",
|
||||
"Post-mandate: Explosion of patents and technology",
|
||||
"Multi-stage inflators, occupant sensors, position detection",
|
||||
"Side airbags, curtain airbags, knee airbags",
|
||||
"",
|
||||
"The regulation created the market",
|
||||
"The market drove innovation",
|
||||
"The innovation saved lives",
|
||||
],
|
||||
)
|
||||
|
||||
add_stat_slide(
|
||||
"~2,800",
|
||||
"Lives saved by airbags per year in the U.S.\nFrom zero in 1985 to thousands today",
|
||||
)
|
||||
|
||||
add_lesson_slide(
|
||||
"Measurement isn't just about understanding what happened. It's about enabling what comes next."
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# CLOSING
|
||||
# ============================================================================
|
||||
|
||||
add_content_slide(
|
||||
"The Meta-Lesson",
|
||||
[
|
||||
"Beautiful Wreck: Crush zones work - deformation is safety",
|
||||
"Survivor's Paradox: Ejection, not crush, was the killer",
|
||||
"Luxury Failure: We'd optimized for the wrong test",
|
||||
"46.2g Man: Assumptions limited our designs",
|
||||
"Airbag Arms Race: Mandates drove innovation",
|
||||
"",
|
||||
"In every case, data didn't just describe - it ENABLED",
|
||||
"It pointed the way forward",
|
||||
],
|
||||
)
|
||||
|
||||
add_content_slide(
|
||||
"Implications for DTS",
|
||||
[
|
||||
"Our instrumentation is the source of these surprises",
|
||||
"",
|
||||
"When data contradicts expectations, it's because:",
|
||||
"Someone measured something that hadn't been measured",
|
||||
"Or measured it more precisely",
|
||||
"Or measured it in a new context",
|
||||
"",
|
||||
"Every sensor we design, every DAQ we build",
|
||||
"is potentially the source of the next breakthrough",
|
||||
],
|
||||
)
|
||||
|
||||
add_lesson_slide(
|
||||
"What assumptions are we making today that data will overturn tomorrow?"
|
||||
)
|
||||
|
||||
# Questions slide
|
||||
add_title_slide("Questions?", "Ben - Application Engineer")
|
||||
|
||||
# ============================================================================
|
||||
# Save
|
||||
# ============================================================================
|
||||
|
||||
output_file = "When_the_Data_Surprised_Us.pptx"
|
||||
prs.save(output_file)
|
||||
print(f"Presentation created: {output_file}")
|
||||
print(f"Total slides: {len(prs.slides)}")
|
||||
Reference in New Issue
Block a user