# Changelog of Airbnb Occupancy & Revenue Estimator 📈: AirDNA Alternative (`tagadanar/airbnb-str-yield`) Actor

- **URL**: https://apify.com/tagadanar/airbnb-str-yield/changelog.md
- **Full Actor documentation**: https://apify.com/tagadanar/airbnb-str-yield.md

## Changelog

### 2026-08-29

- README: the prices on the page were the Gold-plan ones, quoted as if they
  were the list price. Corrected everywhere: a listing analysed is $0.006
  ($6.00 per 1,000) and a market report is $0.59, with the Gold figures
  ($0.0042 and $0.413) shown beside them instead of standing in for them. The
  SaaS comparison and its 20-market example are recomputed on the real numbers,
  and the actor start fee reads its real $0.001 instead of a stale $0.00005.
- README: the pricing section said Apify platform usage was billed to you
  separately. It is not, and has not been since 2026-08-26: compute and proxies
  are included in the per-event price.

### 2026-08-09 (pre-push review fixes)

- **New `currency` input (ISO-4217, default `EUR`)**, replacing yesterday's
  hardcoded `EUR`. Airbnb is asked to price the search *and* the availability
  calendars in it (the calendar query had `currency=EUR` nailed on), and it flows
  through to every listing row, the `yield` block, the market report row and the
  Slack digest — which used to print a "€" sign whatever the run was actually in.
  The default reproduces exactly what the pinned FR residential exit produced, so
  nothing changes for an input that does not set it. Same mechanism as the
  companion actor airbnb-scraper, which defaults to `USD` for the same reason.
- **Multi-currency ADR is no longer "on the roadmap".** The card price parser was
  euro-and-French-punctuation only, so a USD run would have shipped `adr: null`
  (and therefore no RevPAR) for every listing. It now handles every currency
  Airbnb renders — the parser is kept identical to airbnb-scraper's.
- **The calendar-hash KV store was renamed to
  `airbnb-str-yield-calendar-hash-v2`.** The old store had been created by hand
  from a dev box, so the platform recorded it with `actId: null` — it belonged to
  no actor, and a run under LIMITED\_PERMISSIONS may only open named storages its
  own actor created. Every platform run's open was therefore denied and swallowed
  into a single warning, and the actor re-minted the hash with Chromium on every
  run: 50 runs over 30 days, `readCount` still 0. The v2 name has never been
  created manually, so the first platform run creates and owns it, and every run
  after that is WARM.
- **The proxy ladder no longer burns a full search page per rung.** `selectRung`
  now returns the winning probe's parsed body, and the run ingests it as its own
  first page instead of re-fetching the identical URL. The probe went from pure
  overhead (up to 3 extra search pages a run, some on residential-FR at $8/GB) to
  the run's page-1 fetch. Per-rung availability and per-attempt status logging is
  unchanged, and so is the fall-back-to-the-last-rung behaviour; when no rung
  passes there is no validated body, so nothing is reused.
- Tests: 32 → 43.
- Not pushed, not applied.

### 2026-08-09 (later)

- **Proxy ladder (new `src/proxy.js`)**: datacenter proxy is tried first, with
  residential-FR — the path this actor used before — kept as the fallback rung.
  Every rung logs whether the platform granted it and what its probe returned,
  so a proxy-config failure can never vanish silently. Datacenter is obtained by
  omitting `apifyProxyGroups`; there is no Apify proxy group named `DATACENTER`.
  Evidence (2026-08-09, OVH AS16276 datacenter vantage): Airbnb search SSR
  returned HTTP 200 on 25/25 pages across 5 cities, and `PdpAvailabilityCalendar`
  answered 6/6 with full 122-day calendars.
- **Cold-vs-warm accounting (new `src/hashcache.js`)**: the cached calendar hash
  now records when it was minted and when it was last confirmed working, and
  every run ends with a `COST SUMMARY` line stating `mode=WARM|COLD`, the proxy
  rung, the ladder result, the cache age and the number of browser mints. A
  finished run used to be indistinguishable from any other, which is how a
  one-off cold-mint run became this actor's headline cost figure. Legacy
  bare-string cache records still load.
- **Mid-run hash rotation is now handled**: three consecutive empty calendars
  trigger exactly one re-mint, instead of quietly shipping every remaining
  listing with no yield at all.
- **Guardrails**: `maxMemoryMbytes: 1024` added (there was no cap) and the
  default run memory dropped 2048 → 1024. The bucket is set by Chromium during
  the rare hash mint, not by `collected[]`.
- **Currency is now written on every search URL (`EUR`)**, matching the EUR
  already hardcoded in the calendar query and the "€" labels in the input
  schema. This matters more here than on a plain scraper: the market report
  aggregates ADR and RevPAR across listings, so a run that mixed currencies
  because of an unpinned datacenter exit would produce a wrong answer rather
  than merely an odd-looking one.
- Tests: 17 → 32.
- Not pushed, not applied. Console-side changes staged in
  `actors/tools/cfg-airbnb-str-yield.json`; `maxMemoryMbytes` needs `apify push`.

### 2026-08-09 (prepared, not pushed)

- Copy/SEO: title and H1 swapped "Market Report" → "Revenue Estimator" (measured
  rank 23 on the job phrase "airbnb revenue estimator" vs rank 1-2 on "yield
  calculator" / "rental yield" / "AirDNA alternative" — "revenue" was absent
  from title/seoTitle/seoDescription entirely). seoTitle and seoDescription
  reworded the same way (proposed in `actors/tools/cfg-airbnb-str-yield.json`,
  not yet applied to Console). Keywords footer extended with "revenue
  estimator", "airbnb analytics", "investment analysis", "yield calculator".
  A we-pay flip is confirmed NEVER (unchanged); a pricing raise on
  `listing-analyzed` and `market-report` is proposed pending Raphaël's review
  — see `research/we-pay-review-2026-08-09/airbnb-str-yield.md`.

### 2026-07-12

- Invalid input no longer fails the run. The run now finishes cleanly with a clear explanation in its status message, so a typo or an empty list is easy to spot and fix.
- List inputs now accept datasets piped straight from another scraper: entries
  that are objects are read through their usual key names (url/link,
  query/keyword/title, name/company and similar) instead of being rejected.

### 2026-07-11

- Billing fix: the market-report event is now charged reliably. Runs that
  stopped at their listings cap were writing the market report row without
  billing the event; the report is now billed first and only written once
  charged.
- Billing: on paid runs every row is billed before it is written, and a run
  now stops visibly if billing fails instead of continuing unbilled.

### 2026-07-09

- Input is now tolerant of common shapes that previously failed the run: a bare
  string / bare object / delimited string for searchUrls is coerced into a list
  instead of erroring. Empty input still errors.

### 2026-07-06

- Simplified input: pagination depth is now handled automatically (technical field removed).

### 2026-07-05

- Published on Apify Store.

### 2026-07-04

- Initial release.
- Occupancy %, ADR, RevPAR and projected annual revenue for every Airbnb listing in a market.
- Comp-set percentile rank per listing plus a median/quartile market report.
- Transparent, documented occupancy method. Pay per pull — no monthly subscription.
