Michał Kmiecik - Java Software Engineer

Michał KmiecikJava Software Engineer & B2B SaaS Founder

Poznań, Poland

Building enterprise systems and AI-powered B2B products. Expert in Java, DDD, and turning code into revenue.

Get in touch

I built an AI to find work for idle cranes. It also found me a co-founder.

AI engineering

September 16, 2026

13 min read

How I turned public building registers and public tenders into a machine that finds construction jobs before the first shovel - and what a Google update did to it.

Illustration of the Budowo.pl ecosystem - a map dotted with construction investment leads, a crane fitted with a telemetry sensor, and a handshake standing for the partnership behind it

Back after a long silence

It has been quiet on this blog for months. Not because nothing happened - because too much did.

Since my last post about the first paying customer, I have been heads-down building a second product for the Polish construction industry: budowo.pl. This is the story of where the idea came from, how the data pipeline actually works under the hood, what broke, and the one unexpected thing that came out of it - a co-founder.

The problem: several cranes, zero jobs, one guy in a car

My grandfather runs a fairly large mobile crane company. If you have read my first SaaS post, you already know that family business - it is the reason I got into construction software in the first place.

Every winter and early spring the same thing happens. Work dries up. Machines that cost a fortune stand still in the yard. And the company's sales rep gets into a car and drives around the region, looking for construction sites with his own eyes, hoping to catch someone who needs a crane next week.

That is the state of the art in 2026. A man in a car, looking out of the window.

I kept thinking about it until the question became obvious:

What if software could do that driving for him - and do it across the whole country, every single day?

I also knew something the sales rep did not have five years ago: with LLMs, I could not only find those sites, I could understand them. Read a public notice, figure out what is being built, and judge whether it is a job for a 30-tonne crane or nothing at all.

The plan (which was mostly wrong, but a good start)

The first sketch on paper looked like this:

  1. Pull raw, public data from the internet, constantly.
  2. Throw away the garbage in plain code, with my own filters - before any AI touches it.
  3. Enrich what survives with AI: what is being built, how big is it, what machines will it need, is it worth a phone call.
  4. Geocode it, so it lands on a map.
  5. Send an SMS or an e-mail when something matches the user's radius and machine types.

Step 2 is the one that matters commercially, and I will come back to it. Everything else is plumbing.

I was building it for my grandfather's company, but I was honest with myself from day one: if it only ever works for one company, it is a favour, not a product. I wanted other firms testing it as fast as possible.

Where do you get the data? GDELT, of all places

The first wall I hit was embarrassingly basic: I had no idea where the data would come from.

Then I found the GDELT Project - an open initiative that publishes a fresh batch of news articles from around the world every 15 minutes. Hundreds of them, in bulk, unfiltered.

My bet was simple: somewhere inside that firehose there are articles about real investments - a new factory, a logistics park, a bridge - and from those articles you can extract something an equipment company can act on.

The catch is just as simple. If I send every article to an LLM, I go bankrupt before I get my first user. So the whole game became: how cheaply can I throw away 99% of the noise before paying for a single token?

So the ingestion service downloads every GDELT export on a schedule, publishes everything to Kafka unfiltered, and the domain filter sits downstream deciding what deserves attention. Simplified, the cheap part looks like this:

// Simplified: runs on every GDELT record, costs nothing, kills ~99% of the stream
public boolean isWorthAnalyzing(RawArticle article) {
    if (!language.isSupported(article.language())) return false;
    if (!country.isMonitored(article.country()))   return false;
    if (redisDedup.seenBefore(article.urlHash()))  return false;   // same story, 40 outlets

    int score = domainScore(article.title(), article.excerpt());   // "budowa", "inwestycja", "hala"...
    if (score < THRESHOLD)                          return false;
    return !blacklist.matchesAny(article);                         // sport, politics, crime, retrospectives
}

It took several iterations to tune. The first version let through every article containing the word "construction", which in Polish media means half the political section. The second version was so strict it let through nothing for two days.

After a few days of tuning, real investments started landing in the database - with an AI-generated note on what machines the site would plausibly need.

💡 Lesson learned: with LLM products, your margin lives in the code you write before the API call. Prompt engineering is the fun part; pre-filtering is the profitable part.

Problem #1: an address is not a location

I had addresses. I did not have a map - and a list of addresses is useless to a company that thinks in terms of "how far is it from my yard".

So I plugged in geocoding. And then I plugged in more geocoding, because the first thing you learn about free APIs is that they go down, rate-limit you, or return nothing at all, usually at the worst moment.

Today the system uses several providers with fallbacks: GUGiK and ULDK for Polish official addresses and land parcels, Nominatim and Photon for everything else. If one fails, the next one answers. For Polish building permits I can often go further than a pin on a map - I get the actual parcel outline, which is currently the case for over 18,000 records.

💡 Lesson learned: any third-party API you depend on will fail in production. Design for the second provider on day one, not after the first outage.

Problem #2: great, now find me a job that is not a €400M highway

GDELT gave me Europe's megaprojects. Impressive on a map, useless for a crane company near Poznań that needs a job 40 km away next Tuesday.

The product is for Polish companies, so I needed Polish data at Polish scale. I found three open sources and built a pipeline for each:

The register is published as a daily manifest of per-region ZIP files full of CSV. Not an API, not JSON - a ZIP of CSV. The pipeline: download, unpack, filter by category and date in plain code, deduplicate, classify the investor (private person vs. company vs. public body), geocode, then - and only then - enrich with AI.

That last ordering matters. A single-family house in a village is a valid permit and a terrible lead for a crane company. Filtering by object category costs nothing. Asking an LLM about it costs money on every row, forever.

Where the numbers stand today

This is what the system holds as I write this (the landing page reads these live from the API, so they will have moved by the time you read it):

Building permits in the database42,509
Permit decisions in the last 30 days7,196
New in the last 7 days1,307
Localities with at least one investment3,860
Public tenders from BZP + TED10,636 (2,345 still open)
Machine types in the classification71
Data refreshevery 15 minutes

The interesting number is not the total - it is 1,307 per week. That is how many fresh, officially confirmed construction starts a sales rep would have to find by driving around. He finds maybe three.

Budowo.pl demo map - 249 public tenders and building permits inside a 50 km radius around a company near Poznan, clustered and colour-coded by AI potential score

Shipping it while it was still ugly

At some point the thing worked. Not well - locations were occasionally wrong, some AI summaries were nonsense, the UI was rough. But it worked.

I have been a member of construction-related Facebook groups since my first product, so I knew exactly where my users hang out. I wrote a post. Within a few days I had my first 10 testers.

The feedback split neatly in two:

Fair. A lead you cannot call is trivia.

For private investors there is often no contact to find, and that is that. But when the investor is a company, the phone number, e-mail and website are usually a Google Places lookup away. I wired that in, shipped it, and went back to the same Facebook groups to announce the update.

More testers signed up on the back of that post than the original one.

💡 Lesson learned: shipping a visible fix to a complaint people voiced publicly is better marketing than any launch post. They watch whether you actually listen.

The SEO detour, and a lesson that cost me three months

One more thing worth telling, because it went badly and those stories are rarer online than they should be.

To get organic traffic, I generated a programmatic SEO layer on the landing page - roughly 340 pages, one per city and region, each showing the real investment numbers for that area. Classic play, and it started working: about 540 impressions a day, average position around 8.

Then Google shipped a spam update. Overnight my traffic went from ~540 impressions a day to ~5, and the average position fell from 8 to 50 - including for my own brand name. The whole domain got downgraded.

When I finally sat down with the data instead of my feelings, the diagnosis was uncomfortable but obvious. Two random pages of that layer were about 70% word-for-word identical. Some pages had grammar errors visible from orbit, generated by my own templates. I had published a few hundred pages that no human would ever want to read, and Google noticed before I did.

The fix was not clever: indexing thresholds (an area page only gets indexed if it actually has enough data to say something), unique per-area content instead of the same sentence with a different city name, and links pointing only to pages worth linking to. Recovery from that kind of hit usually takes months, not days - I am still waiting.

💡 Lesson learned: npm run build passes just fine on pages that say nothing. A green build is not proof the output is worth publishing. Read the generated pages - especially the weirdest ones.

Money: from free testing to a real price

Tests have been running for about two months. Now comes the less romantic part - phone calls with testers, structured feedback, and the question of whether anyone will pay.

I made a decision in August: the app has a price. 99 PLN net per month for the first 20 companies, locked in for good; 149 PLN afterwards. No contract, no credit card, 30 days of free access.

I could have hidden behind a "beta" badge for another year. But a B2B landing page with no price does not read as neutral - it reads as "expensive, you will have to call us", and that quietly filters out exactly the small equipment firms I built this for.

You can see the whole thing at budowo.pl.

Budowo.pl pricing card - founder price of 99 PLN net per month for the first 20 companies instead of 149 PLN, with no contract and no credit card

The best thing that came out of all this was not the product

Here is the part I did not plan.

All that activity in construction Facebook groups - posting, answering questions, shipping fixes in public - put my name in front of the right person. A domain expert reached out to me. He still works as a machine operator, he knows this industry from the inside in a way I never will, and he builds his own products as a non-technical founder.

He proposed we work together.

Our joint product is BRYGA - and it already has real companies waiting for it. The idea is to connect software and telemetry in one place for equipment companies: the job, the crew assignment, the operator's work log, the data coming off the machine, and the settlement at the end. Right now, office staff reconcile all of that by phone and spreadsheet.

BRYGA looks like the biggest project either of us has built so far.

He and his team (TECTIQ) also run Ochrona Maszyn - GPS monitoring for machines, vans and containers - and TECTIQ, which builds the IoT side: sensors, CAN data, telemetry from equipment in the field.

💡 Lesson learned: being visibly, usefully active where your users already are is a distribution strategy and a recruiting strategy. My co-founder found me, not the other way around.

One ecosystem instead of four side projects

So we decided to stop treating these as separate side projects and connect them into a single ecosystem for construction and equipment companies:

The thesis behind all five is the same. The Polish construction industry is dramatically behind on software - far behind Western Europe - and the companies in it are, for now, deeply sceptical of any tool that is not a notebook or a spreadsheet.

That scepticism is earned, and it makes selling brutal. It also means most of these firms genuinely have no idea what they are losing: idle machines, jobs they hear about two weeks too late, settlements based on someone's memory of last Thursday.

For now they are unwilling. That is the word we keep coming back to. We would like to be the reason that changes.

What's next

If you want to see whether any of this actually produces results - including the parts that fail - I post the updates as they happen on LinkedIn and X.

Thanks for reading 🚀