-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmake_executive_pdf.py
More file actions
122 lines (107 loc) 路 6.42 KB
/
Copy pathmake_executive_pdf.py
File metadata and controls
122 lines (107 loc) 路 6.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""
make_executive_pdf.py
----------------------
Builds a polished, client-ready PDF version of reports/executive_summary.md
using reportlab. Run:
python src/make_executive_pdf.py
"""
from pathlib import Path
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable
)
ROOT = Path(__file__).resolve().parent.parent
OUT = ROOT / "reports" / "executive_summary.pdf"
styles = getSampleStyleSheet()
title_style = ParagraphStyle("TitleCustom", parent=styles["Title"], fontSize=22, textColor=colors.HexColor("#1B2A4A"))
subtitle_style = ParagraphStyle("Subtitle", parent=styles["Normal"], fontSize=11, textColor=colors.HexColor("#555555"), spaceAfter=4)
h2_style = ParagraphStyle("H2Custom", parent=styles["Heading2"], fontSize=14, textColor=colors.HexColor("#1B2A4A"), spaceBefore=16, spaceAfter=8)
body_style = ParagraphStyle("BodyCustom", parent=styles["Normal"], fontSize=10.5, leading=15, spaceAfter=8)
story = []
story.append(Paragraph("Executive Summary", title_style))
story.append(Paragraph("Customer Segmentation Analysis", ParagraphStyle("st2", parent=subtitle_style, fontSize=14, textColor=colors.HexColor("#333333"))))
story.append(Spacer(1, 6))
story.append(Paragraph("Prepared for: Marketing & Growth Leadership", subtitle_style))
story.append(Paragraph("Prepared by: Data Analytics Team", subtitle_style))
story.append(Paragraph("Date: July 2026", subtitle_style))
story.append(Spacer(1, 10))
story.append(HRFlowable(width="100%", color=colors.HexColor("#1B2A4A"), thickness=1.2))
story.append(Spacer(1, 14))
story.append(Paragraph("Objective", h2_style))
story.append(Paragraph(
"Identify distinct customer groups within the retail customer base so marketing spend can be "
"targeted rather than blanket, improving retention and revenue efficiency.", body_style))
story.append(Paragraph("Approach", h2_style))
story.append(Paragraph(
"We analyzed 1,200 customers (built on a real Kaggle customer dataset, expanded with modeled "
"behavioral data) across seven behavioral dimensions: age, income, spending score, purchase "
"frequency, recency, tenure, and monetary value. Three clustering algorithms — K-Means, "
"Hierarchical Clustering, and DBSCAN — were tested and compared. K-Means (k=4) was "
"selected as the production model based on cluster balance, interpretability, and stability "
"testing.", body_style))
story.append(Paragraph("The Four Segments", h2_style))
table_data = [
["Segment", "Share", "Profile", "Recommended Action"],
["Premium\nSpenders", "21%", "Highest income & spending,\nmost frequent, most recent,\nlongest tenure",
"VIP loyalty tier, priority\nsupport, referral program"],
["Steady Mature\nRegulars", "24%", "Older, mid income/spend,\ndependable",
"Cross-sell campaigns,\nmid-tier loyalty perks"],
["New Bargain\nHunters", "28%", "Younger, price-sensitive,\nengaged, growing",
"Time-boxed discounts,\nbundle deals, rewards"],
["At-Risk\nCustomers", "27%", "High income but lowest\nengagement, ~120 days\nsince last order",
"Urgent win-back campaign,\nroot-cause survey"],
]
t = Table(table_data, colWidths=[1.15 * inch, 0.6 * inch, 2.15 * inch, 2.1 * inch])
t.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1B2A4A")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 8.5),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#F2F4F8")]),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CCCCCC")),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
("LEFTPADDING", (0, 0), (-1, -1), 6),
]))
story.append(t)
story.append(Spacer(1, 14))
story.append(Paragraph("Key Findings", h2_style))
findings = [
"<b>No single metric predicts value.</b> Income alone does not predict spending — multi-dimensional clustering was necessary to find real structure.",
"<b>At-Risk Customers are the highest-leverage opportunity.</b> They have the income to spend but have quietly disengaged — unlike low-income segments, the barrier isn't affordability, making them a strong win-back target.",
"<b>The segmentation is statistically stable.</b> Repeated testing on random subsamples showed consistent results, meaning these segments can be trusted for ongoing campaign planning, not just a one-time snapshot.",
"<b>DBSCAN was evaluated but rejected for production use</b> — while it scored well numerically, it produced one oversized cluster and many tiny, unusable fragments rather than clean, equal-sized business segments.",
]
for f in findings:
story.append(Paragraph(f"• {f}", body_style))
story.append(Paragraph("Recommended Next Steps", h2_style))
steps = [
"Launch a win-back campaign for At-Risk Customers within the next marketing cycle.",
"Pilot a VIP loyalty tier for Premium Spenders to protect and grow this high-value segment.",
"Re-run this segmentation quarterly and track segment migration over time.",
"Replace engineered behavioral fields with real transaction data as it becomes available.",
]
for i, s in enumerate(steps, 1):
story.append(Paragraph(f"{i}. {s}", body_style))
story.append(Paragraph("Expected Business Impact", h2_style))
story.append(Paragraph(
"Targeted campaigns per segment (instead of one blanket strategy) are expected to improve "
"campaign response rates and reduce wasted spend on customers unlikely to respond to generic "
"offers, while directly addressing the revenue at risk in the At-Risk segment.", body_style))
story.append(Spacer(1, 16))
story.append(HRFlowable(width="100%", color=colors.HexColor("#CCCCCC"), thickness=0.7))
story.append(Spacer(1, 6))
story.append(Paragraph(
"<i>Full technical methodology, code, and visualizations are available in the accompanying "
"Jupyter notebook and GitHub repository.</i>",
ParagraphStyle("footer", parent=body_style, fontSize=9, textColor=colors.HexColor("#777777"))
))
doc = SimpleDocTemplate(str(OUT), pagesize=letter,
topMargin=0.7 * inch, bottomMargin=0.7 * inch,
leftMargin=0.75 * inch, rightMargin=0.75 * inch)
doc.build(story)
print(f"Saved: {OUT}")