many revisions, getting closer
This commit is contained in:
689
From_Tragedy_to_Triumph/create_presentation.py
Normal file
689
From_Tragedy_to_Triumph/create_presentation.py
Normal file
@@ -0,0 +1,689 @@
|
||||
#!/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)}")
|
||||
Reference in New Issue
Block a user