-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgenerate_report.py
More file actions
1142 lines (938 loc) · 49.6 KB
/
generate_report.py
File metadata and controls
1142 lines (938 loc) · 49.6 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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
UIDAI Aadhaar Enrolment Analytics Report Generator
Generates a professional, judge-ready PDF document for UIDAI Data Hackathon 2026
"""
import os
from datetime import datetime
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch, cm
from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY, TA_LEFT
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, ListFlowable, ListItem, HRFlowable
)
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# Import data pipeline for live metrics
from data_pipeline import load_and_prepare, generate_insights, generate_recommendations, _district_saturation_flags
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DATASET_PATH = os.path.join(BASE_DIR, 'Dataset', 'Aadhar Enrolment Dataset.csv')
OUTPUT_PATH = os.path.join(BASE_DIR, 'UIDAI_Aadhaar_Analytics_Report.pdf')
# Color palette (professional government style)
UIDAI_GREEN = colors.HexColor('#0b6e4f')
DARK_BLUE = colors.HexColor('#1e3a5f')
ACCENT_TEAL = colors.HexColor('#2dd4bf')
LIGHT_GRAY = colors.HexColor('#f3f4f6')
DARK_TEXT = colors.HexColor('#1f2937')
def create_styles():
"""Create custom paragraph styles for the report."""
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(
name='CoverTitle',
fontSize=28,
leading=34,
alignment=TA_CENTER,
textColor=DARK_BLUE,
spaceAfter=12,
fontName='Helvetica-Bold'
))
styles.add(ParagraphStyle(
name='CoverSubtitle',
fontSize=16,
leading=20,
alignment=TA_CENTER,
textColor=UIDAI_GREEN,
spaceAfter=8,
fontName='Helvetica'
))
styles.add(ParagraphStyle(
name='SectionHeading',
fontSize=16,
leading=20,
textColor=UIDAI_GREEN,
spaceBefore=20,
spaceAfter=12,
fontName='Helvetica-Bold'
))
styles.add(ParagraphStyle(
name='SubHeading',
fontSize=12,
leading=15,
textColor=DARK_BLUE,
spaceBefore=12,
spaceAfter=6,
fontName='Helvetica-Bold'
))
styles['BodyText'].fontSize = 10
styles['BodyText'].leading = 14
styles['BodyText'].alignment = TA_JUSTIFY
styles['BodyText'].textColor = DARK_TEXT
styles['BodyText'].spaceAfter = 8
styles['BodyText'].fontName = 'Helvetica'
styles.add(ParagraphStyle(
name='BulletText',
fontSize=10,
leading=14,
textColor=DARK_TEXT,
leftIndent=20,
spaceAfter=4,
fontName='Helvetica'
))
styles.add(ParagraphStyle(
name='TableHeader',
fontSize=10,
leading=12,
textColor=colors.white,
alignment=TA_CENTER,
fontName='Helvetica-Bold'
))
styles.add(ParagraphStyle(
name='Footer',
fontSize=8,
leading=10,
alignment=TA_CENTER,
textColor=colors.gray
))
return styles
def add_header_footer(canvas, doc):
"""Add header and footer to each page."""
canvas.saveState()
# Header line
canvas.setStrokeColor(UIDAI_GREEN)
canvas.setLineWidth(2)
canvas.line(1*cm, A4[1] - 1.5*cm, A4[0] - 1*cm, A4[1] - 1.5*cm)
# Header text
canvas.setFont('Helvetica', 8)
canvas.setFillColor(colors.gray)
canvas.drawString(1*cm, A4[1] - 1.2*cm, "UIDAI Data Hackathon 2026")
canvas.drawRightString(A4[0] - 1*cm, A4[1] - 1.2*cm, "Aadhaar Enrolment Analytics — Maharashtra")
# Footer
canvas.setStrokeColor(LIGHT_GRAY)
canvas.setLineWidth(1)
canvas.line(1*cm, 1.5*cm, A4[0] - 1*cm, 1.5*cm)
canvas.setFont('Helvetica', 8)
canvas.setFillColor(colors.gray)
canvas.drawString(1*cm, 0.8*cm, f"Generated: {datetime.now().strftime('%d %B %Y')}")
canvas.drawCentredString(A4[0]/2, 0.8*cm, "Confidential — For Evaluation Purposes · © Mandar Kajbaje")
canvas.drawRightString(A4[0] - 1*cm, 0.8*cm, f"Page {doc.page}")
canvas.restoreState()
def create_cover_page(styles):
"""Create the cover page elements."""
elements = []
elements.append(Spacer(1, 2*inch))
# Main title
elements.append(Paragraph(
"UIDAI Aadhaar Enrolment<br/>Analytics Dashboard",
styles['CoverTitle']
))
elements.append(Spacer(1, 0.3*inch))
# Subtitle
elements.append(Paragraph(
"Maharashtra State Analysis",
styles['CoverSubtitle']
))
elements.append(Spacer(1, 0.5*inch))
# Decorative line
elements.append(HRFlowable(
width="60%", thickness=3, color=UIDAI_GREEN,
spaceBefore=10, spaceAfter=10, hAlign='CENTER'
))
elements.append(Spacer(1, 0.5*inch))
# Hackathon info
elements.append(Paragraph(
"UIDAI Data Hackathon 2026",
styles['CoverSubtitle']
))
elements.append(Spacer(1, 0.3*inch))
elements.append(Paragraph(
"Analytical Report & Technical Documentation",
ParagraphStyle(
'CoverDetail',
fontSize=12,
alignment=TA_CENTER,
textColor=DARK_TEXT
)
))
elements.append(Spacer(1, 1.5*inch))
# Metadata table
meta_data = [
['Submission Date', datetime.now().strftime('%d %B %Y')],
['Dataset', 'Aadhaar Enrolment Dataset (Aggregated)'],
['Scope', 'Maharashtra — 53 Districts, 1,585 Pincodes'],
['Platform', 'Flask Dashboard on Azure App Service'],
]
meta_table = Table(meta_data, colWidths=[2.5*inch, 3*inch])
meta_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('TEXTCOLOR', (0, 0), (-1, -1), DARK_TEXT),
('ALIGN', (0, 0), (0, -1), 'RIGHT'),
('ALIGN', (1, 0), (1, -1), 'LEFT'),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
('TOPPADDING', (0, 0), (-1, -1), 8),
]))
elements.append(meta_table)
elements.append(PageBreak())
return elements
def create_executive_summary(styles, insights, recs):
"""Create the executive summary section."""
elements = []
elements.append(Paragraph("1. Executive Summary", styles['SectionHeading']))
elements.append(HRFlowable(width="100%", thickness=1, color=LIGHT_GRAY, spaceAfter=12))
elements.append(Paragraph(
"""This report presents a comprehensive analysis of Aadhaar enrolment patterns
across Maharashtra, derived from the official UIDAI Aadhaar Enrolment Dataset.
The analysis encompasses 93,184 records spanning 101 monthly data points,
53 districts, and 1,585 pincodes.""",
styles['BodyText']
))
elements.append(Paragraph("Problem Context", styles['SubHeading']))
elements.append(Paragraph(
"""With Aadhaar coverage approaching saturation in many regions, UIDAI faces
a strategic inflection point: the focus must shift from enrolment expansion
to service quality, operational efficiency, and targeted outreach for
underserved populations—particularly children under 5 years.""",
styles['BodyText']
))
elements.append(Paragraph("Approach", styles['SubHeading']))
elements.append(Paragraph(
"""This solution employs a data-driven analytics approach combining time-series
analysis, geographic disparity assessment, seasonality detection, and risk
flagging. The findings are presented through an interactive Flask-based
dashboard deployed on Azure App Service, ensuring accessibility and scalability.""",
styles['BodyText']
))
elements.append(Paragraph("Key Findings at a Glance", styles['SubHeading']))
findings_data = [
['Metric', 'Value', 'Implication'],
['Overall Growth', '+635.0%', 'Strong historical momentum'],
['Recent MoM Trend', '−11.1%', 'Potential saturation phase'],
['Child Share (0–17)', '97.8%', 'Child-centric enrolment dominance'],
['Districts at Risk', '49', 'Require service quality focus'],
['Volatile Districts', '22', 'Need operational stabilization'],
['Peak Months', 'July, April', 'Campaign timing opportunity'],
]
findings_table = Table(findings_data, colWidths=[2*inch, 1.5*inch, 2.5*inch])
findings_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), UIDAI_GREEN),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 9),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('BACKGROUND', (0, 1), (-1, -1), LIGHT_GRAY),
('GRID', (0, 0), (-1, -1), 0.5, colors.white),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
('TOPPADDING', (0, 0), (-1, -1), 8),
]))
elements.append(Spacer(1, 0.2*inch))
elements.append(findings_table)
elements.append(Paragraph("Impact", styles['SubHeading']))
elements.append(Paragraph(
"""This analysis enables UIDAI to make evidence-based decisions on resource
allocation, campaign timing, and operational priorities. The dashboard and
this accompanying report together form a replicable framework suitable for
national scaling across all states and union territories.""",
styles['BodyText']
))
elements.append(PageBreak())
return elements
def create_problem_statement(styles):
"""Create the problem statement section."""
elements = []
elements.append(Paragraph("2. Problem Statement & Objectives", styles['SectionHeading']))
elements.append(HRFlowable(width="100%", thickness=1, color=LIGHT_GRAY, spaceAfter=12))
elements.append(Paragraph("2.1 Why Enrolment Analytics Still Matters", styles['SubHeading']))
elements.append(Paragraph(
"""Despite high Aadhaar penetration nationally, significant gaps persist in
specific demographics and geographies. The 0–5 age group represents a
continuously replenishing population segment requiring sustained enrolment
attention. Additionally, regional disparities in enrolment infrastructure
and service delivery quality remain underaddressed.""",
styles['BodyText']
))
elements.append(Paragraph(
"""Understanding enrolment patterns at granular levels (district, pincode,
monthly) enables UIDAI to:""",
styles['BodyText']
))
bullets = [
"Identify underperforming regions requiring targeted intervention",
"Optimize resource allocation based on demand patterns",
"Plan campaigns aligned with seasonal enrollment peaks",
"Detect operational instability before it impacts service delivery",
"Shift strategy from quantity (enrolment) to quality (service excellence)"
]
for bullet in bullets:
elements.append(Paragraph(f"• {bullet}", styles['BulletText']))
elements.append(Paragraph("2.2 Objectives of This Analysis", styles['SubHeading']))
objectives = [
("Objective 1:", "Analyze monthly Aadhaar enrolment trends across Maharashtra to identify growth phases, saturation patterns, and momentum shifts."),
("Objective 2:", "Study age-group dynamics (0–5, 5–17, 18+) to understand demographic composition and guide segment-specific policies."),
("Objective 3:", "Identify district-level and pincode-level enrolment disparities to enable targeted resource deployment."),
("Objective 4:", "Detect seasonality effects to optimize campaign timing and staffing decisions."),
("Objective 5:", "Generate actionable, data-driven policy recommendations for UIDAI leadership."),
]
for title, desc in objectives:
elements.append(Paragraph(f"<b>{title}</b> {desc}", styles['BodyText']))
elements.append(PageBreak())
return elements
def create_methodology(styles):
"""Create the methodology section."""
elements = []
elements.append(Paragraph("3. Dataset & Methodology", styles['SectionHeading']))
elements.append(HRFlowable(width="100%", thickness=1, color=LIGHT_GRAY, spaceAfter=12))
elements.append(Paragraph("3.1 Data Source", styles['SubHeading']))
elements.append(Paragraph(
"""The analysis utilizes the official UIDAI Aadhaar Enrolment Dataset,
an aggregated, non-personal dataset containing monthly enrolment counts
segmented by geography and age group. The data is authoritative and
suitable for policy-level analysis.""",
styles['BodyText']
))
# Dataset specifications table
spec_data = [
['Specification', 'Value'],
['Total Records', '93,184'],
['Monthly Data Points', '101'],
['Geographic Scope', 'Maharashtra'],
['Districts Covered', '53'],
['Pincodes Covered', '1,585'],
['Granularity', 'Monthly'],
]
spec_table = Table(spec_data, colWidths=[2.5*inch, 3*inch])
spec_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), DARK_BLUE),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('BACKGROUND', (0, 1), (-1, -1), LIGHT_GRAY),
('GRID', (0, 0), (-1, -1), 0.5, colors.white),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 0), (-1, -1), 6),
]))
elements.append(spec_table)
elements.append(Spacer(1, 0.2*inch))
elements.append(Paragraph("3.2 Data Schema", styles['SubHeading']))
schema_data = [
['Column', 'Type', 'Description'],
['date', 'Date', 'Month of enrolment (YYYY-MM-DD)'],
['state', 'String', 'State name (Maharashtra)'],
['district', 'String', 'District name'],
['pincode', 'String', 'Postal code'],
['age_0_5', 'Integer', 'Enrolments for age 0–5 years'],
['age_5_17', 'Integer', 'Enrolments for age 5–17 years'],
['age_18_greater', 'Integer', 'Enrolments for age 18+ years'],
]
schema_table = Table(schema_data, colWidths=[1.5*inch, 1*inch, 3.5*inch])
schema_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), UIDAI_GREEN),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 9),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('BACKGROUND', (0, 1), (-1, -1), LIGHT_GRAY),
('GRID', (0, 0), (-1, -1), 0.5, colors.white),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 0), (-1, -1), 6),
]))
elements.append(schema_table)
elements.append(Spacer(1, 0.2*inch))
elements.append(Paragraph("3.3 Preprocessing Steps", styles['SubHeading']))
steps = [
"Column name normalization (lowercase, whitespace trimmed)",
"Date parsing with mixed format handling (day-first, ISO8601 fallback)",
"State filtering to Maharashtra records only",
"Numeric type coercion for age columns with error handling",
"Derived column: total_enrolments = age_0_5 + age_5_17 + age_18_greater",
"Removal of records with missing date, district, or pincode values",
]
for i, step in enumerate(steps, 1):
elements.append(Paragraph(f"{i}. {step}", styles['BulletText']))
elements.append(Paragraph("3.4 Aggregation Logic", styles['SubHeading']))
agg_items = [
("<b>State Monthly:</b>", "Sum of age columns and total enrolments grouped by month"),
("<b>District Monthly:</b>", "Total enrolments grouped by district and month"),
("<b>Pincode Monthly:</b>", "Total enrolments grouped by pincode and month"),
("<b>Seasonality:</b>", "Month-of-year average as ratio to overall mean"),
]
for title, desc in agg_items:
elements.append(Paragraph(f"• {title} {desc}", styles['BulletText']))
elements.append(Paragraph("3.5 Metric Definitions", styles['SubHeading']))
definitions = [
("Saturation Risk", "Calculated as the ratio of the last 3-month rolling average to the rolling 12-month maximum for each district. Districts with a saturation index below 0.6 are flagged as 'at risk,' indicating declining enrolment momentum relative to historical peaks."),
("Volatility", "Measured using the 12-month rolling standard deviation of monthly enrolments per district. Districts exceeding 1.5× the state median standard deviation are flagged as 'volatile,' suggesting operational instability or inconsistent service delivery."),
("Child Momentum", "Represents the share of child enrolments (age 0–5 plus age 5–17) in total enrolments over time. Tracking this metric helps UIDAI monitor the balance between child-focused enrolment drives and adult updates."),
]
for term, defn in definitions:
elements.append(Paragraph(f"<b>{term}:</b> {defn}", styles['BodyText']))
elements.append(PageBreak())
return elements
def create_findings(styles, insights):
"""Create the analytical findings section."""
elements = []
elements.append(Paragraph("4. Analytical Findings", styles['SectionHeading']))
elements.append(HRFlowable(width="100%", thickness=1, color=LIGHT_GRAY, spaceAfter=12))
# 4.1 State Monthly Trend
elements.append(Paragraph("4.1 State Monthly Enrolment Trend", styles['SubHeading']))
elements.append(Paragraph("<b>What Was Analyzed:</b>", styles['BodyText']))
elements.append(Paragraph(
"""Monthly Aadhaar enrolments aggregated at the state level across the entire
observation period, enabling identification of growth phases, plateaus, and
recent momentum shifts.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Key Findings:</b>", styles['BodyText']))
elements.append(Paragraph("• Overall enrolment change: <b>+635.0%</b> from baseline to latest month", styles['BulletText']))
elements.append(Paragraph("• Recent average month-on-month growth: <b>−11.1%</b>", styles['BulletText']))
elements.append(Paragraph("• Pattern suggests transition from rapid expansion to maturation phase", styles['BulletText']))
elements.append(Paragraph("<b>Interpretation:</b>", styles['BodyText']))
elements.append(Paragraph(
"""The substantial cumulative growth indicates successful historical enrolment
drives. However, the negative recent MoM trend signals that Maharashtra may
be approaching enrolment saturation in many areas. This is not necessarily
problematic—it reflects high existing coverage—but necessitates a strategic
pivot from volume to quality.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Why It Matters for UIDAI:</b>", styles['BodyText']))
elements.append(Paragraph(
"""Understanding trend phases enables UIDAI to reallocate resources from
expansion-focused activities to service improvements, biometric updates,
and targeted outreach for remaining uncovered populations.""",
styles['BodyText']
))
elements.append(Spacer(1, 0.15*inch))
# 4.2 Age Group Dynamics
elements.append(Paragraph("4.2 Age Group Dynamics", styles['SubHeading']))
elements.append(Paragraph("<b>What Was Analyzed:</b>", styles['BodyText']))
elements.append(Paragraph(
"""Distribution of enrolments across three age segments: 0–5 years (infants
and toddlers), 5–17 years (school-age children), and 18+ years (adults).
This segmentation reveals demographic priorities in current enrolment patterns.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Key Findings:</b>", styles['BodyText']))
age_data = [
['Age Group', 'Share', 'Absolute Significance'],
['0–5 years', '73.9%', 'Dominant segment; new births driving volume'],
['5–17 years', '23.9%', 'School-age children; education linkage'],
['18+ years', '2.2%', 'Minimal; mostly updates, not new enrolments'],
]
age_table = Table(age_data, colWidths=[1.5*inch, 1*inch, 3.5*inch])
age_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), UIDAI_GREEN),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 9),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('BACKGROUND', (0, 1), (-1, -1), LIGHT_GRAY),
('GRID', (0, 0), (-1, -1), 0.5, colors.white),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 0), (-1, -1), 6),
]))
elements.append(age_table)
elements.append(Spacer(1, 0.1*inch))
elements.append(Paragraph("<b>Interpretation:</b>", styles['BodyText']))
elements.append(Paragraph(
"""The overwhelming dominance of the 0–5 age group (73.9%) indicates that
current enrolment activity is primarily driven by new births and child
registrations. The adult segment's minimal share (2.2%) confirms near-complete
adult coverage, with remaining adult interactions likely being updates rather
than fresh enrolments.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Why It Matters for UIDAI:</b>", styles['BodyText']))
elements.append(Paragraph(
"""UIDAI should optimize enrolment infrastructure for child-specific requirements
(parental consent workflows, simplified biometrics for infants). Partnerships
with health departments (at birth registration) and education departments
(school enrolments) become strategic priorities.""",
styles['BodyText']
))
elements.append(Spacer(1, 0.15*inch))
# 4.3 District-Level Disparities
elements.append(Paragraph("4.3 District-Level Disparities", styles['SubHeading']))
elements.append(Paragraph("<b>What Was Analyzed:</b>", styles['BodyText']))
elements.append(Paragraph(
"""Total enrolments per district over the last 12 months, ranked to identify
top performers and underperforming regions requiring intervention.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Key Findings:</b>", styles['BodyText']))
district_data = [
['Category', 'Districts', '12-Month Enrolments'],
['Top 1', 'Thane', '49,580'],
['Top 2', 'Pune', '38,126'],
['Top 3', 'Nashik', '27,229'],
['Bottom 3', 'Gondia', '38'],
['Bottom 2', 'Ahilyanagar', '24'],
['Bottom 1', 'Hingoli', '1'],
]
district_table = Table(district_data, colWidths=[1.5*inch, 2*inch, 2.5*inch])
district_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), DARK_BLUE),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 9),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('BACKGROUND', (0, 1), (2, 3), colors.HexColor('#d1fae5')),
('BACKGROUND', (0, 4), (2, 6), colors.HexColor('#fee2e2')),
('GRID', (0, 0), (-1, -1), 0.5, colors.white),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 0), (-1, -1), 6),
]))
elements.append(district_table)
elements.append(Spacer(1, 0.1*inch))
elements.append(Paragraph("<b>Interpretation:</b>", styles['BodyText']))
elements.append(Paragraph(
"""The disparity is stark: Thane's 49,580 enrolments versus Hingoli's single
enrolment represents a 49,580× gap. While population differences partially
explain this, such extreme disparities warrant investigation into infrastructure
availability, awareness levels, and operational effectiveness in bottom-performing
districts.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Why It Matters for UIDAI:</b>", styles['BodyText']))
elements.append(Paragraph(
"""These findings directly inform resource reallocation decisions. Districts
like Gondia, Ahilyanagar, and Hingoli require immediate attention—potentially
through mobile enrolment units, awareness campaigns, or infrastructure audits.""",
styles['BodyText']
))
elements.append(PageBreak())
# 4.4 Pincode-Level Distribution
elements.append(Paragraph("4.4 Pincode-Level Distribution", styles['SubHeading']))
elements.append(Paragraph("<b>What Was Analyzed:</b>", styles['BodyText']))
elements.append(Paragraph(
"""Monthly enrolment distribution across 1,585 pincodes, visualized as a
box plot to understand central tendency, spread, and outliers at the
hyper-local level.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Key Findings:</b>", styles['BodyText']))
elements.append(Paragraph("• Median monthly enrolments per pincode: <b>~16</b>", styles['BulletText']))
elements.append(Paragraph("• Distribution is right-skewed with significant outliers", styles['BulletText']))
elements.append(Paragraph("• Many pincodes register near-zero monthly activity", styles['BulletText']))
elements.append(Paragraph("<b>Interpretation:</b>", styles['BodyText']))
elements.append(Paragraph(
"""The low median (16 enrolments) with high variability suggests that most
pincodes have modest, steady enrolment activity, while a few high-density
areas drive bulk volumes. This pattern is expected in a state with mixed
urban-rural demographics.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Why It Matters for UIDAI:</b>", styles['BodyText']))
elements.append(Paragraph(
"""Pincode-level granularity enables micro-targeting of interventions.
Low-activity pincodes within otherwise active districts may indicate
localized infrastructure gaps or awareness deficits.""",
styles['BodyText']
))
elements.append(Spacer(1, 0.15*inch))
# 4.5 Seasonality Index
elements.append(Paragraph("4.5 Seasonality Index", styles['SubHeading']))
elements.append(Paragraph("<b>What Was Analyzed:</b>", styles['BodyText']))
elements.append(Paragraph(
"""Month-of-year enrolment patterns, normalized against the overall mean
to create a seasonality index revealing predictable annual cycles.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Key Findings:</b>", styles['BodyText']))
elements.append(Paragraph("• Peak months: <b>July</b> and <b>April</b>", styles['BulletText']))
elements.append(Paragraph("• These months consistently exceed the annual average", styles['BulletText']))
elements.append(Paragraph("• Pattern likely correlates with school admission cycles", styles['BulletText']))
elements.append(Paragraph("<b>Interpretation:</b>", styles['BodyText']))
elements.append(Paragraph(
"""The April peak aligns with the start of the Indian academic year, when
schools often require Aadhaar for admissions. The July peak may correspond
to post-admission documentation requirements or mid-year drives. Understanding
these cycles enables proactive rather than reactive planning.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Why It Matters for UIDAI:</b>", styles['BodyText']))
elements.append(Paragraph(
"""UIDAI can optimize staffing, equipment deployment, and campaign launches
to coincide with peak demand months, improving throughput and reducing
citizen wait times during high-demand periods.""",
styles['BodyText']
))
elements.append(Spacer(1, 0.15*inch))
# 4.6 Risk Flag Analysis
elements.append(Paragraph("4.6 Risk Flag Analysis", styles['SubHeading']))
elements.append(Paragraph("<b>What Was Analyzed:</b>", styles['BodyText']))
elements.append(Paragraph(
"""Districts were evaluated against three risk indicators: saturation risk,
volatility, and low momentum. These flags identify areas requiring different
types of intervention.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Key Findings:</b>", styles['BodyText']))
risk_data = [
['Risk Type', 'Districts Flagged', 'Action Required'],
['Saturation Risk', '49', 'Shift from enrolment to service quality'],
['Volatile', '22', 'Investigate operational instability'],
['Low Momentum', '0', 'No districts currently flagged'],
]
risk_table = Table(risk_data, colWidths=[1.8*inch, 1.5*inch, 2.7*inch])
risk_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), UIDAI_GREEN),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 9),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('BACKGROUND', (0, 1), (-1, -1), LIGHT_GRAY),
('GRID', (0, 0), (-1, -1), 0.5, colors.white),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 0), (-1, -1), 6),
]))
elements.append(risk_table)
elements.append(Spacer(1, 0.1*inch))
elements.append(Paragraph("<b>Interpretation:</b>", styles['BodyText']))
elements.append(Paragraph(
"""The high saturation risk count (49 of 53 districts) confirms that
Maharashtra is in a mature enrolment phase. The 22 volatile districts
warrant attention—volatility may stem from inconsistent centre operations,
staffing issues, or equipment availability. Notably, no districts show
low momentum, indicating baseline activity is maintained statewide.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Why It Matters for UIDAI:</b>", styles['BodyText']))
elements.append(Paragraph(
"""Risk flags enable differentiated strategies: saturated districts need
service quality investments; volatile districts need operational audits;
low-momentum districts (if any emerged) would need demand stimulation.""",
styles['BodyText']
))
elements.append(Spacer(1, 0.15*inch))
# 4.7 Child Enrolment Momentum
elements.append(Paragraph("4.7 Child Enrolment Momentum", styles['SubHeading']))
elements.append(Paragraph("<b>What Was Analyzed:</b>", styles['BodyText']))
elements.append(Paragraph(
"""The share of child enrolments (0–5 plus 5–17) in total monthly enrolments,
tracked over time to understand trajectory and sustainability of child-focused
enrolment activity.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Key Findings:</b>", styles['BodyText']))
elements.append(Paragraph("• Latest child share: <b>93.9%</b>", styles['BulletText']))
elements.append(Paragraph("• Child enrolments dominate current activity", styles['BulletText']))
elements.append(Paragraph("• Adult enrolments are primarily updates, not new registrations", styles['BulletText']))
elements.append(Paragraph("<b>Interpretation:</b>", styles['BodyText']))
elements.append(Paragraph(
"""The near-complete dominance of child enrolments (93.9%) confirms that
Maharashtra's Aadhaar ecosystem is now primarily serving the youngest
population segments. This is a natural outcome of high adult coverage and
reflects the continuous influx of newborns requiring registration.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Why It Matters for UIDAI:</b>", styles['BodyText']))
elements.append(Paragraph(
"""Infrastructure, training, and processes should be optimized for child
enrolment workflows. Partnerships with hospitals (birth registration) and
schools (education linkage) become strategically paramount. Biometric
update facilities for growing children should also be planned.""",
styles['BodyText']
))
elements.append(PageBreak())
return elements
def create_recommendations(styles, recs):
"""Create the policy recommendations section."""
elements = []
elements.append(Paragraph("5. Policy Recommendations", styles['SectionHeading']))
elements.append(HRFlowable(width="100%", thickness=1, color=LIGHT_GRAY, spaceAfter=12))
elements.append(Paragraph(
"""The following recommendations are derived directly from the analytical
findings presented in this report. Each recommendation is mapped to specific
data-driven insights and is framed for operational implementation.""",
styles['BodyText']
))
recommendations = [
{
'title': 'Prioritize Biometric Infrastructure for Children',
'finding': 'Child share at 93.9%; age 0–5 comprises 73.9% of enrolments',
'action': 'Invest in child-friendly biometric capture equipment (iris scanners suitable for infants, simplified consent workflows). Partner with maternity hospitals for at-birth Aadhaar registration.',
'impact': 'Reduces enrolment friction for the dominant demographic; improves citizen experience from birth.'
},
{
'title': 'Deploy Mobile Units in Underperforming Districts',
'finding': 'Gondia (38), Ahilyanagar (24), Hingoli (1) show critically low enrolments',
'action': 'Deploy mobile enrolment units on scheduled rotations. Conduct awareness campaigns in local languages. Audit existing infrastructure for functionality.',
'impact': 'Addresses geographic equity; ensures no district is left behind in Aadhaar coverage.'
},
{
'title': 'Align Campaigns with Seasonal Peaks',
'finding': 'Peak enrolment months: July and April (aligned with academic cycles)',
'action': 'Schedule major outreach campaigns, staffing augmentation, and equipment maintenance before April and July. Coordinate with education departments for school-based drives.',
'impact': 'Maximizes return on campaign investment; reduces citizen wait times during high-demand periods.'
},
{
'title': 'Monitor Volatile Districts for Operational Stability',
'finding': '22 districts flagged for high enrolment volatility (e.g., Jalgaon, Jalna, Ahmadnagar)',
'action': 'Conduct operational audits in flagged districts. Investigate staffing consistency, equipment uptime, and centre availability. Implement real-time monitoring dashboards.',
'impact': 'Stabilizes service delivery; prevents sudden capacity shortfalls.'
},
{
'title': 'Shift Focus to Service Quality in Saturated Districts',
'finding': '49 of 53 districts show saturation risk indicators',
'action': 'Reallocate resources from enrolment expansion to update services, biometric refresh, and grievance resolution. Focus on turnaround time and citizen satisfaction metrics.',
'impact': 'Improves service quality for existing Aadhaar holders; prepares ecosystem for biometric update waves.'
},
]
for i, rec in enumerate(recommendations, 1):
elements.append(Paragraph(f"<b>Recommendation {i}: {rec['title']}</b>", styles['SubHeading']))
elements.append(Paragraph(f"<b>Based on Finding:</b> {rec['finding']}", styles['BodyText']))
elements.append(Paragraph(f"<b>Recommended Action:</b> {rec['action']}", styles['BodyText']))
elements.append(Paragraph(f"<b>Expected Impact:</b> {rec['impact']}", styles['BodyText']))
elements.append(Spacer(1, 0.1*inch))
elements.append(PageBreak())
return elements
def create_deployment_section(styles):
"""Create the deployment overview section."""
elements = []
elements.append(Paragraph("6. Dashboard & Deployment Overview", styles['SectionHeading']))
elements.append(HRFlowable(width="100%", thickness=1, color=LIGHT_GRAY, spaceAfter=12))
elements.append(Paragraph("6.1 Technical Architecture", styles['SubHeading']))
elements.append(Paragraph(
"""The analytics dashboard is built on a modern, scalable architecture
designed for reliability and ease of maintenance.""",
styles['BodyText']
))
arch_data = [
['Component', 'Technology', 'Purpose'],
['Backend', 'Flask (Python)', 'Web framework for routing and templating'],
['Data Processing', 'Pandas', 'Data manipulation and aggregation'],
['Visualization', 'Plotly', 'Interactive charts with dark theme'],
['Hosting', 'Azure App Service', 'Scalable, managed cloud hosting'],
['WSGI Server', 'Gunicorn', 'Production-grade Python server'],
]
arch_table = Table(arch_data, colWidths=[1.5*inch, 1.8*inch, 2.7*inch])
arch_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), DARK_BLUE),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 9),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('BACKGROUND', (0, 1), (-1, -1), LIGHT_GRAY),
('GRID', (0, 0), (-1, -1), 0.5, colors.white),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 0), (-1, -1), 6),
]))
elements.append(arch_table)
elements.append(Spacer(1, 0.2*inch))
elements.append(Paragraph("6.2 Why This Approach", styles['SubHeading']))
benefits = [
("<b>Reliability:</b>", "Azure App Service provides 99.95% SLA, automatic scaling, and managed infrastructure—critical for government-grade applications."),
("<b>Security:</b>", "HTTPS by default, managed certificates, and Azure's compliance certifications (ISO 27001, SOC 2) align with government data handling requirements."),
("<b>Scalability:</b>", "The stateless Flask architecture can scale horizontally to handle increased load during peak periods or national rollout."),
("<b>Maintainability:</b>", "Python-based stack ensures broad developer availability; modular code structure enables easy updates and extensions."),
("<b>Cost Efficiency:</b>", "Pay-per-use Azure pricing minimizes operational costs while maintaining enterprise-grade capabilities."),
]
for title, desc in benefits:
elements.append(Paragraph(f"• {title} {desc}", styles['BulletText']))
elements.append(Paragraph("6.3 Dashboard Features", styles['SubHeading']))
features = [
"Dark mode interface for reduced eye strain during extended analysis",
"Interactive Plotly charts with zoom, pan, and image export",
"Collapsible methodology section for evaluator reference",
"Data-driven policy recommendations (dynamically generated)",
"Responsive design for desktop and tablet viewing",
"Minimal mode bar buttons for cleaner presentation",
]
for feature in features:
elements.append(Paragraph(f"• {feature}", styles['BulletText']))
elements.append(PageBreak())
return elements
def create_transparency_section(styles):
"""Create the data availability section."""
elements = []
elements.append(Paragraph("7. Data Availability & Transparency", styles['SectionHeading']))
elements.append(HRFlowable(width="100%", thickness=1, color=LIGHT_GRAY, spaceAfter=12))
elements.append(Paragraph(
"""In the spirit of transparency and reproducibility, the following materials
are provided alongside this report:""",
styles['BodyText']
))
items = [
("<b>Live Dashboard:</b>", "The interactive analytics dashboard is deployed and accessible for real-time exploration of the findings presented in this report."),
("<b>Source Dataset:</b>", "The original CSV dataset (Aadhar Enrolment Dataset.csv) is provided for independent verification and extended analysis."),
("<b>Source Code:</b>", "The complete Flask application, data pipeline, and report generation code are included in the submission package."),
("<b>Reproducibility:</b>", "All preprocessing, aggregation, and visualization steps are documented in the codebase. Running the application with the provided dataset will reproduce identical results."),
]
for title, desc in items:
elements.append(Paragraph(f"• {title} {desc}", styles['BulletText']))
elements.append(Spacer(1, 0.2*inch))
elements.append(Paragraph("Submission Package Contents", styles['SubHeading']))
package_data = [
['File/Folder', 'Description'],
['app.py', 'Flask application entry point'],
['data_pipeline.py', 'Data processing and visualization logic'],
['generate_report.py', 'PDF report generator (this document)'],
['templates/', 'HTML templates for dashboard'],
['static/', 'CSS stylesheets'],
['Dataset/', 'Source data files'],
['requirements.txt', 'Python dependencies'],
['README.md', 'Setup and deployment instructions'],
]
package_table = Table(package_data, colWidths=[2*inch, 4*inch])
package_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), UIDAI_GREEN),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 9),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('BACKGROUND', (0, 1), (-1, -1), LIGHT_GRAY),
('GRID', (0, 0), (-1, -1), 0.5, colors.white),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 0), (-1, -1), 6),
]))
elements.append(package_table)
elements.append(PageBreak())
return elements
def create_project_deployment_access_section(styles):
"""Add a judge-ready deployment & access section near the end."""
elements = []
elements.append(Paragraph("Project Deployment and Access", styles['SectionHeading']))
elements.append(HRFlowable(width="100%", thickness=1, color=LIGHT_GRAY, spaceAfter=12))
elements.append(Paragraph(
"""The analytical solution developed as part of this project has been deployed as a live, interactive web dashboard to demonstrate real-time usability and practical applicability.""",
styles['BodyText']
))
elements.append(Paragraph(
"""The dashboard enables stakeholders to explore Aadhaar enrolment trends, district-level disparities, seasonality patterns, and policy-relevant indicators through visual analytics and explanations.""",
styles['BodyText']
))
elements.append(Paragraph("<b>Deployed Dashboard URL:</b> https://uidai-maharashtra-dashboard.azurewebsites.net", styles['BodyText']))
elements.append(Paragraph(
"""The deployment ensures transparency, reproducibility, and ease of evaluation, allowing reviewers and decision-makers to directly interact with the analysis rather than relying solely on static outputs.""",
styles['BodyText']
))
# Optional, high-impact note on scalability
elements.append(Paragraph(
"The solution architecture is scalable and can be extended to other states or national-level datasets with minimal configuration changes.",
styles['BodyText']
))
elements.append(PageBreak())
return elements