# Airbnb Adapter Plan v1

Date: 2026-07-01  
Branch: `feature/price-kit-trivago-market-lab`

## Decision

Airbnb should become the next strong adapter after Trivago.

Reason:

- Airbnb search result HTML contains usable structured markers.
- Search cards expose listing identity, title/name, rating/review text, price rows and listing URLs.
- Pagination is visible and contains page links with cursor offsets.
- Airbnb is a platform-specific host-friendly source, useful for own listing position and Airbnb-only market signal.

## Source role

Recommended source positioning:

```text
Trivago = broad aggregated market evidence source
Airbnb = strong platform-specific host market source
Booking = additional / fallback / comparison source
CSV/manual = universal local fallback source
```

Airbnb should not replace Trivago. It complements Trivago with a cleaner platform-specific signal.

## Captured HTML markers observed

Useful listing/card markers:

- `itemprop="itemListElement"`
- `itemtype="http://schema.org/ListItem"`
- `meta itemprop="name"`
- `meta itemprop="position"`
- `meta itemprop="url"`
- `data-testid="card-container"`
- `data-testid="listing-card-title"`
- `data-testid="listing-card-name"`
- `data-testid="price-availability-row"`
- `/rooms/{listing_id}` in listing URLs
- `check_in=YYYY-MM-DD` and `check_out=YYYY-MM-DD` in listing URLs
- rating/review text such as average rating and review count
- quality flag text such as Guest Favorite / Izbira gostov

Useful pagination markers:

- `nav aria-label="Oštevilčenje strani z rezultati iskanja"`
- numeric page links such as `1`, `2`, `3`, `4`, `…`, `15`
- next link with `aria-label="Naprej"`
- `pagination_search=true`
- `cursor=...`
- cursor offsets that indicate 18 results per page in the observed sample

## Capture helper concept

The first Airbnb capture helper should mirror the Trivago capture approach, but preserve Airbnb-specific metadata.

Planned browser-side capture JSON shape:

```json
{
  "schema_version": "cm_market_evidence_airbnb_capture_v1",
  "captured_at": "",
  "source": {
    "name": "Airbnb",
    "type": "ota_platform_search",
    "capture_method": "browser_outerhtml"
  },
  "search": {
    "url": "",
    "query": "",
    "check_in": "",
    "check_out": "",
    "nights": null,
    "adults": null,
    "children": null,
    "currency": "EUR"
  },
  "pages": [
    {
      "page_number": 1,
      "url": "",
      "listing_outer_html": "",
      "pagination_outer_html": ""
    }
  ],
  "pagination": {
    "current_page": 1,
    "visible_pages": [1, 2, 3, 4, 15],
    "has_next": true,
    "next_url": "",
    "estimated_results_per_page": 18
  }
}
```

The helper does not fetch Airbnb and does not automate browsing. It helps the user capture visible page evidence, then the user manually goes to the next page where needed.

## Adapter output contract

The Airbnb adapter must output normalized observations compatible with `cm_market_evidence_package_v1`.

Example normalized observation:

```json
{
  "source": "airbnb",
  "source_type": "airbnb_search_outerhtml",
  "property_name": "Apartma Matevž",
  "location_label": "Stanovanje v mestu Radovljica, Slovenija",
  "nightly_price": 117.5,
  "total_price": 235,
  "nights": 2,
  "currency": "EUR",
  "score": 5.0,
  "review_count": 25,
  "is_own_listing": true,
  "quality_flags": ["guest_favorite"],
  "raw": {
    "listing_id": "1213751959099409340",
    "url": "/rooms/1213751959099409340?..."
  }
}
```

## Parser v0.1 requirements

Minimum parser requirements:

1. Accept pasted/uploaded Airbnb search outerHTML or capture JSON.
2. Split listing cards by `itemListElement` and/or `data-testid="card-container"`.
3. Extract listing ID from `/rooms/{id}`.
4. Extract property name from `listing-card-name` or `meta itemprop="name"`.
5. Extract location/title from `listing-card-title`.
6. Extract check-in/check-out from listing URL or source search URL.
7. Derive nights from date range or `price_filter_num_nights`.
8. Extract total price from `price-availability-row`, especially `aria-label="Skupaj: ..."` where available.
9. Derive nightly price from total price / nights.
10. Extract rating and review count from accessible rating text.
11. Detect Guest Favorite / Izbira gostov as a quality flag.
12. Detect own listing through Subject Profile / own listing names.
13. Return standard `cm_market_evidence_package_v1` using `market_evidence_core.php`.

## Price extraction caution

Airbnb price HTML may be less uniform than Trivago.

The adapter should use multiple fallbacks:

1. `aria-label` containing total price, e.g. `Skupaj: 235 €`.
2. visible price row text within `data-testid="price-availability-row"`.
3. source URL `price_filter_num_nights` only for nights, not price.
4. mark observation with `missing_price` when no trustworthy price can be extracted.

The adapter must not invent prices.

## Multi-page rule

Airbnb must support the same evidence discipline as Trivago:

```text
page 1 capture
→ page 2 capture
→ page 3 capture
→ merge observations
→ deduplicate by listing_id
→ build one evidence package for the selected context
```

The first implementation may remain manual:

- user captures page 1,
- user clicks Airbnb page 2,
- user captures page 2,
- repeated pages are pasted/uploaded together or saved into one capture JSON.

## Integration into unified index

`index.php` should mark Airbnb as:

```text
Next strong adapter
```

not as a distant planned item.

## PM boundary

Standalone Market Evidence Kit should implement Airbnb as another evidence-package generator.

It should not implement cross-source pricing policy comparison. Multi-source and multi-period comparison remains a CM PRO Market Evidence Basket responsibility.
