IT.
All writing

A procurement monitor that neither submits nor signs

Ilya Trapeznikov Published

A service watches the electronic marketplace for small public procurement: it finds new procurements in a narrow niche, pulls the public offers, parses the draft contract and prepares a submission. Twelve screens, twenty-six routes, eight background jobs.

It does not submit the offer and does not sign it. That is not an omission, and I come back to it below.

Checking the source before building

The starting idea was wider: count how often a given supplier goes after procurements, and judge competitor activity from that. It had to be dropped on the second day.

The participants of a procedure are not published: no list of submitted offers, no protocols, no losing bids, and in no status, including completed ones. I checked that on five cards in different states rather than conclude from one unlucky page. Only the cases where a supplier won are countable, because the winner appears in the contract register.

A few hours of checking against a few weeks of building an interface over data that does not exist.

One queue turned out to be one short

There are eight jobs, and at first they ran in a shared queue under a single lock. What broke it was the shop catalogue crawl: eleven sections with a one and a half second pause between requests, which is tens of minutes of work. All that time the lock held everyone else, including the offer polling on hot lots, and a hot lot is one with under an hour to its deadline.

There are two queues now, in separate threads. The fast one goes to the procurement portal and keeps the feed current, the slow one crawls catalogues and distributor price feeds. There are two locks as well, and not for symmetry: the portal lock exists so several jobs do not hit the platform in a volley, while the shop lock is separate because the catalogue crawl runs on its own session and never touches the portal’s request queue.

After the split the periods look like this:

JobPeriod
Find new procurements in the niche10 minutes
Offers on lots we decided to bid for3 minutes
Offers on the rest of the shortlist15 minutes
Download and parse the draft contract10 minutes
Match catalogue models against lot positions20 minutes
Crawl shop cataloguesdaily
Confirm that our submission went through5 minutes
Archive completed items, back up the databasedaily

The three minutes and the hour-to-deadline sit next to each other in the config and are editable in settings, without touching code. The right period becomes clear in use rather than at design time, and that mattered more than I expected when I put the settings screen in.

Every job writes its result to its own table, and one failing does not stop the others. Shutdown waits no longer than ten seconds for the threads: the catalogue crawl can run half an hour, there is no sense holding a stop for it, and unfinished work repeats on the next start.

Where the service stops

It carries the work up to the point of submission. A person sends the offer, and a person signs it.

The last step is the most routine one, and the temptation to close it with automation is obvious. But submitting an offer is a legally binding act, and an error here produces not an inconvenience but a signed contract that has to be fulfilled. The difference from code is simple: bad code gets rewritten, a signed contract gets performed.

Dropping the margin calculation

At first the service calculated price: supplier price lists, markup, lot cost, margin. Then I dropped it. The client here is me, so both the decision and the rework were mine.

The comparison stopped being about money and became about matching the technical specification. Instead of a match score from zero to a hundred there are counters per requirement: how many parameters are better than required, how many are exactly on, how many fail, how many have no data. The summary now reads as “a model passing the specification was found for N positions out of M”, and the comparison is strict: the procurement’s requirement against the model’s characteristic, with no rounding in our favour.

“87 out of 100” looks convincing and does not answer whether you can bid. “Two parameters better, three exact, one failing” does.

What counts as critical changed with it. It used to include a negative margin; now it is three things: under an hour to the deadline, a failed mandatory requirement, and a job error.

Backups

The service backs the database up daily, compresses it and thins the history: the last seven dailies, one per week within the month, one per month.

A copy next to the database does not survive losing the server, so there is a second one sent off-site, and it is verified before it goes: unpack, run an integrity check, count the records. A copy that does not open is worse than no copy, because it creates confidence.

Written down separately is what does not leave the machine and will be lost with it: the file holding passwords and secrets. That list turned out to be more useful than the backup schedule itself.


This piece deliberately contains no request addresses, no parameters, no notes on how the source behaves, and no company names from the analysis. The first is a working advantage, the second is other people’s data.