ContactPoint360 is a global customer-experience and business-process outsourcing company with more than 5,000 employees across 12 strategic centers, operating in over 31 languages. It supports enterprise and Fortune 500 clients in aviation, financial services, media, healthcare, energy, and tech.

I was 17, during a finance and workforce management internship there, when I ended up building the system that replaced an outsourced compliance process covering 2,000+ employees.

The problem

BIR Form 2316 is a Philippine tax certificate every employer prepares for every employee: compensation, taxes withheld, the usual. Of ContactPoint360's global headcount, around 2,000 are based in Cebu, Philippines. Preparing one form is easy. Preparing 2,000+ individualized, accurate ones by hand is not, so the company outsourced it, which meant recurring cost, slow turnaround, and dependence on a vendor for something that followed the same structure every year.

Breaking it down

The "complicated" tax form was a fixed template with fixed field positions. Every employee already had a row in a spreadsheet. So the task reduced to one mapping: employee ID to spreadsheet row to form field to fixed coordinate on the PDF. It's a data-mapping problem, not a document-generation one.

How one employee's form gets built

1Employee ID
2Spreadsheet row
3Matched to form field
4Fixed (x, y) coordinate
5Drawn onto the PDF

The same five steps, repeated once per field, per employee, 2,000+ times over.

Why not AI or a bigger system?

The data was structured and the output was identical every time. A deterministic problem needs a deterministic solution, not a probabilistic one. I used pandas to read the payroll spreadsheet, reportlab to draw a text overlay for each employee, and PyPDF2 to merge that overlay onto the actual BIR 2316 template.

Reading the source data

df_data = pd.read_excel(EXCEL_FILE)

df_address = pd.read_excel(ADDRESS_FILE)
df_address['TIN_clean'] = df_address['TIN'].astype(str).str.replace("'", "").str.strip()
tin_to_address = dict(zip(df_address['TIN_clean'], df_address['ADDRESS']))

Two spreadsheets, one script. Payroll data comes from one file; addresses live in a separate lookup and get joined in by TIN. Cleaning the TIN string here, once, means every later lookup just works.

Every field, mapped to an exact coordinate

coord_map = {
    '3': (126, 828),    # TIN
    '4': (45, 774),     # Full name
    '19': (209, 409),   # Gross compensation
    '23': (209, 332),   # Net taxable
    '24': (209, 312),   # Tax due
    '52': (484, 255),   # Total taxable
}

Every number on the form has a fixed (x, y) position on the page. Map each one by hand once, and the same coordinates work for all 2,000+ employees.

Writing a value onto the page

def draw(section_id, value, can):
    if section_id in coord_map:
        x, y = coord_map[section_id]
        can.drawString(x, y, str(value))

One small helper that every field in the form calls. Look up the coordinate, draw the value. No interpretation, no special cases per employee.

Splitting a TIN into its digit boxes

def get_tin_coordinates_visible():
    coords = []
    y = 800
    groups = [(86, y), (136, y), (186, y), (235, y)]
    for gx, gy in groups:
        for i in range(3):
            coords.append((gx + i * 13, gy))
    return coords[:13]

The form doesn't have one box for a TIN, it has 13. This generates the coordinate for each individual digit box so a 13-digit TIN lands correctly, digit by digit, instead of overflowing one field.

Merging the overlay onto the actual template

packet = BytesIO()
can = canvas.Canvas(packet, pagesize=(width, height))
can.setFont("Helvetica", 9)

# ... draw every field for this employee ...

can.save()
packet.seek(0)
overlay_pdf = PdfReader(packet)
output = PdfWriter()
base_page = template_pdf.pages[0]
base_page.merge_page(overlay_pdf.pages[0])
output.add_page(base_page)

Reportlab draws a transparent layer of just this employee's numbers. PyPDF2 then merges that layer onto the real BIR 2316 template underneath it, so the output looks like a normal filled form, not a screenshot of one.

Some of it got fiddly beyond what's shown here. Employer details, date fields, and formatting each needed their own small adjustments. That's the unglamorous part that actually makes a script usable instead of theoretical.

What it didn't replace

Finance still owned reviewing the source data and the compliance sign-off. The script killed the repetitive part: populating thousands of near-identical documents by hand.

The result

A recurring outsourced task became a reusable internal script, one that can be rerun, tweaked, and handed off instead of rebuilt or repurchased every cycle.

What I learned

The hard part wasn't Python or PDF libraries. It was noticing a task looks complicated because of its volume, not its logic. Decompose it and most "complex" processes turn out to be a small, repeatable loop with a few annoying edge cases. Sometimes the sophisticated move is using less technology, not more.

Payroll & compliance series