first commit

This commit is contained in:
2026-05-21 08:40:24 -04:00
commit b084545275
711 changed files with 3659856 additions and 0 deletions

View File

@@ -0,0 +1,366 @@
import os
from pathlib import Path
from collections import defaultdict
import argparse
from functools import partial
from docx import Document
from docx.shared import Pt, Inches
from docx.enum.text import WD_ALIGN_PARAGRAPH
from pasbdc_word_library import WordDocumentBuilder, PageConfig #pyright:ignore
def parse_and_organize_images(source_folder):
"""
Parses filenames and groups them by Center and Fiscal Year,
keyed by the specific chart tag.
"""
source_path = Path(source_folder)
# Structure: organization[center][fiscal_year][chart_tag] = full_path
# We change the inner default from 'list' to 'dict'
organization = defaultdict(lambda: defaultdict(dict))
for image_path in source_path.glob('*.png'):
filename_stem = image_path.stem
parts = filename_stem.split('_')
if len(parts) < 3:
continue
center = parts[0]
fiscal_year = parts[-1]
# The key you will use to access this specific file later
chart_tag = "_".join(parts[1:-1])
# Map the tag directly to the path
organization[center][fiscal_year][chart_tag] = str(image_path)
return organization
def single_year_nbs_page(
builder: WordDocumentBuilder,
pie_chart_path:str,
grouped_bar_path:str,
center_name:str,
fiscal_year_tag:str,
add_documentation_note:bool
):
# 1. Setup Margins
for section in builder.doc.sections:
section.top_margin = Inches(0.5)
section.bottom_margin = Inches(0.5)
section.left_margin = Inches(0.5)
section.right_margin = Inches(0.5)
# 2. Add Title
title = builder.doc.add_heading(f'{builder.current_section}. {center_name} New Business Starts Attribution Analysis {fiscal_year_tag}', level=1)
title.alignment = WD_ALIGN_PARAGRAPH.CENTER #pyright:ignore
# 3. Create a 2x2 Table for Side-by-Side Layout
# Row 0 = Images, Row 1 = Captions
table = builder.doc.add_table(rows=2, cols=2)
table.autofit = False
# --- LEFT COLUMN (Pie Chart) ---
# Image (Row 0, Col 0)
cell_pie_img = table.cell(0, 0)
p_pie = cell_pie_img.paragraphs[0]
p_pie.alignment = WD_ALIGN_PARAGRAPH.CENTER
# Width set to 3.2 inches to ensure two fit on a 7.5 inch wide printable area
p_pie.add_run().add_picture(pie_chart_path, width=Inches(3.2))
# Caption (Row 1, Col 0)
cell_pie_cap = table.cell(1, 0)
p_pie_cap = cell_pie_cap.paragraphs[0]
p_pie_cap.alignment = WD_ALIGN_PARAGRAPH.CENTER
builder.figure_number += 1
p_pie_cap.add_run(f"Figure {builder.current_section}.{builder.figure_number} shows the proportion of new business start milestones that fell into each documentation level").bold = True
# --- RIGHT COLUMN (Bar Chart) ---
# Image (Row 0, Col 1)
cell_bar_img = table.cell(0, 1)
p_bar = cell_bar_img.paragraphs[0]
p_bar.alignment = WD_ALIGN_PARAGRAPH.CENTER
p_bar.add_run().add_picture(grouped_bar_path, width=Inches(3.2))
# Caption (Row 1, Col 1)
cell_bar_cap = table.cell(1, 1)
p_bar_cap = cell_bar_cap.paragraphs[0]
p_bar_cap.alignment = WD_ALIGN_PARAGRAPH.CENTER
builder.figure_number += 1
p_bar_cap.add_run(f"Figure {builder.current_section}.{builder.figure_number} shows how many of each documentation level were a part of each attribution source").bold = True
# 4. Add Spacing after table
builder.doc.add_paragraph()
if add_documentation_note:
# 6. Add the "NOTE" section (Definitions)
builder.doc.add_heading("NOTE: Data is based on business start impact date and new business start milestone records on the client file, extracted as of 1/27/26.", level=4)
builder.doc.add_heading('In situations where multiple milestone records existed for a client, a de-duplication / merger script was utilized for data analysis (no changes in NS).The script created some unique combination types (e.g., "Email from Client,Requested on Ecenter"); these combined types were left as separate categories to more easily identify potential data entry training opportunities.', level=4)
builder.doc.add_heading("NOTE 2: Documentation levels were determined as follows:", level=4)
note_paragraph = builder.doc.add_paragraph()
note_paragraph.add_run("No documentation (red) - Will NOT be submitted to Nexus\n").bold = True
note_paragraph.add_run("\t1. Attribution Source was blank, OR \n\t2. Attribution source was 'Requested on eCenter'")
note_paragraph.add_run("\n\n")
note_paragraph.add_run("Affirmation Missing (yellow) - Will NOT be submitted to Nexus\n").bold = True
note_paragraph.add_run("\t1. Attribution source field is a non-blank, non-'Requested on eCenter' attribution source, BUT\n\t2. Affirmation Statement was blank")
note_paragraph.add_run("\n\n")
note_paragraph.add_run("Documented (green) - Will be submitted to Nexus as long as 'Director Verified' is checked\n").bold = True
note_paragraph.add_run("\t1. There is a non-blank, non-'Requested on eCenter' attribution source AND\n\t2. Affirmation Statement was non-blank")
note_paragraph.add_run("\n")
note_paragraph.add_run("\tNOTE: If the attribution source is eCenter, then no value is required in the affirmation column.")
note_paragraph.add_run("\n\n")
def single_year_funding_page(
builder: WordDocumentBuilder,
pie_chart_path:str,
grouped_bar_path:str,
center_name:str,
fiscal_year_tag:str,
add_documentation_note:bool
):
# 1. Setup Margins
for section in builder.doc.sections:
section.top_margin = Inches(0.5)
section.bottom_margin = Inches(0.5)
section.left_margin = Inches(0.5)
section.right_margin = Inches(0.5)
# 2. Add Title
title = builder.doc.add_heading(f'{builder.current_section}. {center_name} Capital Transaction Attribution Analysis {fiscal_year_tag}', level=1)
title.alignment = WD_ALIGN_PARAGRAPH.CENTER #pyright:ignore
# 3. Create a 2x2 Table for Side-by-Side Layout
# Row 0 = Images, Row 1 = Captions
table = builder.doc.add_table(rows=2, cols=2)
table.autofit = False
# --- LEFT COLUMN (Pie Chart) ---
# Image (Row 0, Col 0)
cell_pie_img = table.cell(0, 0)
p_pie = cell_pie_img.paragraphs[0]
p_pie.alignment = WD_ALIGN_PARAGRAPH.CENTER
# Width set to 3.2 inches to ensure two fit on a 7.5 inch wide printable area
p_pie.add_run().add_picture(pie_chart_path, width=Inches(3.2))
# Caption (Row 1, Col 0)
cell_pie_cap = table.cell(1, 0)
p_pie_cap = cell_pie_cap.paragraphs[0]
p_pie_cap.alignment = WD_ALIGN_PARAGRAPH.CENTER
builder.figure_number += 1
p_pie_cap.add_run(f"Figure {builder.current_section}.{builder.figure_number} shows the proportion of funding milestones that fell into each documentation level").bold = True
# --- RIGHT COLUMN (Bar Chart) ---
# Image (Row 0, Col 1)
cell_bar_img = table.cell(0, 1)
p_bar = cell_bar_img.paragraphs[0]
p_bar.alignment = WD_ALIGN_PARAGRAPH.CENTER
p_bar.add_run().add_picture(grouped_bar_path, width=Inches(3.2))
# Caption (Row 1, Col 1)
cell_bar_cap = table.cell(1, 1)
p_bar_cap = cell_bar_cap.paragraphs[0]
p_bar_cap.alignment = WD_ALIGN_PARAGRAPH.CENTER
builder.figure_number += 1
p_bar_cap.add_run(f"Figure {builder.current_section}.{builder.figure_number} shows how many of each documentation level were a part of each attribution source").bold = True
# 4. Add Spacing after table
builder.doc.add_paragraph()
if add_documentation_note:
builder.doc.add_heading("NOTE: Data is based on those impacts showing up in the q1 scorecard as of 1/27/26", level=4)
builder.doc.add_heading("NOTE 2: Documentation levels were determined as follows:", level=4)
note_paragraph = builder.doc.add_paragraph()
note_paragraph.add_run("No documentation (red) - Will NOT be submitted to Nexus\n").bold = True
note_paragraph.add_run("\t1. Attribution Source was blank, OR \n\t2. Attribution source was 'Requested on eCenter'")
note_paragraph.add_run("\n\n")
note_paragraph.add_run("Affirmation Missing (yellow) - Will NOT be submitted to Nexus\n").bold = True
note_paragraph.add_run("\t1. Attribution source field is a non-blank, non-'Requested on eCenter' attribution source, BUT\n\t2. Affirmation Statement was blank")
note_paragraph.add_run("\n\n")
note_paragraph.add_run("Documented (green) - Will be submitted to Nexus as long as 'Director Verified' is checked\n").bold = True
note_paragraph.add_run("\t1. There is a non-blank, non-'Requested on eCenter' attribution source AND\n\t2. Affirmation Statement was non-blank")
note_paragraph.add_run("\n")
note_paragraph.add_run("\tNOTE: If the attribution source is eCenter, then no value is required in the affirmation column.")
note_paragraph.add_run("\n\n")
def process_documents(organization_data, out_path):
network_wide_nbs = []
network_wide_funding = []
is_first_network_funding_page = True
is_first_network_nbs_page = True
for fiscal_year, chart_map in sorted(organization_data["network"].items()):
path_funding_grouped = chart_map.get('funding_attribution_grouped')
if not path_funding_grouped:
print(f" WARNING: Missing 'funding_attribution_grouped' for network wide fy {fiscal_year}")
else:
print(path_funding_grouped)
path_funding_pie = chart_map.get('funding_attribution_pie')
if not path_funding_pie:
print(f" WARNING: Missing 'funding_attribution_pie' for network wide fy {fiscal_year}")
else:
print(path_funding_pie)
path_nbs_grouped = chart_map.get('nbs_attribution_grouped')
if not path_nbs_grouped:
print(f" WARNING: Missing 'nbs_attribution_grouped' for network wide fy {fiscal_year}")
else:
print(path_nbs_grouped)
path_nbs_pie = chart_map.get('nbs_attribution_pie')
if not path_nbs_pie:
print(f" WARNING: Missing 'nbs_attribution_pie' for network wide fy {fiscal_year}")
else:
print(path_nbs_pie)
if path_nbs_pie and path_nbs_grouped:
nbs_page = partial(single_year_nbs_page,
pie_chart_path=path_nbs_pie,
grouped_bar_path=path_nbs_grouped,
center_name="Network Wide",
fiscal_year_tag=fiscal_year,
add_documentation_note=True if is_first_network_nbs_page else False
)
network_wide_nbs.append(PageConfig(page_function=nbs_page, add_page_break=True if is_first_network_nbs_page else False))
if is_first_network_nbs_page:
is_first_network_nbs_page = False
else:
print(f" SKIPPING: Network Wide NBS page for {fiscal_year} due to missing graphs.")
if path_funding_pie and path_funding_grouped:
funding_page = partial(single_year_funding_page,
pie_chart_path=path_funding_pie,
grouped_bar_path=path_funding_grouped,
center_name="Network Wide",
fiscal_year_tag=fiscal_year,
add_documentation_note=True if is_first_network_funding_page else False
)
network_wide_funding.append(PageConfig(page_function=funding_page, add_page_break=True if is_first_network_funding_page else False))
if is_first_network_funding_page:
is_first_network_funding_page = False
else:
print(f" SKIPPING: Network Wide Funding page for {fiscal_year} due to missing graphs.")
nbs_pages = []
funding_pages = []
for center, fy_groups in sorted(organization_data.items()):
if center == "network":
continue
# Used to store completed PageConfig objects
center_nbs_pages = []
# Used to tell the script whether to place the documentation level notes or not
is_first_nbs_page = True
center_funding_pages = []
is_first_funding_page = True
for fy, chart_map in sorted(fy_groups.items()):
print(f"--- Creating Doc for {center} {fy} ---")
# Create the funding pages
path_funding_grouped = chart_map.get('funding_attribution_grouped')
if not path_funding_grouped:
print(f" WARNING: Missing 'funding_attribution_grouped' for {center}")
else:
print(path_funding_grouped)
# Example: Getting the 'pie' chart
path_funding_pie = chart_map.get('funding_attribution_pie')
if not path_funding_pie:
print(f" WARNING: Missing 'funding_attribution_pie' for {center}")
else:
print(path_funding_pie)
# Create the new busiuness start pages
path_nbs_grouped = chart_map.get('nbs_attribution_grouped')
if not path_nbs_grouped:
print(f" WARNING: Missing 'nbs_attribution_grouped' for {center}")
else:
print(path_nbs_grouped)
# Example: Getting the 'pie' chart
path_nbs_pie = chart_map.get('nbs_attribution_pie')
if not path_nbs_pie:
print(f" WARNING: Missing 'nbs_attribution_pie' for {center}")
else:
print(path_nbs_pie)
if path_nbs_pie and path_nbs_grouped:
nbs_page = partial(single_year_nbs_page,
pie_chart_path=path_nbs_pie,
grouped_bar_path=path_nbs_grouped,
center_name=center,
fiscal_year_tag=fy,
add_documentation_note=True if is_first_nbs_page else False
)
nbs_pages.append(PageConfig(page_function=nbs_page, add_page_break=True if is_first_nbs_page else False))
center_nbs_pages.append(PageConfig(page_function=nbs_page, add_page_break=True if is_first_nbs_page else False))
if is_first_nbs_page:
is_first_nbs_page = False
else:
print(f" SKIPPING: NBS page for {center} {fy} due to missing graphs.")
if path_funding_pie and path_funding_grouped:
funding_page = partial(single_year_funding_page,
pie_chart_path=path_funding_pie,
grouped_bar_path=path_funding_grouped,
center_name=center,
fiscal_year_tag=fy,
add_documentation_note=True if is_first_funding_page else False
)
funding_pages.append(PageConfig(page_function=funding_page, add_page_break=True if is_first_funding_page else False))
center_funding_pages.append(PageConfig(page_function=funding_page, add_page_break=True if is_first_funding_page else False))
if is_first_funding_page:
is_first_funding_page = False
else:
print(f" SKIPPING: Funding page for {center} {fy} due to missing graphs.")
center_builder = WordDocumentBuilder()
center_builder.create_document(
center_nbs_pages + network_wide_nbs + center_funding_pages + network_wide_funding,
os.path.join(out_path, f"{center}_MilestoneAttributionStudy.docx"),
)
master_builder = WordDocumentBuilder()
master_builder.create_document(
nbs_pages + network_wide_nbs + funding_pages + network_wide_funding,
os.path.join(out_path, f"AllCenters_MilestoneAttributionStudy.docx")
)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--images",
type=str,
required=True,
help="The folder containing all of the report images for the milestones report")
parser.add_argument("--out",
type=str,
required=True,
help="The folder to place the generated reports into")
args = parser.parse_args()
if not os.path.exists(args.out):
os.makedirs(args.out)
# Run the parsing logic
grouped_image_data = parse_and_organize_images(args.images)
# print(grouped_image_data)
# Run the document generation loops
process_documents(grouped_image_data, args.out)