Files
dts-2026Presentation/From_Tragedy_to_Triumph/create_presentation.py

690 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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)}")