-
0.1 (2026-11-15): write per-source status and run summary to the run key-value store (STATUS, SUMMARY) instead of dataset rows, so the dataset contains only listings. Market comparison now uses same make, model and model year within ±1 (and a mileage band when enough comparables exist) and leaves market fields empty when a narrow comparison is not possible.
-
0.1 (2026-11-15): rewrite README as a buyer-focused sales page (clear who it is for, what you get, real output, plain reliability and pricing) and remove internal test/log terminology.
-
0.1 (2026-11-15): add includeRemoved (default true) for monitor mode to suppress removed rows and report only new/changed alerts while keeping removals tracked in state. Input schema, monitor output/summary, README and tests updated.
-
0.1 (2026-11-14): add priceType input filter (fixed, auction, request) for search/detail/monitor to keep only matching price types (e.g. fixed-price only), applied during collection and separated in monitor state. Input schema, tests and README updated.
-
0.1 (2026-11-14): split dataset views so the first Listings view carries listing fields only (no monitor/status columns that are blank on default search), and move change/monitor and run-status columns to a second Changes view. Full details keeps all columns. Dataset schema and tests updated.
-
0.1 (2026-11-14): add listingAgeDays (integer days since postedAt, null when unknown) to search/detail/monitor rows, computed from postedAt vs run time and preserved across monitor re-runs (including removed rows). Dataset schema and README updated. Tests in tests/test_parser.py and tests/test_monitor.py.
-
0.1 (2026-11-14): fix shippingOnly for car searches by treating unknown shipping as null (not false) and keeping unknown rows when shippingOnly is set, so Blocket Bil queries like volvo return rows instead of empty; Torget explicit shipping: false rows are still excluded. Tests in tests/test_filters.py and tests/test_parser.py.
-
0.1 (2026-11-14): apply publishedWithinDays, onlyWithImages and shippingOnly while collecting search/monitor pages (skip non-matching rows and keep paging, page cap unchanged) so maxItems still returns up to the requested number of passing rows. Tests in tests/test_filters.py.
-
0.1 (2026-11-13): populate shipping from search JSON so default search rows are never blank; Torget keeps true/false from shipping_exists/fiks_ferdig, and car search rows default to false when no shipping signal is present. shippingOnly keeps only shipping: true. Tests in tests/test_parser.py and tests/test_filters.py.
-
0.1 (2026-11-13): added shippingOnly (Blocket Frakt) filter and shipping (boolean/null) on listings (search/monitor/detail). Torget rows expose shipping from shipping_exists/fiks_ferdig and detail eligibleForShipping; car rows are null when unknown. shippingOnly: true keeps only shipping: true and monitor state is keyed by the filter. Tests in tests/test_filters.py.
-
0.1 (2026-11-13): added onlyWithImages input to keep only listings with at least one photo (imagesCount > 0) in search/detail/monitor, filtered before market comparison, push and charge. Monitor state is keyed by the filter. Tests in tests/test_filters.py.
-
0.1 (2026-11-12): monitor changed rows carry priceDelta (new minus previous price, same currency only) and every monitor row carries firstSeenAt, kept in the stored state across runs (listings stored before this change show null until next seen as new).
-
0.1 (2026-11-12): added publishedWithinDays input to keep only listings published within the last N days (applied to postedAt) in search/detail/monitor, filtered before market comparison, push and charge. Monitor state is keyed by the recency filter. Tests in tests/test_filters.py.
-
0.1 (2026-11-11): dealer inventory: a dealer page URL in startUrls or an ID in the new dealerOrgIds input returns that dealer's whole car stock (car search API filtered by orgId; live 11 Nov: dealer 7903992, 343 cars, every row sellerId 7903992). Monitor mode on it tracks a competitor's new, repriced and sold cars. Tests in tests/test_dealer.py.
-
0.1 (2026-11-10): honor the proxy input by building requests through Apify proxy configuration with a fresh session per retry (direct only when useApifyProxy is false). Client tracks proxy request usage and rotates sessions on retries. Tests in tests/test_proxy.py.
-
0.1 (2026-11-10): monitor mode ends SUCCEEDED on a quiet day (no new/changed/removed listings), so two unchanged runs report 0 new/0 changed/0 removed at $0 and exit successfully. Legal sign-off recorded (council 2026-11-10-0645, decision d480b24e4): business sellers only, no contact extraction, data use disclaimer in README. Showcase Store task .actor/store_task.json verified (search Blocket Bil cars by make and model, example volvo, 50 rows). Tests in tests/test_monitor_quiet.py, tests/test_store_task.py, tests/test_legal.py.
-
0.1 (2026-11-10): showcase Store task .actor/store_task.json for the most-searched buyer use case (search Blocket Bil cars by make and model, example input volvo, 50 real rows): its own Store landing page with SEO title/description and the overview dataset view, published by pipeline.store_task on the next publish. Tests in tests/test_store_task.py.
-
0.1 (2026-11-10): default search and monitor rows omit keys that are blank on every row (detail-only specs, monitor-only changeType/previousPrice* in a search run, market columns when no row in the run has a comparison), so a CSV export opens with no blank columns; detail mode and scrapeDetails: true still carry the detail specs, and the dataset schema's details view keeps them. Zeros stay (marketComparables: 0, imagesCount: 0). Tests in tests/test_parser.py and tests/test_reliability.py.
-
0.1 (2026-11-10): runtime auto-partitioning past Blocket's 2,500-per-query ceiling. A capped search with partition on (default) and maxItems above 2,500 probes the real price range (price_asc/price_desc), splits the query into price bands (src/urls.py:split_price_band), recursively halves any band with more than 2,000 matches, collects and dedupes by ad ID, and the status row adds partitioned/partitions/delta plus an expected-vs-collected reason. partition: false keeps the old stop-at-2,500 behaviour; maxItems maximum rises to 10,000 so a run can ask for the extra rows. Real fixtures car-volvo-page1 (21,076 matches) and car-price-filter (100,000–200,000 SEK band, 4,454 matches, ad 28097649) back the README section; tests in tests/test_partition.py.
-
0.1 (2026-11-09): server-side mileageMin/mileageMax (mil), bodyType (Swedish name→code map, e.g. Kombi→4) and location (region name→Blocket code, e.g. Stockholm→0.300001, or a region/city code) filters end to end, preserved from pasted search URLs, with monitor state keyed by the new filters; live fixtures car-volvo-mileage-max2000, car-volvo-stockholm and car-volvo-stockholm-mileage prove 0 rows over 2,000 mil and Stockholm-region-only rows; README moves Output before Input and adds a real filtered example with image. Tests in tests/test_filters.py and tests/test_input.py.
-
0.1 (2026-11-09): legal sign-off recorded (council 2026-11-09-1845, decision d0a7fc6af): seller identity is business-only — sellerName/sellerId are emitted only for Företag sellers (organisation_name/org_id), Privat rows return null for both in search, Torget detail and car detail (detail fallback no longer matches breadcrumb "name":"Bil" and only runs for Företag, so dealer detail now returns e.g. Brandt Bil - Vänersborg), and no contact extraction anywhere (no phone/email/contact fields in any parsed row; login/reveal APIs never used). RED was build 0.1.4 only with 0 failures on 0.1.5-0.1.17, so the stale-prefill/retry fixes were not reworked. Re-battery from captured fixtures: all mapped battery search/detail fixtures parse to real rows (8/8 search fixtures 50 rows each, 3/3 detail fixtures 1 row each; fixture battery >=95%, live success clean on 0.1.5-0.1.17). Listing copy, pricing and version unchanged. Tests in tests/test_legal.py.
-
0.1 (2026-11-09): drop stale volvo prefill in Blocket search-URL form too ({"url": ".../search/car?q=volvo"}, plain URL strings, or URL in query) when an explicit non-volvo query is present, so it cannot run as a second volvo search with the real query's filters (runs 7XqSK5cxAAj8oYrwE, YC7TffvReMLGzxyrU). Regression test reuses the bmw fixture.
-
0.1 (2026-11-09): end-to-end regression tests run the exact RED inputs (7XqSK5cxAAj8oYrwE audi, YC7TffvReMLGzxyrU bmw) through the search path against live-captured fixtures and assert whole-SEK prices, no stale volvo prefill, and 10 rows.
-
0.1 (2026-11-09): retry transient HTTP 403/429 with backoff before failing (parallel battery runs 7XqSK5cxAAj8oYrwE and YC7TffvReMLGzxyrU failed 0 rows simultaneously); live re-captures of both inputs now parse 50 rows.
-
0.1 (2026-11-09): stale-prefill hardening for runs 7XqSK5cxAAj8oYrwE (audi/priceMin 100000.0 -> 400) and YC7TffvReMLGzxyrU (bmw/priceMax 150000.0 -> 400): the injected volvo default in queries ran with the real query's filters and failed. Drop volvo/car in dict and string form when an explicit non-volvo query is present, keep whole-SEK prices, and cover both failed inputs with fixtures.
-
In-run below-market valuation (no extra HTTP): new src/market.py adds marketValue (median of ≥5 same make+model priced rows in the run), priceVsMarketSek, priceVsMarketPct, marketComparables, marketConfidence and redFlags (motorfel/växellådsfel/körförbud/startar ej/krockskadad/reservdelar or under 35% of market); new inputs compareToMarket (default on), onlyBelowMarketPct, onlyBelowMarketSek, includeRedFlagged; stable keys in parser base row + dataset schema; real fixture example in README. Tests in tests/test_market.py.
-
Fix 0-row failures when Apify UI injects schema-prefill volvo into queries alongside an explicit query: drop the stale prefill so the real keyword/filters are used (runs 7XqSK5cxAAj8oYrwE, YC7TffvReMLGzxyrU). Tests cover both failed battery inputs.
-
Honest pricing and mileage: priceAmount: 0 now returns null with priceType (fixed/auction/request, e.g. Klaravik auction 28107876 vs Torget free 28092346), mileage is always mil with new mileageKm (×10, km sources divided by 10 with a year-plausibility guard), salesForm stays the Blocket code with new salesFormLabel (Begagnad/Ny/Leasing), chassisNumber is upper-cased, detail pages without JSON-LD recover their price from page targeting, imagesCount: 0 is documented (seller uploaded no photos, e.g. dealer 28107688), and README separates default search nulls from detail/scrapeDetails richness with real fixtures for both. Captured volvo-recent + XC60/no-image detail fixtures.
-
Fix 400 on float prices: build_search_url/with_page emit whole-SEK ints, pasted page/API search URLs are converted/sanitized to API URLs, single query merges with queries so a stale schema default cannot swallow the keyword, and HTTP 400 fails fast without retry. Captured bmw/audi battery fixtures.