Početna / Radovi
Prices — API za agregaciju cijena namirnica Stranica uživo ↗
Podatkovni cjevovod i API

PricesAPI za agregaciju cijena namirnica

Headless podatkovni servis koji svakodnevno objavljene cijene namirnica u Hrvatskoj agregira u jedinstveni API dostupan za upite. Laravel cjevovod za unos normalizira sirove datoteke cijena trgovaca u kanonski katalog proizvoda, prati svaku promjenu cijene kroz vrijeme i odgovara na upite o košarici, povijesti cijena i trgovinama u blizini — podatkovna okosnica iza usporedbe cijena u aplikaciji Household.

Prices je podatkovna okosnica iza usporedbe cijena u aplikaciji Household, i izgrađen je poput infrastrukture, a ne proizvoda: Laravel 13 API bez korisničkog sučelja, oslonjen na PostgreSQL i PostGIS. Prema hrvatskom zakonu o objavi cijena iz 2025., veliki trgovci dnevno objavljuju strojno čitljive datoteke cijena; ovaj ih servis unosi, usklađuje u jedan katalog i poslužuje rezultat preko malog autenticiranog API-ja.

Zanimljiv rad je u cjevovodu i podatkovnom modelu. Adapter po lancu pretvara format svakog trgovca u zajednički DTO, a importer neovisan o adapteru skupno upisuje u kanonski katalog — 23,6k proizvoda razriješeno barkodom kroz 21 lanac, uz klasifikacijski prolaz koji neuredna imena lanaca mapira na normalizirane kategorije. Svaka promjena cijene dodaje se u tablicu povijesti particioniranu po mjesecu, a 294 geokodirane trgovine čine “najjeftinije u mojim trgovinama” stvarnim PostGIS upitom po radijusu, a ne nagađanjem.

Admin je namjerno utilitaran — prikaz crawl pokretanja i klasifikacije za vlastiti nadzor, a ne proizvod. To je i smisao: Prices postoji da jedan posao odradi dobro i da Householdu preda čiste podatke preko poštene API granice. Brojke i tvrdnje iznad pročitane su izravno iz izvora i produkcijske baze, a ne procijenjene.

UlogaArchitecture + full-stack
Godina2026
TehnologijeLaravel 13 · PostgreSQL + PostGIS
PovršinaAPI-only + utilitarian admin
KonzumiraHousehold
Utilitarna administracijska ploča s prikazom crawl pokretanja, pokrivenosti lanaca i broja klasificiranih proizvoda
Cjevovod za unos

Sirove datoteke trgovaca unutra, jedan kanonski katalog van

Svaki lanac objavljuje drukčiji format datoteke cijena prema hrvatskom zakonu o objavi cijena iz 2025. Adapter po lancu parsira njegov format u zajednički PriceRowDTO; importer neovisan o adapteru zatim te retke skupno upisuje u products, chain_products, prices i price_history u dijeljenim transakcijama.

  • PriceSource sučelje s živim Konzum, Studenac i Tommy adapterima; novi lanac znači novi adapter, ništa drugo
  • Importer razrješava products → chain_products → prices → history → godišnju statistiku po seriji od ~1000 redaka
  • CrawlCommand orkestrira dnevno pokretanje; CrawlRun + CrawlStatus enum bilježe svaki uvoz
Identitet proizvoda

Uparivanje istog proizvoda kroz 21 različit katalog

Svaki lanac imenuje i kodira proizvode na svoj način. Kanonski proizvodi ključuju se barkodom, pri čemu se sirovi unos svakog lanca povezuje kao chain_product, a klasifikacijski prolaz mapira neuredna imena lanaca na normalizirane kategorije — tako da košarica može usporediti istovrsno kroz trgovce.

  • 23,6k kanonskih proizvoda razriješeno iz redaka proizvoda po lancu
  • ApplyClassificationCommand + serijski SQL dodjeljuju normalizirane kategorije s ocjenom pouzdanosti
  • Oznaka ručne provjere omogućuje da čovjek nadjača uparivanja niske pouzdanosti
Povijest cijena

Svaka promjena cijene, sačuvana i dostupna za upite

Trenutne cijene žive u vrućoj tablici prices; svaka zabilježena promjena dodaje se u particioniranu price_history. Mjesečne Postgres particije održavaju tablicu povijesti brzom kako raste, a rotacijska naredba osigurava nadolazeće mjesece i ispušta istekle prema rasporedu.

  • price_history particioniran po rasponu po mjesecu; RotatePartitionsCommand održava prozor
  • Povijest po proizvodu i po trgovini napaja prikaz povijesti cijena u Householdu
  • Godišnja statistika agregirana pri uvozu za brze upite na dugom rasponu
Geoprostorno

Trgovine u blizini, po stvarnoj udaljenosti

Trgovine nose koordinate kao PostGIS točku, pa upit stores/near vraća trgovine unutar radijusa poredane po stvarnoj udaljenosti — ne po graničnom okviru. To je ono što omogućuje da korisnik pita 'najjeftinije u mojim trgovinama', a ne 'najjeftinije u državi'.

  • ST_DWithin + ST_Distance nad geom::geography (SRID 4326) za pravu metričku udaljenost
  • 294 geokodirane trgovine; zakonski jaz u formatima prikazan je pošteno, ne skriven
Korisnički API

Mali, pošten API iza jednog tokena

Cijeli je servis API-first: devet autenticiranih krajnjih točaka koje pokrivaju lance, trgovine, košaricu, pretragu proizvoda, trenutne cijene, povijest cijena i status feed. Household je primarni konzument, pozivajući ga preko autenticirane HTTP granice bez dijeljene baze.

  • basket, products/search, prices/history i stores/near jezgra su okrenuta prema Householdu
  • auth.api middleware štiti svaku rutu jednim servisnim tokenom
  • Admin kontroleri stoje iza namjerno utilitarnog Vue prikaza za nadzor
Arhitektura

Cjevovod za unos koji napaja particionirani Postgres store, izložen kao mali autenticirani API — bez korisničkog sučelja, namjerno.

Izvori
Datoteke cijena po lancu
Dnevno zakonski objavljeni CSV / XML / API feedovi, jedan namjenski adapter po lancu, normalizirani u zajednički DTO.
Unos
Laravel Scheduler + Importer
CrawlCommand povlači svaki izvor, Importer skupno upisuje u dijeljenim transakcijama, CrawlRun bilježi ishod.
Podaci
PostgreSQL 18 + PostGIS
Kanonski katalog, vruće cijene, price_history particioniran po mjesecu, geokodirane trgovine, jsonb sirovi payloadi.
API
Autenticirani REST + admin
Devet token-zaštićenih krajnjih točaka koje konzumira Household; tanki Vue admin za nadzor crawlova i klasifikacije.
Riješeni izazovi
01

Jedan proizvod, dvadeset i jedno različito ime

Problem

Svaki trgovac objavljuje vlastita imena proizvoda, kodove i oznake kategorija. Bez zajedničkog identiteta, usporedba 'najjeftinije košarice' bila bi besmislena — isto mlijeko izgleda kao 21 različit proizvod.

Rješenje

Proizvodi se kanonski ključuju barkodom, pri čemu se sirovi redak svakog lanca povezuje kao chain_product. Klasifikacijski prolaz (serijski SQL + Artisan naredba) mapira imena lanaca na normalizirane kategorije s ocjenom pouzdanosti i oznakom za ručno nadjačavanje, pa se usporedbe poklapaju kroz lance dok uparivanja niske pouzdanosti ostaju provjerljiva.

02

Tablica povijesti cijena koja ne trune kako raste

Problem

Praćenje svake promjene cijene kroz tisuće proizvoda i stotine trgovina znači tablicu povijesti koja raste bez granice — i usporava svaki upit kako raste.

Rješenje

price_history je particioniran po rasponu po mjesecu u Postgresu. Zakazani RotatePartitionsCommand osigurava nadolazeće particije i ispušta one izvan prozora zadržavanja, pa čitanja ostaju brza, a stari podaci uredno zastarijevaju bez ručne migracije.

03

Poštena pokrivenost na otoku

Problem

Zakon iz 2025. pokriva samo trgovine velikog formata. Na Korčuli su fizički Konzumi svi malog formata i ne objavljuju ništa — pa bi naivni odgovori 'nema podataka' bili i netočni i beskorisni.

Rješenje

Trgovine su geokodirane i upitivane po stvarnoj PostGIS udaljenosti, a model jaz u formatima drži eksplicitnim: servis može odgovoriti 'najbliža trgovina koja objavljuje' umjesto da glumi pokrivenost koju nema. Poštenje je ugrađeno u podatke, ne nakalemljeno na sučelje.

04

Servis, a ne druga baza podataka

Problem

Householdu trebaju cijene namirnica, ali velik, dnevno ažuriran skup podataka o cijenama nema mjesta unutar sheme aplikacije za vođenje kućanstva.

Rješenje

Prices je samostalan Laravel + Postgres servis s vlastitim unosom i životnim ciklusom. Household ga doseže samo preko autenticiranog HTTP API-ja (basket, search, history, stores-near), pa oba ostaju neovisno deployabilna i nijedan ne posjeduje podatke onog drugog.

Kako je izrađeno
Backend i API
Laravel 13, PHP 8.3, Sanctum (token), API Resources, Form Requests, PHP enums
Podaci
PostgreSQL 18, PostGIS 3.6, monthly partitioning, jsonb raw payloads, ILIKE / pg_trgm matching
Unos podataka
Adapter-per-chain, DTO mapping, chunked upserts, Laravel Scheduler, Artisan commands
Infrastruktura i kvaliteta
IONOS VPS, Pest, Laravel Pint, single-token auth, monitoring admin (Vue 3)

Neglamurozna polovica sustava — pogon za cijene koji jednostavno održava podatke čistima, a API poštenim.