690 lines
20 KiB
Python
690 lines
20 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Create PowerPoint: From Tragedy to Triumph
|
||
How Data Transformed Deaths into Design Changes - 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 - Darker, more somber for emotional impact
|
||
DARK_NAVY = RGBColor(15, 30, 45) # Very dark blue/black
|
||
ACCENT_GOLD = RGBColor(212, 175, 55) # Dignified gold
|
||
SOFT_WHITE = RGBColor(250, 250, 250)
|
||
DARK_GRAY = RGBColor(60, 60, 60)
|
||
MED_GRAY = RGBColor(120, 120, 120)
|
||
LIGHT_GRAY = RGBColor(220, 220, 220)
|
||
HOPE_BLUE = RGBColor(70, 130, 180) # Steel blue for triumph sections
|
||
SOMBER_RED = RGBColor(139, 69, 69) # Muted red for tragedy
|
||
|
||
|
||
def add_dark_title_slide(title, subtitle=""):
|
||
"""Full dark title slide for emotional impact"""
|
||
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 = DARK_NAVY
|
||
bg.line.fill.background()
|
||
|
||
title_box = slide.shapes.add_textbox(
|
||
Inches(0.5), Inches(2.8), Inches(12.333), Inches(1.5)
|
||
)
|
||
tf = title_box.text_frame
|
||
p = tf.paragraphs[0]
|
||
p.text = title
|
||
p.font.size = Pt(56)
|
||
p.font.bold = True
|
||
p.font.color.rgb = SOFT_WHITE
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
if subtitle:
|
||
sub_box = slide.shapes.add_textbox(
|
||
Inches(0.5), Inches(4.5), Inches(12.333), Inches(0.8)
|
||
)
|
||
tf = sub_box.text_frame
|
||
p = tf.paragraphs[0]
|
||
p.text = subtitle
|
||
p.font.size = Pt(24)
|
||
p.font.color.rgb = ACCENT_GOLD
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
return slide
|
||
|
||
|
||
def add_story_intro_slide(story_num, title):
|
||
"""Story section divider - dark and serious"""
|
||
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 = DARK_NAVY
|
||
bg.line.fill.background()
|
||
|
||
# Story number
|
||
num_box = slide.shapes.add_textbox(
|
||
Inches(0.5), Inches(2.2), Inches(12.333), Inches(0.8)
|
||
)
|
||
tf = num_box.text_frame
|
||
p = tf.paragraphs[0]
|
||
p.text = f"STORY {story_num}"
|
||
p.font.size = Pt(20)
|
||
p.font.color.rgb = ACCENT_GOLD
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
# Title
|
||
title_box = slide.shapes.add_textbox(
|
||
Inches(0.5), Inches(3.0), Inches(12.333), Inches(1.5)
|
||
)
|
||
tf = title_box.text_frame
|
||
tf.word_wrap = True
|
||
p = tf.paragraphs[0]
|
||
p.text = title
|
||
p.font.size = Pt(44)
|
||
p.font.bold = True
|
||
p.font.color.rgb = SOFT_WHITE
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
return slide
|
||
|
||
|
||
def add_memorial_slide(main_text, subtext=""):
|
||
"""Memorial-style slide for tragedy sections"""
|
||
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 = DARK_NAVY
|
||
bg.line.fill.background()
|
||
|
||
# Main text
|
||
main_box = slide.shapes.add_textbox(
|
||
Inches(1), Inches(2.5), Inches(11.333), Inches(2)
|
||
)
|
||
tf = main_box.text_frame
|
||
tf.word_wrap = True
|
||
p = tf.paragraphs[0]
|
||
p.text = main_text
|
||
p.font.size = Pt(36)
|
||
p.font.color.rgb = SOFT_WHITE
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
if subtext:
|
||
sub_box = slide.shapes.add_textbox(
|
||
Inches(1), Inches(4.8), Inches(11.333), Inches(0.8)
|
||
)
|
||
tf = sub_box.text_frame
|
||
p = tf.paragraphs[0]
|
||
p.text = subtext
|
||
p.font.size = Pt(22)
|
||
p.font.italic = True
|
||
p.font.color.rgb = MED_GRAY
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
return slide
|
||
|
||
|
||
def add_question_slide(question):
|
||
"""Large 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 = DARK_NAVY
|
||
bg.line.fill.background()
|
||
|
||
q_box = slide.shapes.add_textbox(Inches(1), Inches(2.8), Inches(11.333), Inches(2))
|
||
tf = q_box.text_frame
|
||
tf.word_wrap = True
|
||
p = tf.paragraphs[0]
|
||
p.text = question
|
||
p.font.size = Pt(40)
|
||
p.font.italic = True
|
||
p.font.color.rgb = ACCENT_GOLD
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
return slide
|
||
|
||
|
||
def add_content_slide(title, bullets, tone="neutral"):
|
||
"""Content slide with tonal variation"""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
|
||
# Background based on tone
|
||
if tone == "tragedy":
|
||
header_color = SOMBER_RED
|
||
elif tone == "triumph":
|
||
header_color = HOPE_BLUE
|
||
else:
|
||
header_color = DARK_NAVY
|
||
|
||
# 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 = header_color
|
||
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 = SOFT_WHITE
|
||
|
||
# Bullets
|
||
bullet_box = slide.shapes.add_textbox(
|
||
Inches(0.8), Inches(1.4), Inches(11.5), Inches(5.8)
|
||
)
|
||
tf = bullet_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_data_slide(title, data_description, annotation=""):
|
||
"""Data revelation 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 = DARK_NAVY
|
||
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 = SOFT_WHITE
|
||
|
||
# Data box placeholder
|
||
data_box = slide.shapes.add_shape(
|
||
MSO_SHAPE.RECTANGLE, Inches(1), Inches(1.5), Inches(11.333), Inches(4)
|
||
)
|
||
data_box.fill.solid()
|
||
data_box.fill.fore_color.rgb = LIGHT_GRAY
|
||
data_box.line.color.rgb = DARK_NAVY
|
||
|
||
data_text = slide.shapes.add_textbox(
|
||
Inches(1), Inches(3), Inches(11.333), Inches(1)
|
||
)
|
||
tf = data_text.text_frame
|
||
p = tf.paragraphs[0]
|
||
p.text = f"[{data_description}]"
|
||
p.font.size = Pt(22)
|
||
p.font.color.rgb = DARK_GRAY
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
if annotation:
|
||
ann_box = slide.shapes.add_textbox(
|
||
Inches(1), Inches(5.8), Inches(11.333), Inches(1)
|
||
)
|
||
tf = ann_box.text_frame
|
||
tf.word_wrap = True
|
||
p = tf.paragraphs[0]
|
||
p.text = annotation
|
||
p.font.size = Pt(18)
|
||
p.font.bold = True
|
||
p.font.color.rgb = SOMBER_RED
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
return slide
|
||
|
||
|
||
def add_triumph_stat_slide(stat, description):
|
||
"""Triumph statistic slide - hopeful tone"""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
|
||
# Gradient-like effect with hope blue
|
||
bg = slide.shapes.add_shape(
|
||
MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height
|
||
)
|
||
bg.fill.solid()
|
||
bg.fill.fore_color.rgb = HOPE_BLUE
|
||
bg.line.fill.background()
|
||
|
||
stat_box = slide.shapes.add_textbox(
|
||
Inches(0.5), Inches(2), Inches(12.333), Inches(2.5)
|
||
)
|
||
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 = SOFT_WHITE
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
desc_box = slide.shapes.add_textbox(
|
||
Inches(0.5), Inches(4.5), Inches(12.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 = SOFT_WHITE
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
return slide
|
||
|
||
|
||
def add_arc_slide(title):
|
||
"""Show the tragedy-to-triumph arc"""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
|
||
title_box = slide.shapes.add_textbox(
|
||
Inches(0.5), Inches(0.3), Inches(12.333), Inches(0.8)
|
||
)
|
||
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 = DARK_NAVY
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
# Arc elements
|
||
elements = ["TRAGEDY", "INVESTIGATION", "DATA", "SOLUTION", "TRIUMPH"]
|
||
colors = [SOMBER_RED, DARK_GRAY, DARK_NAVY, HOPE_BLUE, HOPE_BLUE]
|
||
|
||
box_width = Inches(2.0)
|
||
spacing = Inches(0.4)
|
||
start_x = Inches(0.8)
|
||
|
||
for i, (element, color) in enumerate(zip(elements, colors)):
|
||
x = start_x + i * (box_width + spacing)
|
||
|
||
# Box
|
||
box = slide.shapes.add_shape(
|
||
MSO_SHAPE.ROUNDED_RECTANGLE, x, Inches(3), box_width, Inches(1.2)
|
||
)
|
||
box.fill.solid()
|
||
box.fill.fore_color.rgb = color
|
||
box.line.fill.background()
|
||
|
||
# Text
|
||
text_box = slide.shapes.add_textbox(x, Inches(3.3), box_width, Inches(0.8))
|
||
tf = text_box.text_frame
|
||
p = tf.paragraphs[0]
|
||
p.text = element
|
||
p.font.size = Pt(16)
|
||
p.font.bold = True
|
||
p.font.color.rgb = SOFT_WHITE
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
# Arrow
|
||
if i < len(elements) - 1:
|
||
arrow = slide.shapes.add_shape(
|
||
MSO_SHAPE.RIGHT_ARROW,
|
||
x + box_width + Inches(0.05),
|
||
Inches(3.35),
|
||
spacing - Inches(0.1),
|
||
Inches(0.5),
|
||
)
|
||
arrow.fill.solid()
|
||
arrow.fill.fore_color.rgb = MED_GRAY
|
||
arrow.line.fill.background()
|
||
|
||
return slide
|
||
|
||
|
||
def add_final_quote_slide(quote):
|
||
"""Final reflective quote"""
|
||
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 = DARK_NAVY
|
||
bg.line.fill.background()
|
||
|
||
quote_box = slide.shapes.add_textbox(
|
||
Inches(1), Inches(2.5), Inches(11.333), Inches(3)
|
||
)
|
||
tf = quote_box.text_frame
|
||
tf.word_wrap = True
|
||
p = tf.paragraphs[0]
|
||
p.text = quote
|
||
p.font.size = Pt(32)
|
||
p.font.italic = True
|
||
p.font.color.rgb = ACCENT_GOLD
|
||
p.alignment = PP_ALIGN.CENTER
|
||
|
||
return slide
|
||
|
||
|
||
# ============================================================================
|
||
# BUILD THE PRESENTATION
|
||
# ============================================================================
|
||
|
||
# Slide 1: Title
|
||
add_dark_title_slide(
|
||
"From Tragedy to Triumph", "How Data Transformed Deaths into Design Changes"
|
||
)
|
||
|
||
# Slide 2: Arc introduction
|
||
add_arc_slide("Every Safety Standard Follows This Arc")
|
||
|
||
# Slide 3: Story 1 intro
|
||
add_story_intro_slide("1", "The Children Who Were\nKilled by Safety")
|
||
|
||
# Slide 4: Memorial
|
||
add_memorial_slide(
|
||
"In memory of the 175+ people\nkilled by airbags, 1990-1997",
|
||
"Most were children sitting in the front seat",
|
||
)
|
||
|
||
# Slide 5: The question
|
||
add_question_slide(
|
||
"Why were airbags killing the people\nthey were designed to protect?"
|
||
)
|
||
|
||
# Slide 6: Investigation
|
||
add_content_slide(
|
||
"The Investigation: Out-of-Position Testing",
|
||
[
|
||
"What happens when someone isn't in the ideal position?",
|
||
"What happens when they're too close to the airbag?",
|
||
"What happens when they're small?",
|
||
"",
|
||
"Researchers used instrumented dummies:",
|
||
" Small female dummies, child dummies, infant dummies",
|
||
" Accelerometers and load cells capturing every millisecond",
|
||
" The same sensors DTS makes today",
|
||
],
|
||
)
|
||
|
||
# Slide 7: The data
|
||
add_data_slide(
|
||
"The Data: Fatal Forces Revealed",
|
||
"Neck load trace showing 3,500 N peak vs. 1,600 N injury threshold",
|
||
"More than DOUBLE the fatal threshold for a child's neck",
|
||
)
|
||
|
||
# Slide 8: The solution
|
||
add_content_slide(
|
||
"The Engineering Solution",
|
||
[
|
||
"1998: Immediate depowering (20-35% force reduction)",
|
||
"Dual-stage inflators: Variable deployment force",
|
||
"Occupant classification: Weight sensors detect children",
|
||
"Suppression systems: Turn off for rear-facing child seats",
|
||
"Multi-stage deployment: Match force to crash severity",
|
||
"",
|
||
"Every solution required new testing, new data, new validation",
|
||
],
|
||
tone="triumph",
|
||
)
|
||
|
||
# Slide 9: Triumph
|
||
add_triumph_stat_slide(
|
||
"50 → 0", "Airbag fatalities per year:\nFrom over 50 to essentially zero"
|
||
)
|
||
|
||
# Slide 10: Story 2 intro
|
||
add_story_intro_slide("2", "The Car That Was\nCheaper to Let Burn")
|
||
|
||
# Slide 11: Pinto tragedy
|
||
add_content_slide(
|
||
"The Ford Pinto: 1970s Bestseller",
|
||
[
|
||
"Small, cheap, fuel-efficient during the oil crisis",
|
||
"One problem: Gas tank 9 inches from rear bumper",
|
||
"",
|
||
"In rear-end collisions:",
|
||
" Rear crumpled into gas tank",
|
||
" Tank ruptured, fuel sprayed",
|
||
" A spark ignited it",
|
||
"",
|
||
"People burned to death inside their cars",
|
||
"Estimates: 27 to several hundred deaths",
|
||
],
|
||
tone="tragedy",
|
||
)
|
||
|
||
# Slide 12: The memo
|
||
add_memorial_slide(
|
||
'"Costs: $11/car × 12.5M cars = $137M\nBenefits: 180 deaths × $200K = $36M"',
|
||
"Internal Ford cost-benefit analysis. Cost exceeds benefit. Don't fix it.",
|
||
)
|
||
|
||
# Slide 13: Data/solution
|
||
add_content_slide(
|
||
"The Data That Should Have Driven the Decision",
|
||
[
|
||
"Rear impact testing showed catastrophic fuel leakage",
|
||
"Tank design failed at impacts as low as 25 mph",
|
||
"Ford engineers knew. They had the data.",
|
||
"But data didn't make it past accounting.",
|
||
"",
|
||
"After the scandal, FMVSS 301 was strengthened:",
|
||
" Rear impact: 50 mph",
|
||
" Side impact: 20 mph",
|
||
" Rollover: 360 degrees",
|
||
" Fuel leakage precisely measured and limited",
|
||
],
|
||
)
|
||
|
||
# Slide 14: Triumph
|
||
add_triumph_stat_slide("Down 76%", "Vehicle fire deaths since 1980")
|
||
|
||
# Slide 15: Story 3 intro
|
||
add_story_intro_slide("3", "The Test That\nHumbled an Industry")
|
||
|
||
# Slide 16: Context
|
||
add_content_slide(
|
||
"2010: Frontal Safety Was 'Solved'",
|
||
[
|
||
"Vehicles acing federal full-frontal test",
|
||
"Vehicles acing IIHS moderate overlap test",
|
||
"Frontal crash deaths were down significantly",
|
||
"",
|
||
"But IIHS researchers noticed a pattern...",
|
||
"",
|
||
"People were still dying in frontal crashes",
|
||
"Tree strikes. Pole strikes. Corner-to-corner.",
|
||
"Crashes where only 20-25% of width was engaged",
|
||
],
|
||
)
|
||
|
||
# Slide 17: The test
|
||
add_content_slide(
|
||
"The New Test: Small Overlap Frontal",
|
||
[
|
||
"25% overlap - not 40%",
|
||
"Rigid barrier - not deformable",
|
||
"40 mph",
|
||
"",
|
||
"Designed to stress the outer edge of the structure",
|
||
"The area where trees and poles were hitting",
|
||
"",
|
||
"2012: Testing begins",
|
||
"Results shocked everyone",
|
||
],
|
||
)
|
||
|
||
# Slide 18: The failures
|
||
add_content_slide(
|
||
"The Results: Premium Brands Failed",
|
||
[
|
||
"BMW 5-Series: Marginal",
|
||
"Mercedes C-Class: Marginal",
|
||
"Audi A4: Marginal",
|
||
"Lexus ES: Poor",
|
||
"",
|
||
"Only 3 of 13 midsize cars earned 'Good'",
|
||
"",
|
||
"Data showed catastrophic intrusion, collapsed footwells,",
|
||
"HIC values exceeding limits, femur loads off the charts",
|
||
],
|
||
tone="tragedy",
|
||
)
|
||
|
||
# Slide 19: Response
|
||
add_content_slide(
|
||
"The Industry Response",
|
||
[
|
||
"Within two years: Fundamental structural redesign",
|
||
"Small overlap load paths added",
|
||
"Extended bumper beams",
|
||
"Reinforced A-pillars",
|
||
"",
|
||
"By 2015: Most vehicles earning 'Good'",
|
||
"",
|
||
"Instrumented testing proved the problem",
|
||
"And proved that the redesign worked",
|
||
],
|
||
tone="triumph",
|
||
)
|
||
|
||
# Slide 20: Triumph
|
||
add_triumph_stat_slide(
|
||
"Down 8%",
|
||
"Additional reduction in frontal crash deaths\nwithin 3 years of test introduction",
|
||
)
|
||
|
||
# Slide 21: Story 4 intro
|
||
add_story_intro_slide("4", "The Sport That\nSaved Itself")
|
||
|
||
# Slide 22: Football tragedy
|
||
add_memorial_slide(
|
||
"32 players per year", "Killed by head and neck injuries in football, 1960s-1970s"
|
||
)
|
||
|
||
# Slide 23: The problem
|
||
add_content_slide(
|
||
"Helmets Existed. They Just Didn't Work.",
|
||
[
|
||
"No standard. No test. No validation.",
|
||
"Manufacturers made claims. Coaches trusted them.",
|
||
"Players died.",
|
||
"",
|
||
"1969: NOCSAE formed",
|
||
"Mission: Create a test that actually predicts protection",
|
||
"",
|
||
"Drop tower. Instrumented headform. Accelerometers.",
|
||
"Severity Index (SI) metric developed.",
|
||
],
|
||
)
|
||
|
||
# Slide 24: The data
|
||
add_data_slide(
|
||
"The Data: Some Helmets Weren't Even Close",
|
||
"Chart: SI values - Good helmets at 600-800, bad helmets at 1500-2000",
|
||
"Pass threshold: SI < 1200. Many market leaders failed catastrophically.",
|
||
)
|
||
|
||
# Slide 25: Solution
|
||
add_content_slide(
|
||
"The Solution: Set a Standard",
|
||
[
|
||
"NOCSAE established SI < 1200 requirement",
|
||
"1980: NCAA mandated compliance",
|
||
"High school federations followed",
|
||
"",
|
||
"Helmet manufacturers had to design for performance",
|
||
"Better liners. Better shells. Better protection.",
|
||
"",
|
||
"2016: Linear impactor test added for rotational injury",
|
||
"The science advances; the standard advances",
|
||
],
|
||
tone="triumph",
|
||
)
|
||
|
||
# Slide 26: Triumph
|
||
add_triumph_stat_slide(
|
||
"32 → 3", "Football deaths per year from head/neck injuries\n90%+ reduction"
|
||
)
|
||
|
||
# Slide 27: Pattern summary
|
||
add_arc_slide("Four Stories, One Pattern: Data Made the Difference")
|
||
|
||
# Slide 28: DTS position
|
||
add_content_slide(
|
||
"Where DTS Fits in This Arc",
|
||
[
|
||
"We're in the middle. We're the data capture mechanism.",
|
||
"",
|
||
"Our accelerometers are in the dummies",
|
||
"Our DAQ systems are in the crash labs",
|
||
"Our calibration ensures global consistency",
|
||
"",
|
||
"We don't design vehicles or write regulations",
|
||
"But without accurate data capture,",
|
||
"none of that other work is possible",
|
||
],
|
||
)
|
||
|
||
# Slide 29: Ongoing mission
|
||
add_content_slide(
|
||
"The Mission Continues",
|
||
[
|
||
"Electric vehicles: New crash dynamics, battery safety",
|
||
"Autonomous vehicles: Active safety validation",
|
||
"Pedestrians & cyclists: Rising fatalities need solutions",
|
||
"Military: Evolving threats, new protection needs",
|
||
"Sports: Concussion science revealing rotational injury",
|
||
"",
|
||
"Each challenge will follow the same arc",
|
||
"There will be tragedies. Then data. Then solutions.",
|
||
],
|
||
)
|
||
|
||
# Slide 30: Final quote
|
||
add_final_quote_slide(
|
||
"Behind every safety standard is someone who didn't come home.\n\n"
|
||
"Data ensures their loss wasn't in vain."
|
||
)
|
||
|
||
# Slide 31: Questions
|
||
add_dark_title_slide("Questions?", "Ben - Application Engineer")
|
||
|
||
# ============================================================================
|
||
# Save
|
||
# ============================================================================
|
||
|
||
output_file = "From_Tragedy_to_Triumph.pptx"
|
||
prs.save(output_file)
|
||
print(f"Presentation created: {output_file}")
|
||
print(f"Total slides: {len(prs.slides)}")
|