174 lines
7.0 KiB
Python
174 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Build the ABSLI policy-schedule / welcome-kit .docx template for app 385.
|
|
|
|
Dependency-free: writes a minimal-but-valid WordprocessingML package by hand.
|
|
Each {{placeholder}} is emitted as ONE contiguous run so docx-service's
|
|
placeholder substitution resolves it cleanly (it also handles fragmented runs,
|
|
but single runs are the safe path).
|
|
|
|
Output: ai-employee-leadtopolicy/policy_schedule.docx
|
|
Upload it via Studio > Templates (app 385); docx-service substitutes the
|
|
{{vars}} mapped in the 44d generateDoc node.
|
|
|
|
Placeholders (all sourced from companion tbl_wf_385_lead_to_policy at 44d):
|
|
policy_number lead_name plan_name sum_assured premium_amount
|
|
premium_frequency policy_term commencement_date maturity_date
|
|
advisor_name issue_date
|
|
"""
|
|
import os
|
|
import zipfile
|
|
from xml.sax.saxutils import escape
|
|
|
|
OUT = os.path.join(os.path.dirname(__file__), "..", "..",
|
|
".supacode", "repos", "sm2", "lead-to-policy",
|
|
"ai-employee-leadtopolicy", "policy_schedule.docx")
|
|
# When run from the aria-console scripts/ dir the relative hop above is brittle;
|
|
# allow an explicit override.
|
|
OUT = os.environ.get("POLICY_DOCX_OUT", OUT)
|
|
|
|
NSW = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
|
|
# ---- run / paragraph helpers -------------------------------------------------
|
|
|
|
def run(text, *, bold=False, size=None, color=None):
|
|
rpr = ""
|
|
props = []
|
|
if bold:
|
|
props.append("<w:b/>")
|
|
if size: # half-points
|
|
props.append(f'<w:sz w:val="{size}"/><w:szCs w:val="{size}"/>')
|
|
if color:
|
|
props.append(f'<w:color w:val="{color}"/>')
|
|
if props:
|
|
rpr = "<w:rPr>" + "".join(props) + "</w:rPr>"
|
|
return (f'<w:r>{rpr}<w:t xml:space="preserve">{escape(text)}</w:t></w:r>')
|
|
|
|
|
|
def para(runs, *, align=None, space_after=120, shading=None):
|
|
ppr_bits = []
|
|
if align:
|
|
ppr_bits.append(f'<w:jc w:val="{align}"/>')
|
|
if shading:
|
|
ppr_bits.append(f'<w:shd w:val="clear" w:color="auto" w:fill="{shading}"/>')
|
|
ppr_bits.append(f'<w:spacing w:after="{space_after}"/>')
|
|
ppr = "<w:pPr>" + "".join(ppr_bits) + "</w:pPr>"
|
|
body = runs if isinstance(runs, str) else "".join(runs)
|
|
return f"<w:p>{ppr}{body}</w:p>"
|
|
|
|
|
|
def cell(text_runs, *, w=4500, fill=None):
|
|
shd = f'<w:shd w:val="clear" w:color="auto" w:fill="{fill}"/>' if fill else ""
|
|
tcpr = f'<w:tcPr><w:tcW w:w="{w}" w:type="dxa"/>{shd}</w:tcPr>'
|
|
return f"<w:tc>{tcpr}{para(text_runs, space_after=40)}</w:tc>"
|
|
|
|
|
|
def kv_row(label, placeholder):
|
|
left = cell([run(label, bold=True, size=20, color="475569")], w=3600, fill="F1F5F9")
|
|
right = cell([run("{{" + placeholder + "}}", size=20, color="0F172A")], w=5400)
|
|
return f"<w:tr>{left}{right}</w:tr>"
|
|
|
|
|
|
def table(rows):
|
|
borders = (
|
|
"<w:tblBorders>"
|
|
'<w:top w:val="single" w:sz="4" w:color="E2E8F0"/>'
|
|
'<w:left w:val="single" w:sz="4" w:color="E2E8F0"/>'
|
|
'<w:bottom w:val="single" w:sz="4" w:color="E2E8F0"/>'
|
|
'<w:right w:val="single" w:sz="4" w:color="E2E8F0"/>'
|
|
'<w:insideH w:val="single" w:sz="4" w:color="E2E8F0"/>'
|
|
'<w:insideV w:val="single" w:sz="4" w:color="E2E8F0"/>'
|
|
"</w:tblBorders>"
|
|
)
|
|
tblpr = f'<w:tblPr><w:tblW w:w="9000" w:type="dxa"/>{borders}</w:tblPr>'
|
|
grid = '<w:tblGrid><w:gridCol w:w="3600"/><w:gridCol w:w="5400"/></w:tblGrid>'
|
|
return f"<w:tbl>{tblpr}{grid}{''.join(rows)}</w:tbl>"
|
|
|
|
|
|
# ---- document body -----------------------------------------------------------
|
|
|
|
body_parts = [
|
|
para([run("Aditya Birla Sun Life Insurance", bold=True, size=32, color="9A1F40")],
|
|
align="center", space_after=40),
|
|
para([run("Policy Schedule & Welcome Kit", bold=True, size=24, color="475569")],
|
|
align="center", space_after=240),
|
|
|
|
para([run("Dear ", size=22),
|
|
run("{{lead_name}}", bold=True, size=22),
|
|
run(",", size=22)]),
|
|
para([run(
|
|
"Welcome to the Aditya Birla Sun Life Insurance family. We are delighted to "
|
|
"confirm that your policy has been issued. Please find your policy schedule "
|
|
"below. Kindly retain this document for your records.", size=22)],
|
|
space_after=240),
|
|
|
|
para([run("Policy Schedule", bold=True, size=24, color="9A1F40")], space_after=120),
|
|
table([
|
|
kv_row("Policy Number", "policy_number"),
|
|
kv_row("Plan", "plan_name"),
|
|
kv_row("Life Assured", "lead_name"),
|
|
kv_row("Sum Assured (INR)", "sum_assured"),
|
|
kv_row("Premium (INR)", "premium_amount"),
|
|
kv_row("Premium Frequency", "premium_frequency"),
|
|
kv_row("Policy Term (years)", "policy_term"),
|
|
kv_row("Risk Commencement Date", "commencement_date"),
|
|
kv_row("Maturity Date", "maturity_date"),
|
|
kv_row("Date of Issue", "issue_date"),
|
|
kv_row("Your Advisor", "advisor_name"),
|
|
]),
|
|
para([run("", size=22)], space_after=200),
|
|
|
|
para([run(
|
|
"This is a computer-generated welcome document. The benefits, terms and "
|
|
"conditions are governed by the policy contract. For any assistance, please "
|
|
"contact your advisor named above or reach us at care.abslifeinsurance@adityabirlacapital.com.",
|
|
size=18, color="64748B")], space_after=240),
|
|
|
|
para([run("Warm regards,", size=22)], space_after=40),
|
|
para([run("Team Aditya Birla Sun Life Insurance", bold=True, size=22)]),
|
|
]
|
|
|
|
document_xml = (
|
|
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
|
f'<w:document xmlns:w="{NSW}">'
|
|
"<w:body>"
|
|
+ "".join(body_parts)
|
|
+ '<w:sectPr><w:pgSz w:w="11906" w:h="16838"/>'
|
|
'<w:pgMar w:top="1134" w:right="1134" w:bottom="1134" w:left="1134"'
|
|
' w:header="709" w:footer="709" w:gutter="0"/></w:sectPr>'
|
|
"</w:body></w:document>"
|
|
)
|
|
|
|
CONTENT_TYPES = (
|
|
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
|
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
|
|
'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
|
|
'<Default Extension="xml" ContentType="application/xml"/>'
|
|
'<Override PartName="/word/document.xml" '
|
|
'ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>'
|
|
"</Types>"
|
|
)
|
|
|
|
RELS = (
|
|
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
|
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
|
|
'<Relationship Id="rId1" '
|
|
'Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" '
|
|
'Target="word/document.xml"/>'
|
|
"</Relationships>"
|
|
)
|
|
|
|
DOC_RELS = (
|
|
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
|
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
|
|
"</Relationships>"
|
|
)
|
|
|
|
os.makedirs(os.path.dirname(OUT), exist_ok=True)
|
|
with zipfile.ZipFile(OUT, "w", zipfile.ZIP_DEFLATED) as z:
|
|
z.writestr("[Content_Types].xml", CONTENT_TYPES)
|
|
z.writestr("_rels/.rels", RELS)
|
|
z.writestr("word/document.xml", document_xml)
|
|
z.writestr("word/_rels/document.xml.rels", DOC_RELS)
|
|
|
|
print("wrote", os.path.abspath(OUT))
|