💾 Archived View for freeshell.de › stories › billing.gmi captured on 2023-03-20 at 18:06:21. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-01-29)
-=-=-=-=-=-=-
Once I was working for a consultancy company and they sent me to a client site that was in the middle of nowhere. It was a two hour drive, which wasn't a nice commute. The client printed paper bills on behalf of other companies: it was an outsourcing thing. They had a shop floor with big printers feeding pages into envelope stuffing machines. Every day big Royal Mail lorries would take the bills away to deliver.
One of their customers was a phone company. They had a mainframe that spat out print output, but they'd got rid of their printers and the output went to tape instead. The tapes went to another branch of the place where I was working where they were fed into a mainframe printer. But that branch was closing and there were no other mainframe printers. So they'd bought some software that translated the mainrame printer language into the right format for the printers on the shop floor. But there were some problems.
The first problem was with envelope stuffing. Printed bills usually have black marks down one side that look a bit like a very widely spaced bar code. These lines tell the envelope stuffer which pages belong in the same envelope, and whether any additional inserts should go in as well: advertising blurb, or whatever. These lines were included in the print output, but they were the wrong kind of lines, and on the wrong side. So I had to find and remove them, and add the right lines in the right place. Fortunately, this was very clearly specified, so it although it was weird, it wasn't hard.
The second problem was that the control software for the printers expected that there would be header information before each page, so I had to find where the page started and insert some stuff. And the stuff to insert included some of the printed data on the page. The customer's name, for example, or the bill total. Think about that for a moment. On a page filled with numbers - telephone numbers, call durations, prices, call costs, customer references and so on - I had to find the number that represented the bill total. It's not at the bottom. If you bill filled half a page, it's half way down the page under the last item. But if you bill filled several pages, it's somewhere on the last page. To make finding things interesting, the data on the page wasn't necessarily in a sensible order. The print data would say "go to coordinates x,y and print a 7" then "go to x1,y1 and print an F". It didn't matter what order the data went on to the page. If the coordinates where right then the end result was correct. So my software had to read these random character placements and collect them up into strings, then decide if they were important enough to put in the page header. But that's not all. A quirk of the translation process from one printer language to another was that characters could accidentally overlap slightly, and if that happened, one of them would disappear. The bought-in translation software had a fix for this: in every string, alternate characters where offset vertically by one pixel, and no, I don't know *why* that fixed it. The printer resolution was 300 dots per inch, and if alternate characters wiggle up and down by 1/300 of an inch no one will notice. But my software noticed. I'm trying to allocate characters from the print stream to strings of data that I might be interested in. I can't just collect together everything that's at the same vertical position. When I find a character, I have to say "do I have any candidate strings that are within one pixel of this position?"
To make this more fun, I was doing this in Visual Basic, which isn't the first language you'd think of for such a task. Wikipedia says:
Visual Basic 6.0 was selected as the most dreaded programming language by respondents of Stack Overflow's annual developer survey in 2016, 2017, and 2018
I'm assuming that they just didn't ask the question any other year.
The workflow was that someone would receive the tape from the phone company, run the translation software, then run the output through my thing on a Windows desktop. And they wanted all their apps to have a certain look and feel, and to be maintainable by devs who only had VB experience, so that's how it had to be.
It was a bit hit and miss. I got to the point where most of the data was collected for most of the bills by following a layout spec. But the layouts varied more than the bloke who specified it realised. There were lots of edge cases. The total isn't always under a columns of figures: sometimes it's on the next page. The customer's name is usally in one place, but sometimes it's somewhere else entirely. Progress slowed. Fixing one edge case broke another. Deadlines came and went. Eventually the cross manager came to see me. He'd recently called a staff meeting and told everyone that they could "fit in or fuck off."
"Why hasn't this been finished? We were supposed to start using it last week."
"I haven't been able to identify this field and that field on all of the bills."
"Why do you need them?"
"They have to go in the page header in the print stream."
"Why?"
"I don't know, but it's in the spec."
"Who wrote the spec?"
"So-and-so."
And off we go to see so-and-so. After blunt-to-the-point-of-rudeness questioning by the manager, it turns out that we can print the bills fine without most of the header data. The rest is just "printer software reporting progress" data, and we don't really need to know that level of detail. Great. Half my time was wasted.
My one technical success from this project was that I managed to implement a kind of stack trace for VB. There's no exception handling in VB. You can't have try/catch blocks. But you can jump to an error handler inside the current procedure: "On error goto someLabel". Mmm, goto! VB distinguishes between subroutines and functions: functions return a value. So you make everything a function that returns a boolean for success or failure. Every time you call a function, you check the result and jump to the error handler if it didn't work. And every function starts with "On error goto errorhandler". In the error handler, you append something to a global variable (!) and return false. At the top level if something returns false, you show the user the global, and it's a list of what each nested function call said went wrong. It's a fairly gruesome hack, and not quite a stack dump, but enough to point you at what needs fixing. Happily, no function ever to needed return an actual value. Everything was done by side effects: writing to globals. Scary.
Eventually it went live, and I was ready to leave the client site. The manager invited me to dinner. It was a surprisingly upmarket restaurant. I hadn't twigged what was going on, partly because I'd worked with him before he went to the print place. I thought he was just being friendly. We had a meal and talked about people we both knew. Then the conversation turned to what I was working on next. That wasn't clear to me. Oh really? So did I want to work at the print place? *CLICK* Now I understand the fancy restaurant. No, I don't want to work in the middle of nowhere with a two hour commute each way and with out-dated technology in a business that is going to disappear (who has a printed phone bill any longer?). I've never turned down a job so easily. And I didn't have to tell him that I didn't fit in, so I was fucking off.