CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/nanoclaw-travel

Travel assistant for NanoClaw: byAir flight notifications (delay, gate, connection risk, inbound aircraft delay, time-to-leave, arrival logistics), traffic-aware drive planning for in-person meetings (auto drive blocks + leave-by traffic rechecks), travel-booking gap checks, and nightly TripIt sync. Per-chat overlay plugin.

Quality

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

seat_quality.pyskills/expertflyer/scripts/

"""Rank open seats by the operator's stated preferences.

Ranking is preference, not fact, so it lives here rather than in the
`expertflyer-api` service: the service reports what a seat *is*, this decides
what a seat is *worth*.

The operator's rules, verbatim in effect:

  1. Window best, aisle works, middle never. A Main Cabin non-middle beats a
     Comfort+ middle — and since middles are never taken, a middle is not
     ranked low, it is excluded outright.
  2. Closer to the front is better. Bulkhead carries no penalty. An
     emergency exit row beats being further forward.
  3. Exit row WITH recline (the second one) beats exit row without recline,
     which beats any other seat in the cabin.
  4. A Comfort+ middle never beats a Main Cabin aisle.
  5. The exit-row preference applies in every cabin. Exit rows are
     overwhelmingly a Main Cabin feature, but the preference is harmless
     where there are none and correct where there are.

Cabin outranks the exit row, per the operator: a better cabin buys
front-of-the-bus AND leg room, whereas an exit row buys leg room alone. Cabins
rank on the full ladder (`CABIN_SCORE`), not Comfort+-versus-everything: a
two-value split puts First and Delta One BELOW Comfort+, so a Comfort+ window
in row 30 reads as an upgrade from seat 1A.

Window-versus-row is graded rather than absolute: an aisle up front beats a
window far back, but a window a few rows further back still wins. WINDOW_WORTH_ROWS
is that exchange rate.
"""

from __future__ import annotations

import re

# Comfort+ beats an exit row: it buys forward position AND leg room, where an
# exit row buys only leg room.
CABIN_OUTRANKS_EXIT = True

# How many rows further back a window may sit and still beat an aisle. At
# exactly this many rows the window wins; beyond it, the aisle does.
WINDOW_WORTH_ROWS = 3

# Never offered. Rule 1 is absolute: "middle - never".
EXCLUDED_POSITIONS = frozenset({"middle"})

# Higher is better.
POSITION_SCORE = {"window": 2, "aisle": 1}

FIRST = "F"
BUSINESS = "C"
PREMIUM_ECONOMY = "A"
COMFORT_PLUS = "W"
MAIN_CABIN = "Y"

# The cabin ladder, higher is better. Codes are the service's, per
# `skills/expertflyer/references/web-contract.md`. Ranking Comfort+ against
# everything else instead would score First, Delta One and Premium Select at
# the Main Cabin's 0 — so a Comfort+ seat would outrank the First seat already
# held, which is the one comparison a seat check must never get backwards.
CABIN_SCORE = {MAIN_CABIN: 0, COMFORT_PLUS: 1, PREMIUM_ECONOMY: 2, BUSINESS: 3, FIRST: 4}

# The words the operator uses for each cabin, from the same table. Premium
# economy is Premium Select (`A`) and is NOT Comfort+ (`W`); resolving it to
# `W` would compare a seat against the wrong cabin's ladder position.
CABIN_ALIASES = {
    "first": FIRST,
    "business": BUSINESS,
    "delta one": BUSINESS,
    "premium economy": PREMIUM_ECONOMY,
    "premium select": PREMIUM_ECONOMY,
    "comfort+": COMFORT_PLUS,
    "comfort plus": COMFORT_PLUS,
    "economy comfort": COMFORT_PLUS,
    "economy": MAIN_CABIN,
    "main cabin": MAIN_CABIN,
    "coach": MAIN_CABIN,
}

# Exit tiers, rule 3. The recline distinction separates paired exit rows: the
# forward one is fixed-back precisely BECAUSE the second sits behind it.
EXIT_RECLINE = 2
EXIT_NO_RECLINE = 1
NOT_EXIT = 0


class SeatQualityError(ValueError):
    """A seat that cannot be ranked, rather than one ranked badly."""


def cabins_at_or_above(cabin: str, rungs: int) -> list[str]:
    """The held cabin plus the `rungs` cabins above it, best first.

    A seat check that reads only the cabin already occupied cannot see the
    Comfort+ window that opened while the operator sits in Main — the whole
    class of upgrade worth interrupting for. Walking the ladder is what makes
    that visible, and each rung costs one more request to a bot-walled
    service, so the width is the caller's to choose.
    """
    if rungs < 0:
        raise SeatQualityError(f"rungs must be zero or more, got {rungs}")
    held = CABIN_SCORE[cabin_code(cabin)]
    reachable = [c for c, score in CABIN_SCORE.items() if held <= score <= held + rungs]
    return sorted(reachable, key=lambda c: CABIN_SCORE[c], reverse=True)


def cabins_above(cabin: str) -> list[str]:
    """Every cabin that outranks this one, best first.

    What a sweep stopping at `cabin` did NOT look at. A verdict of "nothing
    open beats the held seat" is only ever true of the cabins actually read,
    so the ones left out have to be nameable.
    """
    score = CABIN_SCORE[cabin_code(cabin)]
    above = [c for c, other in CABIN_SCORE.items() if other > score]
    return sorted(above, key=lambda c: CABIN_SCORE[c], reverse=True)


# A seat designator is a row number followed by a column letter — "21F", "1A".
# Fully enumerable, unlike the free text elsewhere in a booking.
_LABEL_RE = re.compile(r"^(\d{1,3})\s*([A-Z]{1,2})$")


def parse_seat_label(label: str) -> tuple[int, str]:
    """Row and column from a seat designator, e.g. '21F' -> (21, 'F').

    Rows are numbered from 1. Row 0 parses as a number and then sorts ahead of
    row 1, so a mistyped seat would rank as the furthest-forward seat on the
    aircraft and report every real seat as worse than it.
    """
    match = _LABEL_RE.match(str(label).strip().upper())
    if not match:
        raise SeatQualityError(
            f"{label!r} is not a seat designator — pass a row and column like 21F or 1A"
        )
    row = int(match.group(1))
    if row < 1:
        raise SeatQualityError(
            f"{label!r} is row {row}, and seat rows start at 1 — pass the seat as printed "
            "on the boarding pass, e.g. 21F"
        )
    return row, match.group(2)


def column_positions(seats) -> dict[str, str]:
    """Column letter to position, read off the cabin's own bookable seats.

    The service reports open seats only, so the seat already held is never in
    the list and its position cannot be looked up directly. It can be derived:
    the aircraft states what column F is on every open seat in the same cabin.

    A column reported as two different positions is dropped rather than
    resolved — a cabin whose forward rows are 2-2 and rear rows 3-3 makes the
    same letter a window and a middle, and picking one would decide the
    operator is in a seat they are not in.
    """
    seen: dict[str, str | None] = {}
    for seat in seats:
        column = seat.get("column")
        if not column:
            continue
        position = seat.get("position")
        if position not in POSITION_SCORE and position not in EXCLUDED_POSITIONS:
            continue
        key = str(column).strip().upper()
        if key in seen and seen[key] != position:
            seen[key] = None
        else:
            seen[key] = str(position)
    return {column: position for column, position in seen.items() if position}


def is_acceptable(seat: dict) -> bool:
    """False for seats the operator would never take, whatever else is true."""
    return _position(seat) not in EXCLUDED_POSITIONS


def _position(seat: dict) -> str:
    """Seat position, from whichever shape the caller supplies.

    The expertflyer-api service reports a `position` string; the raw seat-map
    payload it parses uses isWindow/isAisle/isMiddle booleans. Accept both, so
    the ranker works against the API response and against a captured fixture
    without a translation layer between them.
    """
    named = seat.get("position")
    if named in POSITION_SCORE or named in EXCLUDED_POSITIONS:
        return str(named)
    if seat.get("isWindow"):
        return "window"
    if seat.get("isAisle"):
        return "aisle"
    if seat.get("isMiddle"):
        return "middle"
    if named is not None:
        # A present-but-unknown value and an absent one send the operator to
        # different places — one is a service that renamed a position, the
        # other is a seat map that classified nothing.
        raise SeatQualityError(
            f"seat {seat.get('label')!r} reports position {named!r}, which is not "
            f"window, aisle or middle — the service's vocabulary changed, so "
            f"ranking it would guess at what the operator would sit in"
        )
    raise SeatQualityError(
        f"seat {seat.get('label')!r} reports no window/aisle/middle flag — "
        "the seat map did not classify it, so it cannot be ranked"
    )


def exit_tiers(seats, cabin_exit_rows=None) -> dict[int, int]:
    """Map each exit row to its recline tier, derived from the cabin's layout.

    The seat map carries `isExitRow` and `row` but no recline signal, so the
    distinction is read off geometry: an exit row cannot recline when another
    exit row sits directly behind it. That is why the forward row of a pair is
    fixed-back, and why the second is the one worth having.

    `cabin_exit_rows` must be EVERY exit row in the cabin. `seats` alone is not
    enough: the service reports bookable seats only, so an occupied rear exit
    row is invisible and the open row in front of it would be promoted to
    reclining — recommending the one seat the operator specifically does not
    want. Without the full layout, no exit row is claimed to recline.
    """
    exits = [s for s in seats if s.get("isExitRow")]
    present = {int(s["row"]) for s in exits}
    if cabin_exit_rows is None:
        # No layout: fall back to an explicit per-seat flag where one exists,
        # and otherwise claim nothing. Never promote on absent evidence.
        return {
            int(s["row"]): (EXIT_RECLINE if s.get("reclines") else EXIT_NO_RECLINE) for s in exits
        }
    layout = {int(r) for r in cabin_exit_rows}
    return {r: (EXIT_NO_RECLINE if (r + 1) in layout else EXIT_RECLINE) for r in present}


def _exit_tier(seat: dict, cabin: str, tiers: dict[int, int] | None = None) -> int:
    """Exit-row tier, applied in every cabin.

    Exit rows are overwhelmingly a Main Cabin feature, but scoping the
    preference to Main would silently mis-rank a cabin that happens to have
    one. `cabin` is retained for callers and future per-cabin nuance.

    `tiers` comes from `exit_tiers()` over the whole cabin. Without it — a
    single seat judged in isolation — fall back to an explicit `reclines`
    field, and failing that to the weaker tier, so an unknown seat is never
    promoted over one known to recline.
    """
    if not seat.get("isExitRow"):
        return NOT_EXIT
    if tiers is not None:
        return tiers.get(int(seat["row"]), EXIT_NO_RECLINE)
    return EXIT_RECLINE if seat.get("reclines") else EXIT_NO_RECLINE


def cabin_code(cabin: str) -> str:
    """The service's cabin code, from a code or the words the operator uses.

    Both shapes reach ranking: the service stamps a code on each seat, while a
    held cabin arrives as whatever the operator said. An unrecognised cabin
    raises — scoring it as Main Cabin would silently rank a premium seat at the
    bottom of the ladder and report the downgrade as an upgrade.
    """
    text = str(cabin).strip()
    if text.upper() in CABIN_SCORE:
        return text.upper()
    code = CABIN_ALIASES.get(text.lower())
    if code is None:
        raise SeatQualityError(
            f"cabin {cabin!r} is not a cabin this ranks — pass one of "
            f"{', '.join(sorted(CABIN_SCORE))} or a name from "
            f"{', '.join(sorted(CABIN_ALIASES))}"
        )
    return code


def _cabin_rank(cabin: str) -> int:
    return CABIN_SCORE[cabin_code(cabin)]


def seat_cabin(seat: dict, fallback: str | None = None) -> str:
    """The seat's own cabin, falling back to the caller's.

    The service stamps `cabin` on each seat. Comparing two seats therefore has
    to read each one's own cabin — a single shared argument cannot express
    "Comfort+ outranks a Main Cabin exit row", which is the rule that made
    cross-cabin comparison worth having.
    """
    cabin = seat.get("cabin") or fallback
    if not cabin:
        raise SeatQualityError(
            f"seat {seat.get('label')!r} carries no cabin and none was supplied — "
            "cabin decides rank, so guessing it would silently mis-order"
        )
    try:
        return cabin_code(cabin)
    except SeatQualityError as exc:
        # `cabin_code` knows the cabin but not whose it is. The seat label is
        # what the operator needs to act on, and this is the outermost place
        # that still has it, so the label is attached here.
        raise SeatQualityError(f"seat {seat.get('label')!r}: {exc}") from exc


def seat_sort_key(
    seat: dict, cabin: str | None = None, tiers: dict[int, int] | None = None
) -> tuple:
    """Sort key for one seat; higher tuples are better seats.

    Position and row trade off rather than one dominating: a window counts as
    WINDOW_WORTH_ROWS rows further forward than it is. Row is negated so a
    lower row number sorts higher. The raw position breaks exact ties, which
    is what makes a window exactly WINDOW_WORTH_ROWS back beat the aisle.
    """
    resolved = seat_cabin(seat, cabin)
    position = POSITION_SCORE[_position(seat)]
    exit_tier = _exit_tier(seat, resolved, tiers)
    row = int(seat["row"])

    effective_row = row - WINDOW_WORTH_ROWS if _position(seat) == "window" else row
    cabin_key = _cabin_rank(resolved) if CABIN_OUTRANKS_EXIT else 0
    return (cabin_key, exit_tier, -effective_row, position)


def rank_seats(seats, cabin: str | None = None, cabin_exit_rows=None) -> list[dict]:
    """Acceptable seats, best first. Middles are dropped, never ranked last.

    `cabin_exit_rows` is the cabin's full exit-row set; see `exit_tiers`.
    """
    usable = [s for s in seats if is_acceptable(s)]
    # Adjacency is a property of the cabin, not of one seat, so the tiers are
    # computed over every seat given — including the ones ranking will drop.
    tiers = exit_tiers(seats, cabin_exit_rows)
    return sorted(usable, key=lambda s: seat_sort_key(s, cabin, tiers), reverse=True)


def best_seat(seats, cabin: str | None = None, cabin_exit_rows=None) -> dict | None:
    ranked = rank_seats(seats, cabin, cabin_exit_rows)
    return ranked[0] if ranked else None


def seat_label(seat: dict) -> str:
    """Full seat designator, whichever shape the caller supplies.

    The service reports `label` already row-qualified ("14B"); the raw
    seat-map payload reports the column alone ("B") with `row` beside it.
    Concatenating blindly renders "1414B".
    """
    label = str(seat.get("label", ""))
    row = str(seat.get("row", ""))
    if label.startswith(row) and label != row:
        return label
    column = seat.get("column") or label
    return f"{row}{column}"


def describe(seat: dict, cabin: str | None = None, tiers: dict[int, int] | None = None) -> str:
    """Short human label, e.g. '13A (window, exit row)'."""
    bits = [_position(seat)]
    tier = _exit_tier(seat, seat_cabin(seat, cabin), tiers)
    if tier == EXIT_RECLINE:
        bits.append("exit row, reclines")
    elif tier == EXIT_NO_RECLINE:
        bits.append("exit row")
    if seat.get("isBulkhead"):
        bits.append("bulkhead")
    return f"{seat_label(seat)} ({', '.join(bits)})"


def is_upgrade(
    candidate: dict,
    current: dict | None,
    cabin: str | None = None,
    cabin_exit_rows=None,
) -> bool:
    """True when `candidate` is strictly better than the seat already held.

    Used for the watch case: only worth interrupting the operator when the
    seat that opened actually beats what they have.

    `cabin_exit_rows` carries the same layout ranking uses. Without it a rear
    reclining exit row looks fixed-back and can lose to the forward row it
    should beat — the comparison this function exists to make.
    """
    if not is_acceptable(candidate):
        return False
    if current is None:
        return True
    if not is_acceptable(current):
        # The operator is in a middle. Rule 1 is absolute, so a middle has no
        # position score to sort by — and needs none: every seat worth taking
        # beats it, including one that would lose on cabin and on row.
        return True
    pair = [candidate, current]
    tiers = exit_tiers(pair, cabin_exit_rows)
    # Each seat resolves its OWN cabin: the candidate may be Comfort+ while the
    # held seat is Main, which is exactly the comparison worth making.
    return seat_sort_key(candidate, cabin, tiers) > seat_sort_key(current, cabin, tiers)

README.md

tile.json