Back to Feed
article 3m read

One page number knocks every later row out of step in a schedule import that reads in threes

The scheduling tool rebuilds a weekly grid from flattened PDF text three lines at a time, so one stray page counter shifts every later group, the cells fail their checks and get skipped, and the import still reports success.

Article DepthPractice

Flow

Hardening checklist for a stride parser

  1. 1Sample real text

    Print raw extracted lines across a page break.

  2. 2Clean first

    Remove furniture before any counting.

  3. 3Detect boundaries

    Stop and restart at headers, links and date rows.

  4. 4Count and classify skips

    Separate expected filters from shape failures.

  5. 5Fail loudly

    Return an error with the first bad line index.

  6. 6Fixture the ugly cases

    Page counter, footer, blank cell, duplicate first name.

The weekly staff schedule arrives as a PDF, and my scheduling tool only sees it after it's been turned into plain text. The table lines are gone. What's left is a row of day names, a row of dates, and a pile of lines where the cells are split apart by runs of spaces.

Reading in threes

My parser finds the day names and dates, then reads the rest in groups of three lines: role, name, hours. It splits each line on runs of spaces, so the first piece belongs to the first day, the second to the next day, and so on. Position is the only thing telling it what's what.

for role, name, hours in zip(it, it, it):
    ...

Each cell is then checked. If the hours aren't a time range, or the name isn't last-comma-first, the cell is skipped. Inventory and time-off roles are skipped on purpose.

One extra line breaks the rest

If one line that isn't part of the table sneaks in, like a page number, every group after it starts one line late. Names end up where hours should be, so they fail the check and get skipped. The parser never gets back in step. It still reports success, just with fewer people.

It does look for the end of a block, like a new row of day names or a link. But it only checks the first line of each group. Once it's off by one, the next page's header can slide right past.

Cleaning first

The fuller version strips junk before it counts. Blank lines, title lines, lines with links, and lines that are just a number, a slash and a number (a page counter) all go. When a block ends, it starts looking for the next header from there, so a second page gets its own block. Probably a page counter is what caused trouble, since one commit is titled for that filter. I don't have the bad import saved.

What still slips through

Cleaning only removes junk you've already listed. A new footer still knocks the count off.

A blank cell is worse. Splitting on spaces can't tell an empty cell from a wide gap, so later cells can slide over by a day and still pass every check.

If the table parser finds a header, it counts as done, even with zero records. So the older backup parser never runs. Names are matched by first name only. Two people who share one will collide.

Making it say what it skipped

A group that made records or was filtered on purpose is fine. A group that just didn't fit should stop the import and point at the line. None of my changes had tests, and that's the gap I mind most. If I were starting over, I'd write the page-break sample before the parser, then a footer, a blank cell and two people with one first name.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward