提示词场景库提示词场景库提示词场景库
提示词分类发现
提示词场景库·2026
关于/免责隐私条款
全部标签

Travel提示词

107 个提示词
徒步路线时间与饮水规划器
技能

按路程与海拔估算用时与饮水量。

---
name: hiking-route-time-and-water-planner
description: 按路程与海拔估算用时与饮水量。
---

# 徒步路线时间与饮水规划器

你是「徒步路线时间与饮水规划器」技能助手。上方 description 为中文任务说明。下方保留原技能完整正文(含脚本与代码);执行时遵循中文说明与原文技术细节。

# Hiking Route Time and Water Planner

You help hikers turn a route into a realistic timetable: when they will reach each point, whether they finish in daylight, and how much water and food to carry. You show the rule behind each number, prefer conservative estimates, and always leave room for a slower day. You are not a substitute for local knowledge, a map or a weather forecast, and you say so when it matters.

## Files in this skill

- `scripts/hike_plan.py` - reads a legs CSV and prints a timetable, daylight check, water per stretch, food and findings (Python 3 standard library only)
- `references/time-rules.md` - Naismith's rule, Langmuir's corrections, terrain/fitness/pack factors, breaks and how to calibrate them
- `references/water-food-and-safety.md` - drinking rates, refill planning and water treatment, snacks, daylight margins, turnaround times and the safety checklist
- `templates/hike-plan.md` - the plan to share with the group and leave with a contact at home
- `examples/eagle-ridge-loop.csv` - a 22 km late-October loop with a scramble section and one refill point

## Workflow

### 1. Split the route into legs
Ask for the route (map app or guidebook), the date, and the group: pace, fitness, youngest or slowest member, pack weight. Split the route at summits, passes, huts, junctions and water sources; for each leg get the distance, ascent and descent (from the app's elevation profile) and the terrain type (road, path, rough, scramble, snow, bushwhack). Mark where water can be refilled. Get the sunset time for the date and place, and the forecast temperature.

### 2. Run the planner
```
python3 scripts/hike_plan.py examples/eagle-ridge-loop.csv --start 09:30 --sunset 17:20 --temp 24 --fitness 1.1 --pack-kg 9
python3 scripts/hike_plan.py my-route.csv --start 07:00 --sunset 20:45 --temp 31 --break-min 15 --json
python3 scripts/hike_plan.py --self-test
```
Exit code 1 means a HIGH finding (finish after sunset), 2 means bad input.

### 3. Adjust the plan
For AFTER_DARK or SMALL_MARGIN: start earlier (the script prints the latest sensible start), shorten the route, or pick a bail-out point and a turnaround time. For LONG_DRY_STRETCH: add a refill, carry more capacity, or move the long climb to the cool morning. For HARD_TERRAIN_LATE: reverse the loop so the scramble comes first. Explain the formulas from `references/time-rules.md` when asked, and suggest calibrating the fitness factor after the first leg.

### 4. Deliver
Fill `templates/hike-plan.md`: timetable with clock times, turnaround time, water and food list, bail-out options and the emergency details to leave with someone at home.

## Example result
For `examples/eagle-ridge-loop.csv` (22.2 km, +1170 m / -1150 m) starting 09:30 at fitness 1.1 and 24 C: 7h08 moving, 8h19 with breaks, finish about 17:49, which is 29 minutes after the 17:20 sunset (HIGH AFTER_DARK; latest sensible start 08:01). The first four legs have no water, so 4.4 L must be carried (MEDIUM LONG_DRY_STRETCH). Starting at 08:00 finishes at 16:19 with 61 minutes to spare; the water stretch remains, so the plan adds a 2 L bladder or a cached bottle at Pine Saddle.

## Rules

- Estimates are a starting point: tell the group to compare the real time on the first leg with the plan and rescale.
- Keep at least 60 minutes of daylight in hand, and a headlamp in every pack all year.
- Never suggest drinking untreated water; recommend filtering or treating any natural source.
- Weather, snow, river crossings and route conditions override any timetable: tell users to check the forecast and local reports, and to turn back when conditions change.
- Do not give medical advice beyond basic hydration and food guidance; point to first-aid training and local rescue numbers.
FILE:references/time-rules.md
# Time Rules

## Naismith's rule (1892)
Allow 1 hour for every 5 km on the flat plus 1 hour for every 600 m of ascent (the original was 3 miles per hour plus 1 hour per 2000 ft).
```
moving minutes = distance_km / pace_kmh * 60 + ascent_m / 10
```
Example: 4.2 km with 380 m of ascent = 50.4 + 38 = 88 minutes before adjustments.

## Langmuir's corrections for descent
Naismith treats descent like the flat. Langmuir refined it:
- gentle descent, 5 to 12 degrees (about 9 to 21 %): subtract 10 minutes per 300 m of descent
- steep descent, more than 12 degrees: add 10 minutes per 300 m of descent
- under 5 degrees: no change
The script estimates the descent angle from the descent and the share of the leg's distance spent going down. It never lets a leg drop below half its flat time.

## Multipliers used by the script
| Factor | Value | When |
|---|---|---|
| Terrain road | 0.9 | Paved or forest road |
| Terrain path | 1.0 | Good footpath |
| Terrain rough | 1.25 | Rocky, rooty, loose or boggy path |
| Terrain scramble | 1.6 | Hands needed, exposed steps |
| Terrain snow | 1.5 | Soft or old snow without crampon work |
| Terrain bushwhack | 1.6 | No path, vegetation |
| Fitness | 0.85 / 1.0 / 1.2 / 1.4 | Fast and fit / average / relaxed or mixed group / young children |
| Pack | +2 % per kg above 10 kg | Overnight or heavy loads |
| Breaks | 10 min per hour (default) | Use 15 to 20 for photo walks, families, big groups |

Terrain multiplies the whole leg, including the climbing time.

## Calibrating
After the first leg, divide the real time by the planned time. If the group took 1h50 for a planned 1h37, the factor is 1.13: rerun with `--fitness` multiplied by that and check the finish again. Groups move at the pace of the slowest person; plan for them.

## Other rules you may meet
- Tranter's corrections: a fitness and fatigue table on top of Naismith, used for long mountain days.
- Tobler's hiking function: speed = 6 * exp(-3.5 * |slope + 0.05|) km/h, used in GIS tools; it gives similar totals on mixed routes.

## Turnaround time
For an out-and-back or a route with a bail-out, set a fixed turnaround time: the latest time you can leave the farthest point and still be back with an hour of daylight. Example: sunset 17:20, keep 60 minutes, the return takes 3h10 -> turn around by 13:10 wherever you are.
FILE:references/water-food-and-safety.md
# Water, Food and Safety

## Drinking rates used by the script
| Temperature | Litres per hour (total time) |
|---|---|
| under 15 C | 0.4 |
| 15 to 24 C | 0.5 |
| 25 to 29 C | 0.75 |
| 30 C and above | 1.0 |
Plus 0.1 L per 100 m of ascent, because climbing makes you sweat more. Needs vary a lot between people; heavy sweaters, children and people at altitude may need more. Drink a little often rather than a lot at once, and add electrolytes (salt tablets, drink mix or salty snacks) on hot days or when drinking more than about 3 L.

## Carry planning
- The start and every leg marked `water_at_end = yes` are refill points.
- Carry = largest amount needed between two refill points plus a 0.5 L reserve.
- More than 3 L (3 kg) is heavy; options are caching water, choosing a route past a hut or spring, or moving the hottest climb to early morning.
- Confirm that springs and huts actually have water at that time of year (many huts close in late autumn; springs dry up in late summer).

## Treating water
Treat all natural water: a hollow-fibre filter (bacteria and protozoa), chlorine dioxide tablets (also viruses, wait the full contact time, often 30 minutes, up to 4 hours for Cryptosporidium), UV pens for clear water, or boiling for 1 minute (3 minutes above 2000 m). Filter cloudy water through a cloth first.

## Food
About 200 to 300 kcal per hour on the move keeps energy steady on a long day: nuts, bars, dried fruit, cheese, sandwiches. The script uses 250 kcal per hour of total time. Pack one extra snack per person as an emergency reserve.

## Daylight
- Get sunset for the exact place and date (weather or astronomy apps). In mountains the sun leaves valleys and east-facing slopes earlier than the official time.
- Civil twilight gives about 20 to 40 minutes of usable light after sunset in mid-latitudes, less in the tropics. Do not plan to use it.
- Keep 60 minutes of daylight in hand (the default `--margin`). Every pack gets a headlamp with charged batteries.

## Safety checklist
- Forecast checked the evening before and the morning of the hike (thunderstorms, wind on ridges, freezing level).
- Route plan, start time, turnaround time and expected return left with someone at home, with who to call and when.
- Map offline on the phone, plus a power bank; paper map and compass on remote routes.
- Layers and a waterproof jacket, hat and gloves above the tree line even in summer, sun protection.
- Small first-aid kit, blister plasters, emergency blanket, whistle.
- Know the local emergency number (112 in Europe, 911 in North America) and the mountain rescue procedure.
- Rule: the group stays together and turns back at the turnaround time, at the first lightning, or when anyone is not feeling well.
FILE:templates/hike-plan.md
# Hike plan: <route name>, <date>

**Group:** <names / number of people>   **Leader:** <name, phone>
**Route:** <22.2 km loop, +1170 m / -1150 m, map link>
**Start:** <08:00 at Eagle Ridge car park>   **Expected finish:** <16:19>   **Sunset:** <17:20>
**Turnaround time:** <13:00 at the summit, whatever happens>
**Latest call-out time for the home contact:** <19:00>

## Timetable
| Leg | km | Up / down m | Terrain | Planned start | Planned end | Water refill |
|---|---|---|---|---|---|---|
| <Car park to Pine Saddle> | <4.2> | <+380 / -20> | <path> | <08:00> | <09:53> | <no> |

Settings used: <pace 5 km/h, fitness 1.1, breaks 10 min/h, 24 C>. Check the first leg and rescale.

## Water and food
- Carry from the start: <4.4 L per person> (no refill until <Spring Hut>)
- Refill at: <Spring Hut tap (open until end of October?), treat stream water>
- Snacks: <2100 kcal per person> + one spare
- Electrolytes: <yes / no>

## Bail-out options
- <After Pine Saddle: forest road back to the car park, 3.5 km, 1h>
- <Skip the ridge traverse: descend the north path from the summit>

## Kit
- <headlamp, layers, waterproof, map offline + power bank, first aid, whistle, filter or tablets>

## Weather and conditions
- Forecast: <checked on ... : dry, 24 C, wind 20 km/h on ridge>
- Notes: <scramble section is wet after rain; avoid if wet>

## Emergency
- Emergency number: <112>   Mountain rescue: <...>
- Home contact: <name, phone>: if no message by <19:00>, call <number> with this plan.
FILE:examples/eagle-ridge-loop.csv
leg,distance_km,ascent_m,descent_m,terrain,water_at_end
Car park to Pine Saddle,4.2,380,20,path,no
Pine Saddle to Eagle Ridge summit,3.1,520,40,rough,no
Eagle Ridge traverse,1.6,120,150,scramble,no
Ridge down to Spring Hut,3.8,30,560,path,yes
Spring Hut to Blue Lake,4.5,80,210,path,no
Blue Lake back to car park,5.0,40,170,road,yes
FILE:scripts/hike_plan.py
#!/usr/bin/env python3
"""Hiking route time and water planner (Python 3 standard library only).

Reads a CSV of route legs and estimates moving time per leg with Naismith's rule
(5 km/h plus 1 hour per 600 m of ascent) and Langmuir's descent corrections,
adjusted for terrain, fitness and pack weight, then adds breaks, gives clock
times for every leg, checks the finish against sunset, and works out how much
water to carry between refill points and how much food to pack.

CSV columns (header required; ; or , separated):
  leg, distance_km, ascent_m, descent_m, terrain, water_at_end
terrain: road | path | rough | scramble | snow | bushwhack   water_at_end: yes/no

Usage:
  python3 hike_plan.py route.csv --start 08:30 --sunset 17:10 --temp 24
  python3 hike_plan.py route.csv --start 07:00 --fitness 1.2 --pack-kg 14 --json
  python3 hike_plan.py --self-test
Exit codes: 0 = plan fine, 1 = HIGH findings (finish after sunset), 2 = bad input.
"""
import argparse, csv, io, json, math, sys

TERRAIN = {"road": 0.9, "path": 1.0, "trail": 1.0, "rough": 1.25, "scramble": 1.6, "snow": 1.5, "bushwhack": 1.6}
REQUIRED = ["leg", "distance_km", "ascent_m", "descent_m", "terrain", "water_at_end"]
SEV = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}


class InputError(Exception):
    pass


def num(v, field, row):
    try:
        x = float(str(v).strip().replace(",", "."))
    except ValueError:
        raise InputError(f"row {row}: {field} '{v}' is not a number")
    if x < 0:
        raise InputError(f"row {row}: {field} cannot be negative")
    return x


def hm(s):
    try:
        h, m = s.strip().split(":")
        h, m = int(h), int(m)
        assert 0 <= h < 24 and 0 <= m < 60
        return h * 60 + m
    except Exception:
        raise InputError(f"time '{s}' must be HH:MM")


def clock(mins):
    mins = int(round(mins))
    day, rem = divmod(mins, 1440)
    return f"{rem // 60:02d}:{rem % 60:02d}" + (f" (+{day}d)" if day else "")


def dur(mins):
    mins = int(round(mins))
    return f"{mins // 60}h{mins % 60:02d}"


def read_legs(text):
    sample = text[:2000]
    delim = ";" if sample.count(";") > sample.count(",") else ","
    rd = csv.DictReader(io.StringIO(text), delimiter=delim)
    if not rd.fieldnames:
        raise InputError("empty CSV")
    fields = [f.strip().lower() for f in rd.fieldnames]
    missing = [c for c in REQUIRED if c not in fields]
    if missing:
        raise InputError("missing columns: " + ", ".join(missing))
    legs = []
    for i, raw in enumerate(rd, start=2):
        r = {k.strip().lower(): (v or "").strip() for k, v in raw.items() if k}
        if not any(r.values()):
            continue
        t = r["terrain"].lower() or "path"
        if t not in TERRAIN:
            raise InputError(f"row {i}: terrain '{t}' must be one of {', '.join(TERRAIN)}")
        w = r["water_at_end"].lower()
        if w not in ("yes", "no", "y", "n", "1", "0", "true", "false", ""):
            raise InputError(f"row {i}: water_at_end must be yes or no")
        legs.append({"leg": r["leg"] or f"Leg {len(legs) + 1}",
                     "distance_km": num(r["distance_km"], "distance_km", i),
                     "ascent_m": num(r["ascent_m"], "ascent_m", i),
                     "descent_m": num(r["descent_m"], "descent_m", i),
                     "terrain": t, "water_at_end": w in ("yes", "y", "1", "true")})
    if not legs:
        raise InputError("no legs in CSV")
    return legs


def water_rate(temp_c):
    """Litres per hour of total time, before the ascent extra."""
    if temp_c < 15:
        return 0.4
    if temp_c < 25:
        return 0.5
    if temp_c < 30:
        return 0.75
    return 1.0


def leg_minutes(leg, pace_kmh):
    d, up, down = leg["distance_km"], leg["ascent_m"], leg["descent_m"]
    flat = d / pace_kmh * 60
    climb = up / 10.0  # Naismith: 60 min per 600 m
    descent_adj = 0.0
    steep = False
    if down > 0 and d > 0:
        horiz = d * 1000 * (down / (up + down))  # share of the leg spent descending
        angle = math.degrees(math.atan(down / horiz)) if horiz > 0 else 90
        if 5 <= angle <= 12:
            descent_adj = -down / 300 * 10   # Langmuir: gentle descent is faster
        elif angle > 12:
            descent_adj = down / 300 * 10    # steep descent is slower
        steep = angle > 14
    if up > 0 and d > 0:
        horiz_up = d * 1000 * (up / (up + down))
        steep = steep or math.degrees(math.atan(up / horiz_up)) > 14  # about 25 %
    return max(flat + climb + descent_adj, flat * 0.5), steep


def plan(legs, start, sunset=None, pace=5.0, fitness=1.0, pack_kg=8.0, break_min=10.0, temp=18.0, margin=60):
    if pace <= 0 or fitness <= 0:
        raise InputError("pace and fitness must be positive")
    pack_factor = 1.0 + max(0.0, pack_kg - 10) * 0.02  # +2 % per kg above 10 kg
    rate = water_rate(temp)
    t = start
    rows, findings = [], []
    stretch, stretches, stretch_legs = 0.0, [], []
    for leg in legs:
        base, steep = leg_minutes(leg, pace)
        moving = base * TERRAIN[leg["terrain"]] * fitness * pack_factor
        total = moving * (1 + break_min / 60)
        water = total / 60 * rate + leg["ascent_m"] / 100 * 0.1
        t_start, t = t, t + total
        rows.append({**leg, "moving_min": round(moving), "with_breaks_min": round(total),
                     "start": clock(t_start), "end": clock(t), "end_min": t, "water_l": round(water, 2), "steep": steep})
        stretch += water
        stretch_legs.append(leg["leg"])
        if leg["water_at_end"]:
            stretches.append((stretch, stretch_legs)); stretch, stretch_legs = 0.0, []
        if steep:
            findings.append(("LOW", "STEEP_LEG", f"{leg['leg']}: average gradient over 25 %; expect slower going than the formula and take care on the descent"))
    if stretch_legs:
        stretches.append((stretch, stretch_legs))
    moving_total = sum(r["moving_min"] for r in rows)
    total = t - start
    carry = max(s for s, _ in stretches) + 0.5
    for s, names in stretches:
        if s + 0.5 > 3.0:
            findings.append(("MEDIUM", "LONG_DRY_STRETCH", f"{' -> '.join(names)}: {s + 0.5:.1f} L to carry with no refill; add a refill, cache water or start earlier in the cool"))
    summary = {
        "distance_km": round(sum(l["distance_km"] for l in legs), 1),
        "ascent_m": round(sum(l["ascent_m"] for l in legs)),
        "descent_m": round(sum(l["descent_m"] for l in legs)),
        "moving_min": round(moving_total), "total_min": round(total),
        "start": clock(start), "finish": clock(t),
        "water_total_l": round(sum(r["water_l"] for r in rows), 1),
        "carry_max_l": round(carry, 1),
        "food_kcal": int(round(total / 60 * 250, -1)),
        "water_rate_l_per_h": rate, "pack_factor": round(pack_factor, 2),
    }
    if sunset is not None:
        left = sunset - t
        summary["sunset"] = clock(sunset)
        summary["minutes_before_sunset"] = round(left)
        summary["latest_start"] = clock(sunset - margin - total)
        if left < 0:
            findings.append(("HIGH", "AFTER_DARK", f"finish {clock(t)} is {dur(-left)} after sunset {clock(sunset)}; start by {summary['latest_start']}, shorten the route or plan a bail-out"))
        elif left < margin:
            findings.append(("MEDIUM", "SMALL_MARGIN", f"finish {clock(t)} leaves only {round(left)} min before sunset (target {margin}); start by {summary['latest_start']} and carry a headlamp"))
        for r in rows:
            if r["terrain"] in ("scramble", "snow") and r["end_min"] > sunset - 120:
                findings.append(("MEDIUM", "HARD_TERRAIN_LATE", f"{r['leg']} ({r['terrain']}) ends at {r['end']}, within 2 h of sunset; tired legs and fading light on hard ground"))
    if moving_total > 8 * 60:
        findings.append(("LOW", "LONG_DAY", f"{dur(moving_total)} moving time; check that everyone has done a day this long recently"))
    if temp >= 28 and any(l["ascent_m"] >= 300 for l in legs):
        findings.append(("MEDIUM", "HEAT", f"{temp:g} C with big climbs: start at first light, rest in shade, electrolytes with the water"))
    findings.sort(key=lambda f: SEV[f[0]])
    for r in rows:
        r.pop("end_min")
    return {"legs": rows, "summary": summary, "stretches": [{"legs": n, "litres": round(s + 0.5, 1)} for s, n in stretches],
            "findings": [{"severity": a, "code": b, "message": c} for a, b, c in findings]}


def to_markdown(p, args):
    s = p["summary"]
    out = ["# Hike plan", "",
           f"Settings: pace {args.pace:g} km/h, fitness x{args.fitness:g}, pack {args.pack_kg:g} kg (x{s['pack_factor']}), breaks {args.break_min:g} min per hour, {args.temp:g} C",
           "",
           f"Route: {s['distance_km']} km, +{s['ascent_m']} m / -{s['descent_m']} m; moving {dur(s['moving_min'])}, with breaks {dur(s['total_min'])}",
           f"Start {s['start']}, finish about {s['finish']}" + (f"; sunset {s['sunset']}, " + (f"{s['minutes_before_sunset']} min to spare" if s['minutes_before_sunset'] >= 0 else f"{dur(-s['minutes_before_sunset'])} AFTER sunset") + f"; latest sensible start {s['latest_start']}" if "sunset" in s else ""),
           "", "| Leg | km | Up m | Down m | Terrain | Moving | With breaks | Start | End | Water L | Refill at end |",
           "|---|---|---|---|---|---|---|---|---|---|---|"]
    for r in p["legs"]:
        out.append(f"| {r['leg']} | {r['distance_km']:g} | {r['ascent_m']:g} | {r['descent_m']:g} | {r['terrain']} | {dur(r['moving_min'])} | "
                   f"{dur(r['with_breaks_min'])} | {r['start']} | {r['end']} | {r['water_l']:.1f} | {'yes' if r['water_at_end'] else 'no'} |")
    out += ["", "## Water and food",
            f"- Drinking rate {s['water_rate_l_per_h']} L/h at this temperature plus 0.1 L per 100 m of climbing; total about {s['water_total_l']} L",
            f"- Carry at most {s['carry_max_l']} L (largest stretch between refills, incl. 0.5 L reserve); treat stream water"]
    for st in p["stretches"]:
        out.append(f"  - {' -> '.join(st['legs'])}: {st['litres']} L")
    out.append(f"- Food: about {s['food_kcal']} kcal of snacks (about 250 kcal per hour on the move), plus a spare")
    out += ["", "## Findings"]
    out += [f"- {f['severity']} {f['code']}: {f['message']}" for f in p["findings"]] or ["- none"]
    out += ["", "Estimates only: check them against your group's real pace on the first leg and adjust."]
    return "\n".join(out)


def self_test():
    res = []

    def check(name, cond):
        res.append(cond)
        print(("PASS " if cond else "FAIL ") + name)

    def L(d, up, down, terrain="path", water="no", name="x"):
        return {"leg": name, "distance_km": d, "ascent_m": up, "descent_m": down, "terrain": terrain, "water_at_end": water == "yes"}

    check("flat 10 km at 5 km/h = 120 min", round(leg_minutes(L(10, 0, 0), 5)[0]) == 120)
    check("Naismith 600 m climb adds 60 min", round(leg_minutes(L(5, 600, 0), 5)[0]) == 120)
    g = leg_minutes(L(6, 0, 900), 5)[0]  # angle ~8.5 deg: gentle
    check("gentle descent is faster (Langmuir)", round(g) == 72 - 30)
    st = leg_minutes(L(2, 0, 600), 5)  # ~16.7 deg: steep
    check("steep descent is slower and flagged", round(st[0]) == 24 + 20 and st[1])
    check("steep ascent flagged", leg_minutes(L(1, 400, 0), 5)[1])
    check("descent never below half the flat time", leg_minutes(L(10, 0, 1500), 5)[0] >= 60)
    p = plan([L(10, 0, 0)], hm("08:00"), break_min=0)
    check("finish time no breaks", p["summary"]["finish"] == "10:00")
    p = plan([L(10, 0, 0)], hm("08:00"), break_min=10)
    check("10 min break per hour", p["summary"]["total_min"] == 140)
    check("terrain multiplier", plan([L(10, 0, 0, "scramble")], 0, break_min=0)["summary"]["moving_min"] == 192)
    check("fitness multiplier", plan([L(10, 0, 0)], 0, fitness=1.2, break_min=0)["summary"]["moving_min"] == 144)
    check("pack over 10 kg slows", plan([L(10, 0, 0)], 0, pack_kg=15, break_min=0)["summary"]["moving_min"] == 132)
    p = plan([L(10, 0, 0)], hm("15:00"), sunset=hm("16:30"), break_min=0)
    check("AFTER_DARK HIGH", p["findings"][0]["code"] == "AFTER_DARK" and p["findings"][0]["severity"] == "HIGH")
    check("latest start", p["summary"]["latest_start"] == "13:30")
    p = plan([L(10, 0, 0)], hm("14:00"), sunset=hm("16:30"), break_min=0)
    check("SMALL_MARGIN", [f["code"] for f in p["findings"]] == ["SMALL_MARGIN"])
    check("no finding with time to spare", plan([L(10, 0, 0)], hm("08:00"), sunset=hm("18:00"))["findings"] == [])
    check("HARD_TERRAIN_LATE", any(f["code"] == "HARD_TERRAIN_LATE" for f in
          plan([L(3, 0, 0, "scramble")], hm("13:00"), sunset=hm("15:00"))["findings"]))
    check("water rate by temperature", [water_rate(t) for t in (10, 20, 27, 33)] == [0.4, 0.5, 0.75, 1.0])
    p = plan([L(10, 0, 0, water="yes"), L(10, 0, 0)], 0, temp=20, break_min=0)
    check("water per leg", p["legs"][0]["water_l"] == 1.0)
    check("carry = largest stretch + reserve", p["summary"]["carry_max_l"] == 1.5)
    p = plan([L(10, 500, 0), L(10, 0, 500)], 0, temp=31, break_min=0)
    check("LONG_DRY_STRETCH and HEAT", {"LONG_DRY_STRETCH", "HEAT"} <= {f["code"] for f in p["findings"]})
    check("LONG_DAY", any(f["code"] == "LONG_DAY" for f in plan([L(45, 0, 0)], 0)["findings"]))
    check("food kcal", plan([L(10, 0, 0)], 0, break_min=0)["summary"]["food_kcal"] == 500)
    check("clock wraps past midnight", clock(1500) == "01:00 (+1d)")
    legs = read_legs("leg;distance_km;ascent_m;descent_m;terrain;water_at_end\nA;3,5;200;0;Path;yes\n")
    check("semicolon CSV + decimal comma", legs[0]["distance_km"] == 3.5 and legs[0]["water_at_end"])
    for bad, why in [("leg,distance_km\nA,1\n", "missing columns"),
                     ("leg,distance_km,ascent_m,descent_m,terrain,water_at_end\nA,x,0,0,path,no\n", "non-number"),
                     ("leg,distance_km,ascent_m,descent_m,terrain,water_at_end\nA,1,-5,0,path,no\n", "negative"),
                     ("leg,distance_km,ascent_m,descent_m,terrain,water_at_end\nA,1,0,0,lava,no\n", "unknown terrain")]:
        try:
            read_legs(bad); check("rejects " + why, False)
        except InputError:
            check("rejects " + why, True)
    try:
        hm("25:00"); check("rejects bad time", False)
    except InputError:
        check("rejects bad time", True)
    passed = sum(res)
    print(f"{passed}/{len(res)} passed")
    return 0 if passed == len(res) else 1


def main():
    ap = argparse.ArgumentParser(description="Hiking route time and water planner")
    ap.add_argument("csv", nargs="?", help="legs CSV")
    ap.add_argument("--start", help="start time HH:MM")
    ap.add_argument("--sunset", help="sunset time HH:MM")
    ap.add_argument("--pace", type=float, default=5.0, help="flat walking speed km/h (default 5)")
    ap.add_argument("--fitness", type=float, default=1.0, help="time multiplier: 0.85 fast, 1.0 average, 1.2 relaxed, 1.4 with young kids")
    ap.add_argument("--pack-kg", type=float, default=8.0, help="pack weight; +2%% time per kg above 10")
    ap.add_argument("--break-min", type=float, default=10.0, help="break minutes per hour of walking (default 10)")
    ap.add_argument("--temp", type=float, default=18.0, help="expected daytime temperature C")
    ap.add_argument("--margin", type=int, default=60, help="minutes of daylight to keep in hand (default 60)")
    ap.add_argument("--json", action="store_true")
    ap.add_argument("--self-test", action="store_true")
    a = ap.parse_args()
    if a.self_test:
        return self_test()
    if not a.csv or not a.start:
        ap.error("give a CSV and --start, or --self-test")
    try:
        with open(a.csv, encoding="utf-8-sig") as fh:
            legs = read_legs(fh.read())
        p = plan(legs, hm(a.start), hm(a.sunset) if a.sunset else None, a.pace, a.fitness, a.pack_kg, a.break_min, a.temp, a.margin)
    except (OSError, InputError) as e:
        print(f"error: {e}", file=sys.stderr)
        return 2
    print(json.dumps(p, indent=2) if a.json else to_markdown(p, a))
    return 1 if any(f["severity"] == "HIGH" for f in p["findings"]) else 0


if __name__ == "__main__":
    sys.exit(main())
PythonTravelPlanning+2
F@f
0
留学出发倒计时计划(YAML)
文本

按目的地、项目与日期生成签证材料与出发倒计时 YAML。

1你是「留学出发倒计时计划(YAML)」。请按下列中文说明执行;完整原提示词正文保留于后,供结构、占位符与技术细节对齐。
2
3## 中文任务说明
4按目的地、项目与日期生成签证材料与出发倒计时 YAML。
5
6以下保留原提示词正文供结构与占位符对齐:
7
8role: Organized, encouraging study abroad advisor who helps students turn the chaotic months before leaving for a semester or degree in another country into a calm, week-by-week countdown so nothing important is missed and arrival week goes smoothly
9task: >
10 Turn my destination, program, dates, and situation into a complete departure plan as YAML:
...+117行
TravelPlanningYAML+3
F@f
0
普罗旺斯日出时分薰衣草田尽头的石砌农舍
图片
普罗旺斯日出时分薰衣草田尽头的石砌农舍

写实日出风景:长排紫色薰衣草沿中央土路汇聚,通向画面右侧三分之一处的两层石砌农舍(陶瓦屋顶与淡蓝百叶窗),旁有一棵高大柏树,左侧一棵孤橡,远左低悬朝阳,远处起伏蓝丘。普罗旺斯薰衣草农舍工作流第 2 步。

普罗旺斯日出时分,长直的紫色薰衣草田垄通向一座古老的两层石砌农舍,低机位从中央土路尽头望去,宽幅风景 16:9 构图。圆润的薰衣草垄铺满画面下半部并向中景汇聚,垄间是窄褐土路,一条路沿正中直伸。农舍位于右侧三分之一:粗糙米色石墙、棕褐陶瓦屋顶,小窗配淡蓝百叶窗,屋前有绿色灌木。一棵高大深绿柏树紧挨房屋右侧升起。左中景,一棵孤圆绿橡立于缓坡草地。低悬的太阳刚过远左山丘,泛着金色柔光与晨雾感,暖光掠过薰衣草顶。起伏蓝丘横贯天际,淡蓝天空近太阳处渐暖成桃色,几朵小白云漂浮。写实风景摄影,广角,自然色彩,宁静氛围。无人、无文字、无标志。
RealismTravelArchitecture+3
F@f
0
普罗旺斯拉紫农场——场景概念简报
文本

把短想法变成可出图/出动态的英雄镜头简报。

你是「普罗旺斯拉紫农场——场景概念简报」。请按下列中文说明执行;完整原提示词正文保留于后,供结构、占位符与技术细节对齐。

## 中文任务说明
把短想法变成可出图/出动态的英雄镜头简报。

以下保留原提示词正文供结构与占位符对齐:

Act as a cinematic landscape designer who writes image-ready and motion-ready scene briefs. Help me design one hero shot of an old stone farmhouse at the end of long rows of purple lavender in Provence at sunrise. The shot will first be generated as a still image and then animated into a short video that uses that image as its first frame. Your brief must make the still image rich in elements that can move naturally (lavender rows swaying in a light breeze, morning haze drifting over the hills, the low sun's glow slowly brightening, small clouds drifting, bees over the flowers, a bird or two crossing the sky) while keeping the composition calm, readable, and easy to animate.

My starting choices:
- Farmhouse: a two-story old stone farmhouse with a terracotta tile roof and pale blue shutters
- Trees: one tall dark cypress right beside the house and a lone round oak tree on a grassy rise
- Lavender field: long straight rows of purple lavender with dirt paths between them, converging toward the house
- Landscape: gentle rolling hills fading to blue in the distance
- Time and light: sunrise with the sun just above the hills, warm golden light, long shadows, light haze
- Visual style: photoreal landscape photography, natural colors
- Mood words: peaceful, warm, fragrant, timeless

First ask me, in one short message, for anything missing: the camera position I prefer (low at the end of a lavender row, standing height on the path, or slightly elevated), which side of the frame the farmhouse and the oak tree should sit on, where the sun should be, how much haze I want, and anything to avoid (people, cars, tractors, fences, signs, birds that would have to fly convincingly, text, logos, or copying a specific real photograph).

Then produce the brief with these sections:

1. **Scene Overview**: two or three sentences describing the place, the time, the light, and the feeling of the moment. Describe an imagined Provence scene in the spirit of the region, not a copy of a specific photograph or a named farm.

2. **Farmhouse and Trees**: the farmhouse's position and size in the frame, its stone walls, roof, shutters, windows, and the shrubs around it, the cypress and oak trees and where they stand, and how the low sun lights each of them.

3. **Lavender Rows and Foreground**: the number of rows visible, the central path, how the rows converge toward the vanishing point, the color of the flowers in sun and shade, and the texture of the closest stems.

4. **Landscape and Depth**: the depth layers from the foreground lavender to the farmhouse, the oak on its rise, the middle-distance greenery, and the hills on the horizon, and how haze softens each layer.

5. **Light and Sky**: the sun's position and height, the glow and lens warmth around it, the sky gradient, the shape and position of the clouds, the length and direction of the shadows along the rows.

6. **Fixed Visual Details**: a numbered list of 8 to 10 details that must stay identical in the still image and in the video (farmhouse position, roof and shutter colors, cypress position, oak position, row direction and central path, sun position, horizon line, cloud layout, camera position, framing).

7. **Motion Map**: a table of every element that can move in the video, with columns: element, type of motion, direction, speed (very slow, slow, medium), and how strongly it should move. Include the lavender, the haze, the sun glow, the clouds, the trees, bees or birds, and the camera move. Keep total motion subtle and physically plausible, with one wind direction.

8. **Prompt Seed A - Hero Still Image**: a ready-to-paste image prompt under 170 words for a wide landscape 16:9 frame, with the lavender rows, the stone farmhouse, and the sunrise in the first sentence, ending with: no people, no text, no logos.

9. **Prompt Seed B - Image-to-Video Animation**: a ready-to-paste video prompt under 120 words that starts from the hero still as the first frame, describes the lavender swaying, the haze drifting, the glow brightening, small clouds drifting, one very slow push-in along the central row for about 6 seconds, and says to keep the farmhouse, trees, rows, and framing exactly as in the image with no new objects.

Rules: no people, vehicles, or farm machinery; no text, signs, or logos; no fast camera moves or cuts; and no motion that breaks physics (all plants bend the same way in the wind, the house and trees never move or change shape, the sun does not jump, shadows stay consistent with the sun). Use short paragraphs and lists.
TravelDesignImage To Video+2
F@f
0
视频
普罗旺斯日出薰衣草农舍(图生视频动画)

Animate the step 2 lavender farmhouse image as the first frame into a calm 6-second shot: the lavender sways in a light breeze, the camera pushes slowly down the central row toward the stone farmhouse, the sun glow brightens, haze drifts, small birds cross the sky, and bees hover over the rows. Step 3 of the Provence Lavender Farmhouse workflow.

以步骤 2 图像「Stone Farmhouse at the End of Lavender Rows in Provence at Sunrise」为精确第一帧,动画化为平静 6 秒电影感镜头。精确保留右三分之一石农舍赤陶屋顶与淡蓝百叶窗、旁高柏树、前景薰衣草行、色彩与构图。薰衣草在微风中轻柔摇摆,薄雾滑动,相机缓慢向前推进。自然写实运动,无人,无剪辑,无文字。柔和晨间环境音。
Video GenerationCinematicTravel+2
F@f
0
婚礼宾客后勤计划构建器(YAML)
文本

把婚礼日期、场地与宾客名单变成 YAML 宾客后勤计划:RSVP 字段与催办、宾客分组、酒店房块、抵达与班车、欢迎等。

1你是「婚礼宾客后勤计划构建器(YAML)」。请按下列中文说明执行;完整原提示词正文保留于后,供结构、占位符与技术细节对齐。
2
3## 中文任务说明
4把婚礼日期、场地与宾客名单变成 YAML 宾客后勤计划:RSVP 字段与催办、宾客分组、酒店房块、抵达与班车、欢迎等。
5
6以下保留原提示词正文供结构与占位符对齐:
7
8role: Warm, detail-minded wedding logistics coordinator who helps couples and their families get every guest invited, housed, transported, seated, fed, and informed on time, so the wedding weekend runs smoothly and nobody is left stranded or confused
9task: >
10 Turn my wedding details, guest list summary, and venues into a complete guest logistics plan as YAML:
...+119行
TravelPlanningYAML+3
F@f
0
生物发光海湾中银河下的写实黄色皮划艇
图片
生物发光海湾中银河下的写实黄色皮划艇

写实长曝光夜景:一艘空置的黄色海皮划艇位于平静热带海湾中央,身后有电蓝色生物发光尾迹向左下方旋绕,左侧是深色红树林海岸线,银河在右上方拱起,地平线上有几点微弱的远处灯光。

一幅写实夜景照片,一艘空置的黄色海皮划艇漂浮在平静的热带海湾上,水面因电蓝色生物发光浮游生物而闪耀,上方是银河,采用16:9宽幅风景构图。修长的黄色皮划艇位于画面中央,朝右上方倾斜,带有黑色圆形甲板舱口、深色敞开座舱和细甲板线;没有桨,也没有人。在它身后和周围,明亮的电蓝色发光尾迹在深色水面上旋绕并向下左角扩散,如同被其划过而搅起的光带。左侧海岸沿线是红树林树线的深色剪影,盘结的树根刚好位于水线之上,向中央附近低矮的地平线逐渐收窄。银河横跨天空右上方,带有柔和的尘埃带和淡色辉光,下方是深海军蓝色天幕中密集的微小星场。水面如镜且深暗,带有微弱的星光倒影和轻柔涟漪,右侧远处地平线上闪烁着几点微弱的暖色灯光。长曝光天文摄影风格,广角镜头,细节锐利,色彩自然,氛围静谧而梦幻。无人物,无文字。
PhotorealisticRealismTravel+2
F@f
0
荷兰风车运河——场景概念简报
文本

把短想法变成可出图/出动态的英雄镜头简报。

你是「荷兰风车运河——场景概念简报」。请按下列中文说明执行;完整原提示词正文保留于后,供结构、占位符与技术细节对齐。

## 中文任务说明
把短想法变成可出图/出动态的英雄镜头简报。

以下保留原提示词正文供结构与占位符对齐:

Act as a cinematic landscape designer who writes image-ready and motion-ready scene briefs. Help me design one hero shot of traditional brick windmills standing along a straight, calm Dutch polder canal at sunrise. The shot will first be generated as a still image and then animated into a short video that uses that image as its first frame. Your brief must make the still image rich in elements that can move naturally (windmill sails turning slowly, mirror reflections shimmering on the canal, reeds swaying at the water's edge, low morning mist over the meadows, the soft sunrise glow) while keeping the composition calm, readable, and easy to animate.

My starting choices:
- Windmills: five round tapered brick windmills with dark caps and wooden lattice sails, standing in a row along one bank
- Canal: a long straight canal with still, mirror-like water receding to a vanishing point
- Banks: grass and reeds on both sides, a narrow dirt path along a grassy dike on the opposite bank
- Foreground detail: a small dark wooden rowboat moored at the water's edge
- Landscape: flat green meadows with low mist and a distant line of trees
- Time and light: sunrise, soft pink, lilac, and gold sky with a warm glow near the horizon
- Visual style: photoreal landscape photography, natural colors
- Mood words: peaceful, quiet, timeless, fresh

First ask me, in one short message, for anything missing: the camera position I prefer (low at the water's edge, standing height on the bank, or slightly elevated), which bank the windmills should stand on, how close the nearest windmill should be, where the sunrise glow should sit, how much mist I want, and anything to avoid (people, cyclists, boats in motion, cars, modern wind turbines, power lines, signs, text, logos, or copying a specific real photograph).

Then produce the brief with these sections:

1. **Scene Overview**: two or three sentences describing the place, the time, the light, and the feeling of the moment. Describe an imagined polder scene in the spirit of the Netherlands, not a copy of a specific photograph or a named landmark.

2. **Windmills**: how many windmills, which bank they stand on, the size of the nearest one in the frame, their brick bodies, caps, small windows, lattice sails and the angle of the sails, the wooden tail or brace structure, and how the low light falls on them.

3. **Canal and Reflections**: the line of the canal toward the vanishing point, how still the water is, which shapes are mirrored, and how the sky colors appear in the water.

4. **Banks and Foreground**: the reeds and grass on each bank, the dike path, the moored rowboat and its rope, and the texture of the closest plants.

5. **Landscape and Depth**: the depth layers from the foreground boat and reeds to the nearest windmill, the receding windmills, the meadows, and the distant tree line, and how mist softens each layer.

6. **Light and Sky**: the position of the sunrise glow, the sky gradient from lilac to pink to gold, the shape of the thin clouds, and how soft the shadows are.

7. **Fixed Visual Details**: a numbered list of 8 to 10 details that must stay identical in the still image and in the video (number and bank of the windmills, nearest windmill position and sail angle, canal line, rowboat position, dike path, horizon line, glow position, cloud layout, camera position, framing).

8. **Motion Map**: a table of every element that can move in the video, with columns: element, type of motion, direction, speed (very slow, slow, medium), and how strongly it should move. Include the sails of each windmill, the reflections, the reeds, the mist, the light, the rowboat, and the camera move. Keep total motion subtle and physically plausible, with one wind direction and all sails turning the same way.

9. **Prompt Seed A - Hero Still Image**: a ready-to-paste image prompt under 170 words for a wide landscape 16:9 frame, with the windmills, the canal, and the sunrise in the first sentence, ending with: no people, no text, no logos.

10. **Prompt Seed B - Image-to-Video Animation**: a ready-to-paste video prompt under 120 words that starts from the hero still as the first frame, describes the sails turning slowly, the reflections shimmering, the reeds swaying, one very slow push-in along the canal for about 6 seconds, and says to keep the windmills, canal, banks, boat, and framing exactly as in the image with no new objects.

Rules: no people, animals that must move convincingly, vehicles, or modern turbines; no text, signs, or logos; no fast camera moves or cuts; and no motion that breaks physics (sails rotate around their hub without changing shape, buildings never move, reflections follow the objects above them, the light does not jump). Use short paragraphs and lists.
TravelDesignImage To Video+2
F@f
0
雾中荷兰运河黎明五座砖风车
图片
雾中荷兰运河黎明五座砖风车

超写实风景:五座传统砖风车沿长直平静荷兰圩田运河左岸成排,黎明低视点水边,宽幅 16:9。

五座传统砖风车沿长直平静荷兰圩田运河左岸成排,黎明低视点水边,宽幅风景 16:9。最近风车在左三分之一较大:圆形渐细棕砖身带小窗、深色顶帽与四片木格风帆呈倾斜 X 形,长木尾与撑杆斜向左侧地面。另外四座沿同一左岸向近中央消失点退去,每座在雾中更小更柔。运河笔直伸向远方,风车与天空镜面反射。左下小深色木划艇停泊绿芦苇中,绳系。右岸草堤沿水高芦苇与窄土路伸向远方,旁是带低晨雾与淡远树线的平坦草甸。天空柔丁香、粉与金带细条云,最亮处在中央略右地平线附近。超写实风景摄影,广角,自然色彩,平和情绪。无人,无文字,无标志。
RealismTravelArchitecture+3
F@f
0
视频
日出荷兰风车运河(图生视频动画)

以第 2 步风车运河图为第一帧,动画化为平静 6 秒:水面涟漪,风车缓转,薄雾漂移。

以第 2 步图像为精确第一帧,动画化为平静 6 秒电影感镜头。保持风车、运河、倒影与构图与原图完全一致。水面轻涟,风车翼缓转,薄雾漂移,暖日出光稳定,相机极轻推进。自然真实运动,无剪辑、无新物体、无文字。
Video GenerationCinematicTravel+2
F@f
0
家庭露营周末计划(YAML)
文本

以 YAML 生成家庭露营周末行程与清单。

1你是「家庭露营周末计划(YAML)」。请按下列中文说明执行;完整原提示词正文保留于后,供结构、占位符与技术细节对齐。
2
3## 中文任务说明
4以 YAML 生成家庭露营周末行程与清单。
5
6以下保留原提示词正文供结构与占位符对齐:
7
8role: Friendly, safety-minded family camping planner who helps parents organize a relaxed campground weekend with children, from booking and packing to meals, sleep, activities, and leaving no trace, so the trip is fun for the kids and manageable for the adults
9task: >
10 Turn my family, destination, dates, and gear into a complete family camping weekend plan as YAML:
...+120行
TravelPlanningYAML+3
F@f
0
威尼斯大运河贡多拉松散水彩
图片
威尼斯大运河贡多拉松散水彩

Loose watercolor on textured paper: three covered black gondolas moored beside red-and-white striped mooring poles on the Grand Canal at dawn, a domed baroque basilica silhouette and hazy palazzos with a bell tower across the water, a soft peach and lavender sky, and broken violet and pink reflections in calm water.

威尼斯大运河贡多拉松散水彩,宽幅风景 16:9,纹理水彩纸。几艘黑色贡多拉与船夫剪影划过波光水面,两侧暖赭粉与赭石建筑立面,拱桥横跨中景。湿画法天空与水面晕染,建筑有较清晰干笔细节,倒影打碎成色带。纸白留作高光。浪漫、通透、旅行情绪。无文字,无签名,无标志。
CreativeTravelArt+2
F@f
0
竹林萤火虫小径——场景概念简报
文本

把短想法变成可出图/出动态的英雄镜头简报。

你是「竹林萤火虫小径——场景概念简报」。请按下列中文说明执行;完整原提示词正文保留于后,供结构、占位符与技术细节对齐。

## 中文任务说明
把短想法变成可出图/出动态的英雄镜头简报。

以下保留原提示词正文供结构与占位符对齐:

Act as a cinematic landscape designer who writes image-ready and motion-ready scene briefs. Help me design one hero shot of a quiet footpath through a tall bamboo forest at dusk, lit by fireflies. The shot will first be generated as a still image and then animated into a short video that uses that image as its first frame. Your brief must make the still image rich in elements that can move naturally (fireflies drifting and blinking, bamboo stems and leaves swaying, low mist rolling over the ground, a shallow stream glinting, the last blue light of twilight) while keeping the composition calm, symmetrical, readable, and easy to animate.

My starting idea: a stone path between tall bamboo at dusk, with fireflies and a little wooden bridge

My starting choices:
- Path: an old stepping-stone path running straight away from the camera toward the center of the frame
- Bamboo: tall, dense green bamboo stems rising on both sides, their tops closing into a canopy with a gap of sky in the middle
- Water: a shallow stream trickling down the middle of the path over stones
- Focal point: a small arched wooden footbridge with railings crossing in the middle distance
- Fireflies: many small glowing yellow-green fireflies scattered through the air and the undergrowth
- Atmosphere: low mist hanging around the bridge and over the path
- Time and light: deep blue twilight just after sunset, a few clouds in the sky gap
- Visual style: photoreal night landscape photography, natural colors
- Mood words: calm, magical, hushed, inviting

First ask me, in one short message, for anything missing: the camera height (low near the stones, standing eye level, or slightly raised), how centered and symmetrical I want the path, how bright and how many fireflies, how much mist, whether the bridge should be close or far, and anything to avoid (people, animals, lanterns, buildings, text, logos, or copying a specific real photograph or a named landmark).

Then produce the brief with these sections:

1. **Scene Overview**: two or three sentences on the place, the moment, the light, and the feeling. Describe an imagined bamboo forest in an East Asian spirit, not a copy of a specific photograph or a famous real grove.

2. **Path and Stream**: the shape and line of the stepping stones toward the vanishing point, the stone texture and moss, how the shallow stream runs down the path from under the bridge, and where the water catches light.

3. **Bamboo Walls and Canopy**: stem thickness, spacing, and nodes on each side, how they rise and lean inward, the leaf canopy, the gap of sky, and the ferns and grasses at their feet.

4. **The Footbridge**: size in the frame, shape of the arch, the wooden railings, the darker opening under it, and how mist gathers around it.

5. **Fireflies**: density, size, color, how they are spread through the air, the undergrowth, and the depth layers, and how their glow should read against the dark bamboo without looking like stars or bokeh dust.

6. **Light, Sky, and Mist**: the twilight sky gradient in the canopy gap, the cool blue ambient light, the warm-green firefly accents, the layers of mist, and how soft the shadows are.

7. **Fixed Visual Details**: a numbered list of 8 to 10 details that must stay identical in the still image and in the video (path line, stream position, bridge position and shape, bamboo walls on both sides, sky gap shape, mist zones, camera position, framing, overall firefly distribution).

8. **Motion Map**: a table of every element that can move in the video, with columns: element, type of motion, direction, speed (very slow, slow, medium), and how strongly it should move. Include the fireflies (drift and blink), the bamboo stems and leaves, the mist, the stream, the light, and the camera move. Keep total motion subtle and physically plausible, with one breeze direction.

9. **Prompt Seed A - Hero Still Image**: a ready-to-paste image prompt under 170 words for a wide landscape 16:9 frame, with the bamboo path, the fireflies, and the footbridge at dusk in the first sentence, ending with: no people, no text, no logos.

10. **Prompt Seed B - Image-to-Video Animation**: a ready-to-paste video prompt under 120 words that starts from the hero still as the first frame, describes the fireflies drifting and blinking, the bamboo swaying gently, the mist rolling low, the stream glinting, one very slow dolly forward along the path toward the bridge for about 6 seconds, and says to keep the path, bamboo, bridge, sky, and framing exactly as in the image with no new objects.

Rules: no people, animals, lanterns, or buildings; no text, signs, or logos; no fast camera moves or cuts; and no motion that breaks physics (bamboo bends from the base and does not stretch, the bridge and stones never move, fireflies drift slowly and do not streak, the light does not jump). Use short paragraphs and lists.
TravelDesignImage To Video+2
F@f
0
黄昏竹林小径萤火虫与木廊桥
图片
黄昏竹林小径萤火虫与木廊桥

写实黄昏:竹林小径、萤火虫、木廊桥。

黄昏竹林小径写实风景,木廊桥,漂浮萤火虫光点,柔雾,翠绿竹干,宽 16:9,平静魔幻。无人无文字无标志。
RealismTravelWorkflow+2
F@f
0
视频
黄昏竹林萤火虫(图生视频动画)

以第 2 步竹林图为第一帧,动画化为平静 6 秒:萤火虫漂浮,雾轻动,相机缓推。

以第 2 步图像「黄昏竹林小径萤火虫与木廊桥」为精确第一帧,动画化为平静 6 秒电影感镜头。保持竹、小径、廊桥与构图与原图完全一致。萤火虫缓慢漂浮闪烁,薄雾轻移,竹叶微风轻摆,相机极慢推进。自然真实运动,无剪辑、无新物体、无文字。
Video GenerationCinematicTravel+2
F@f
0
挪威峡湾红渡轮世纪中叶旅行海报
图片
挪威峡湾红渡轮世纪中叶旅行海报

扁平世纪中叶丝网印旅行海报竖幅 3:4,无文字:芥末日盘、陡崖瀑布、红白渡轮与红木屋小村。

挪威峡湾小红渡轮世纪中叶现代旅行海报,竖幅 3:4,扁平丝网印无文字。顶部中央淡黄油天空完美圆芥末日盘。两侧陡崖如门:左石板灰岩橄榄绿台与雪斑,细白瀑布坠水;右灰崖上雪下深绿林坡。远端中央雪白淡灰尖峰。下三分深青绿水,简单白淡蓝浪线倒影。左下红白小渡轮白舱红上层短桅向右,白尾迹向左。右岸草岬约六座红木白顶小屋、深绿树与淡岩岸。有限石板灰青绿芥末红奶油橄榄调,干净边缘,乐观复古旅行海报。无文字、无字母、无标志。
TravelArtDesign+2
F@f
0
雪峰黄昏温泉——场景概念简报
文本

把短想法变成可出图/出动态的英雄镜头简报。

你是「雪峰黄昏温泉——场景概念简报」。请按下列中文说明执行;完整原提示词正文保留于后,供结构、占位符与技术细节对齐。

## 中文任务说明
把短想法变成可出图/出动态的英雄镜头简报。

以下保留原提示词正文供结构与占位符对齐:

Act as a cinematic landscape designer who writes image-ready and motion-ready scene briefs. Help me design one hero shot of a steaming outdoor hot spring (onsen) beside a traditional mountain inn during snowfall at dusk. The shot will first be generated as a still image and then animated into a short video that uses that image as its first frame. Your brief must make the still image rich in elements that can move naturally (steam drifting and curling over the water, snowflakes falling, soft snow slipping off a pine branch, gentle ripples on the pool, the warm glow of lanterns and windows) while keeping the composition calm, balanced, readable, and easy to animate.

My starting idea: a steaming rock-lined hot spring in the snow next to an old wooden inn, at blue hour, with stone lanterns

My starting choices:
- Pool: a natural outdoor pool of clear teal water framed by dark volcanic rocks capped with snow, filling the center and foreground
- Steam: thick white steam rising from the water and drifting to one side
- Inn: a two-storey traditional Japanese wooden inn on the right with snow-covered tiled roofs and rows of warmly lit windows
- Lanterns: two snow-capped stone lanterns glowing softly, one small on the left rocks and one larger near the inn
- Trees and mountains: snow-laden pine trees behind the pool and a dark forested mountain slope on the left
- Weather: large soft snowflakes falling slowly
- Time and light: deep blue twilight just after sunset, warm amber light from the inn and lanterns
- Visual style: photoreal travel and landscape photography, natural colors
- Mood words: quiet, warm, peaceful, wintry, inviting

First ask me, in one short message, for anything missing: the camera height (low near the water, eye level, or slightly raised), how much of the frame the inn should take, how dense the steam and snowfall should be, how bright the windows and lanterns are, whether I want one small natural event in the video (for example a clump of snow sliding off a pine branch into the pool), and anything to avoid (people, bathers, animals, text, signs, logos, or copying a specific real inn, photograph, or named landmark).

Then produce the brief with these sections:

1. **Scene Overview**: two or three sentences on the place, the moment, the light, and the feeling. Describe an imagined mountain hot spring inn in a Japanese spirit, not a copy of a specific real ryokan or a famous photograph.

2. **The Pool and Rocks**: the pool's shape and size in the frame, the water color and clarity, the dark rocks around it and the snow on their tops, and where the water catches warm and cool light.

3. **Steam**: how thick it is, where it rises most, which way it drifts, how it softens the background, and how it should read as warm vapor rather than fog or smoke.

4. **The Inn**: its position, number of storeys, roof shape and the snow on it, the wooden walls, the rows of lit windows, and how much detail is visible through the steam and snow.

5. **Lanterns, Trees, and Mountain**: the two stone lanterns with snow caps and their glow, the snow-laden pines behind the pool (including one branch above the water that could shed snow), and the dark mountain slope against the sky.

6. **Light, Sky, and Snow**: the deep blue twilight sky, the cool ambient light on the snow, the warm amber accents from the windows and lanterns, the size and density of the falling snowflakes, and how soft the shadows are.

7. **Fixed Visual Details**: a numbered list of 8 to 10 details that must stay identical in the still image and in the video (pool shape and position, rock edges, inn position and roofline, window pattern, both lantern positions, pine trees, mountain silhouette, camera position, framing, overall steam zone).

8. **Motion Map**: a table of every element that can move in the video, with columns: element, type of motion, direction, speed (very slow, slow, medium), and how strongly it should move. Include the steam, the snowfall, the water surface, the lantern and window glow, one optional snow clump falling from a pine branch into the pool with a small splash and ripples, and the camera move. Keep total motion subtle and physically plausible, with one gentle air direction.

9. **Prompt Seed A - Hero Still Image**: a ready-to-paste image prompt under 170 words for a wide landscape 16:9 frame, with the steaming hot spring, the snowy inn, and the stone lanterns at blue hour in the first sentence, ending with: no people, no text, no logos.

10. **Prompt Seed B - Image-to-Video Animation**: a ready-to-paste video prompt under 120 words that starts from the hero still as the first frame, describes the steam drifting and curling, snow falling, the steady glow of lanterns and windows, a slow gentle push-in toward the pool and the inn for about 6 seconds, and (if I want it) one clump of snow falling from a pine branch into the pool with a small splash and spreading ripples, and says to keep the pool, rocks, inn, lanterns, trees, and framing exactly as in the image with no new objects.

Rules: no people, bathers, or animals; no text, signs, or logos; no fast camera moves or cuts; and no motion that breaks physics (rocks, lanterns, and the inn never move, steam rises and drifts and does not pour downward, snow falls slowly and evenly, a falling snow clump drops straight down under gravity and makes only a small splash, the light does not flicker or jump). Use short paragraphs and lists.
TravelDesignImage To Video+2
F@f
0
蓝调时分雪山旅店旁冒蒸汽户外温泉
图片
蓝调时分雪山旅店旁冒蒸汽户外温泉

轻雪蓝调时分传统日本山间旅店旁冒蒸汽户外温泉水池,两盏发光石灯笼,低机位水面边缘,宽幅 16:9。

轻雪蓝调时分传统日本山间旅店旁冒蒸汽户外温泉水池,两盏发光石灯笼照明,低机位自水边望去,宽幅风景 16:9。清澈青绿水天然池带淡绿辉占画面中央与下中,前景与两侧暗圆火山岩覆厚新雪帽框住。厚白蒸汽自水面升起,左与池中最浓,上飘柔化后方树木。左岩上小积雪石灯笼暖窗发光;右侧近旅馆处较大石灯笼宽雪帽,亦暖琥珀发光。右侧两层木旅馆雪顶与暖亮格窗。负雪松与暗山坡,深蓝暮空。写实旅行摄影,平静冬意。无人、无文字、无标志。
RealismTravelWorkflow+2
F@f
0
视频
黄昏雪山温泉(图生视频动画)

以第 2 步户外温泉图为第一帧,动画化为平静 6 秒镜头:缓慢推进池与旅馆,厚白蒸汽漂移,大软雪花持续飘落,灯笼与窗稳定发光。

以第 2 步图像「蓝调时分雪山旅店旁冒蒸汽户外温泉」为精确第一帧,动画化为平静 6 秒电影感镜头。保持青绿温泉水池、周围暗积雪岩、左侧小石灯笼、右侧较大石灯笼、带雪顶与暖亮窗的两层木旅馆、负雪松、暗山坡、深蓝暮色天空与构图与原图完全一致。相机向水池与旅馆缓慢轻柔推进。厚白蒸汽在水面缓慢卷曲升起飘向树。大软雪花持续均匀飘落全景。灯笼与旅馆窗稳定发光不闪烁。自然真实运动,无剪辑、无新物体、无人、无文字。柔风与远处水滴声。
Video GenerationCinematicTravel+2
F@f
0
通勤成本对比计算器
技能

对比自驾/公交/拼车等真实通勤成本。

---
name: commute-cost-comparison-calculator
description: 对比自驾/公交/拼车等真实通勤成本。
---

# 通勤成本对比计算器

你是「通勤成本对比计算器」技能助手。上方 description 为中文任务说明。下方保留原技能完整正文(含脚本与代码);执行时遵循中文说明与原文技术细节。

# Commute Cost Comparison Calculator

You help someone decide how to get to work by putting every option on the same footing: what it costs in cash, how much of their life it takes, and what that time is worth to them. You count what really changes with the choice (fuel, parking, fares, the bike), keep costs they pay anyway out unless they would actually sell the car, and show the trade-off as a clear number: "driving saves 32 lost hours a year and costs $3,697 more, so it pays only if your hour is worth more than $115".

## Files in this skill

- `scripts/commute_cost.py` - reads a scenario JSON (distance, commute days, value of time, options) and prints a comparison table, headline, break-even values of time and per-option notes; supports metric (km, L/100 km) and imperial (mi, mpg), EVs, transit passes with daily caps and employer subsidies, day-share mixes and multi-leg trips (Python 3.8+ standard library only)
- `references/cost-components.md` - every option field, what to include for each mode, where to find the numbers, and typical ranges
- `references/time-value-and-assumptions.md` - how to choose a value of time, usable time on transit, what the totals leave out (health, safety, weather, flexibility) and how to run sensitivity checks
- `templates/commute-comparison.md` - the decision summary to hand back
- `examples/commute.json` - an 11-mile commute with driving, bus + light rail, an e-bike, an e-bike/transit mix and park-and-ride
- `examples/comparison-output.md` - the real output of the calculator for that scenario

## Workflow

### 1. Collect the facts
One-way distance, commute days per week (hybrid work matters a lot) and weeks worked per year, door-to-door minutes per option (including walking, waiting, parking and transfers: ask for a real trip, not the map estimate), and per mode: car fuel economy, fuel or electricity price, parking and tolls, whether they would keep the car anyway; transit fare, pass price, daily cap, employer subsidy; bike or e-bike price, expected life and upkeep. Ask what an hour of their time is worth, or offer a default (half their hourly pay) and show the sensitivity.

### 2. Run the calculator
```
python3 scripts/commute_cost.py examples/commute.json
python3 scripts/commute_cost.py scenario.json --days-per-week 3
python3 scripts/commute_cost.py scenario.json --value-of-time 0 --json
python3 scripts/commute_cost.py --self-test
```
Exit 0 = comparison printed, 2 = bad input (missing field, mix shares not adding up to 1, unknown mode, negative price). Without code, follow the formulas in `references/cost-components.md`.

### 3. Read the result
- The table is sorted by total (cash + time cost); the headline names the cheapest in cash, the cheapest once time counts and the fastest.
- **Break-even value of time** compares every option with the cheapest in cash: an option that costs more is worth it only if your hour is worth more than the number shown. "Never" means it costs more and does not save usable time.
- Time spent on transit that you can use (reading, email) is discounted with `usable_time_share`; the same field can credit bike time that replaces exercise you would do anyway. Say which you used.
- Check the transit note: on 2-3 commute days a week a monthly pass is often dearer than pay-per-ride; the calculator picks the cheaper and says so.

### 4. Deliver
Fill `templates/commute-comparison.md`: recommendation in one sentence, the table, break-even values, what changes the answer (hybrid days, fuel price, parking, value of time), what is not priced, and a trial suggestion (e.g. two weeks of the mix before buying anything).

## Example result
11 miles each way, 5 days a week, 46 weeks, time valued at $20/hour (`examples/commute.json`). Cheapest in cash: bus + light rail at $49/month after a $50 employer subsidy (the $99 pass beats $105 of single fares). Cheapest once time counts: park and ride ($95 cash, $361/month total) because 80 % of the train leg is usable. Driving downtown is fastest (28 min) but costs $357/month with $12/day parking and pays off only if an hour is worth more than $115. The e-bike costs $67/month all-in and emits nothing, but it does not save usable time at these numbers; it wins if the ride replaces gym time. See `examples/comparison-output.md`.

## Rules

- Count only costs that change with the choice; include car insurance, registration and loan costs only if the person would actually give up the car (`count_fixed`).
- Use real door-to-door times, including walking, waiting, parking and transfers.
- Never present the value of time as a fact: it is the person's own choice; show at least one sensitivity run (e.g. `--value-of-time 0` and their wage).
- Keep health, safety and weather out of the money totals but mention them; do not moralize about cars or bikes.
- State every assumption with its number (fuel price, parking, depreciation per mile) so the person can change it.
- Give currency and units as the person uses them; do not mix miles and kilometres in one scenario.
FILE:references/cost-components.md
# Cost components and option fields

Scenario fields: `currency` (USD, EUR, GBP, TRY, ...), `units` (`metric`: km, L/100 km, price per litre; `imperial`: miles, mpg, price per US gallon), `one_way_distance`, `days_per_week` (default 5), `weeks_per_year` (default 46: holidays, sick days, travel), `value_of_time_per_hour`, `options`.

Every option: `name`, `mode`, `minutes_one_way` (door to door), optional `distance_one_way` (defaults to the scenario distance), optional `usable_time_share` (0..1).

## Car (`mode: car`)
| Field | Meaning | Typical |
|---|---|---|
| `fuel_economy_mpg` / `fuel_l_per_100km` | real-world consumption on this route (city driving is worse than the label) | 25-35 mpg / 6-9 L/100 km |
| `fuel_price` | per gallon / per litre | local pump price |
| `fuel: "diesel"` | uses the diesel CO2 factor | petrol default |
| `kwh_per_100` + `electricity_price` | electric car instead of fuel (kWh per 100 mi or km) | 25-35 kWh/100 mi, 16-22 kWh/100 km |
| `grid_kg_co2_per_kwh` | grid intensity for EV CO2 | 0.4 default; 0.05 hydro/nuclear, 0.7 coal-heavy |
| `maintenance_per_distance` | tyres, servicing, repairs per mile/km | $0.08-0.12/mi, 0.05-0.08/km |
| `depreciation_per_distance` | value lost from extra mileage only (not age) | $0.05-0.15/mi |
| `parking_per_day`, `tolls_per_day` | per commute day | workplace parking can dominate |
| `fixed_per_year` + `count_fixed` | insurance, registration, loan interest, age depreciation; counted only when `count_fixed` is true | true only if commuting is the reason to own the car |

Fuel cost = 2 x distance x days / mpg x price (or x L/100 km / 100 x price). CO2: petrol 2.35 kg/L (8.89 kg per US gallon), diesel 2.68 kg/L (10.18 kg/gal), EV kWh x grid factor.

All-in per-mile rates (tax mileage rates, AAA "Your Driving Costs") include fixed costs; they overstate the marginal cost of a commute when the car is kept anyway.

## Transit (`mode: transit`)
| Field | Meaning |
|---|---|
| `fare_per_ride`, `rides_per_day` (default 2) | single fares; add a ride for each paid transfer that is not free |
| `daily_cap` | fare capping per day, if the system has it |
| `monthly_pass` | price per month; the calculator compares 12 passes with pay-per-ride for the actual days and picks the cheaper |
| `subsidy_per_month` | employer or tax-free benefit, deducted (never below zero) |
| `co2_g_per_passenger_km` | default 80 g; electric rail 20-40, diesel bus 80-120 at average load |
| `usable_time_share` | share of the trip you can read or work (seat, no transfer stress): 0.2-0.8 |

## Bike, e-bike, walk (`mode: bike | ebike | walk`)
| Field | Meaning | Typical |
|---|---|---|
| `purchase_price`, `life_years` | spread over the life (default 5 years); not used for walk | e-bike $1,500-3,500 |
| `maintenance_per_year` | tyres, chain, brake pads, battery fund for e-bikes | $100-300 |
| `gear_per_year` | lights, lock, rain gear, helmet replacement | $50-150 |
| `kwh_per_100`, `electricity_price` | e-bike charging | 1-2 kWh per 100 mi |
| `other_per_month` | bike parking, locker, shower membership | 0-30 |
Ownership costs are counted in full even in a mix: you pay for the bike whether you ride 3 or 5 days.

## Mix (`mode: mix`)
`"shares": {"E-bike": 0.6, "Bus + light rail": 0.4}` splits commute days between other options (shares add up to 1). Each part is costed for its own days, so a transit part on 2 days a week usually goes pay-per-ride. Use it for weather-dependent cycling, hybrid driving, or carpool days.

## Multi-leg (`mode: multi`)
`"legs": [ {car leg}, {transit leg} ]` with `transfer_minutes`. Each leg needs its own `distance_one_way` and `minutes_one_way`; costs and minutes are summed. Use it for park and ride, bike + train, or walk + bus.

## Where to get the numbers
- Fuel economy: the car's trip computer over a week of commuting.
- Parking and fares: the operator's price page; check employer benefits (pre-tax transit, parking cash-out).
- Door-to-door time: time one real trip each way at commute hours; add 5 minutes for parking or waiting variability.
FILE:references/time-value-and-assumptions.md
# Value of time and what the totals leave out

## Choosing a value of time
- There is no correct number: it is what an hour of this person's commute is worth to them.
- Common anchor: about half of hourly take-home pay for personal travel (the US Department of Transportation's travel-time guidance uses 50 % of hourly income for personal local trips; business travel is valued at 100 %).
- Someone on a fixed salary who cannot earn more by commuting less may choose a lower value; a freelancer who bills the saved hours may choose a higher one.
- Always show a sensitivity: `--value-of-time 0` (cash only), the chosen value, and their full hourly wage. If the ranking does not change, the decision is robust.

## Usable time
`usable_time_share` discounts time you can use. Rough guide:
| Situation | Share |
|---|---|
| Driving, cycling in traffic, standing on a crowded bus | 0 |
| Bus with a seat, short hops, transfers | 0.2-0.4 |
| Train with a seat and table, 20+ minutes without changes | 0.6-0.8 |
| Bike or walk that replaces exercise you would otherwise do | 0.3-0.7 (say so explicitly) |

## Not in the money totals (mention them)
- **Health:** regular cycling or walking is associated with better cardiovascular health; some people value it highly, others not at all. Use `usable_time_share` if they want it priced.
- **Safety:** road risk per mile differs by mode and route; a protected bike route changes the picture.
- **Weather and season:** use a mix (e.g. 60 % bike days) rather than assuming every day is a bike day.
- **Reliability:** a 40-minute train with a 10-minute spread may beat a 30-minute drive that ranges from 25 to 60; ask about late penalties (childcare pick-up).
- **Flexibility and option value:** having a car for errands on the way, or for emergencies; car share or taxi credits can replace it for rare days (add them as `other_per_month`).
- **Stress and comfort:** do not price it, but ask.

## Sensitivity runs worth doing
- Hybrid work: `--days-per-week 3` often flips pass vs pay-per-ride and makes owning a second car look worse.
- Parking: a change in parking price (or employer parking cash-out) is often the biggest single lever.
- Fuel or electricity price +-25 %.
- Selling a second car: rerun with `count_fixed: true` on the car to see the full saving.

## Sanity checks before presenting
- Cash per trip for driving should be at least fuel + parking per trip.
- Transit cash per month should not exceed the pass price unless the pass does not cover the route.
- Hours per month = minutes one way x 2 x days per month / 60.
- If the "winner" depends on a value of time far from what the person said, say so plainly.
FILE:templates/commute-comparison.md
# Commute options: <home> to <work>

**Trip:** <11 mi each way, 5 days/week, 46 weeks/year>   **Value of time used:** <$20/hour (about half of hourly pay)>
**Recommendation:** <one sentence, e.g. "Park and ride: $95/month cash, 40 min door to door, most of the train ride usable for email">

## Comparison (per month)
| Option | Cash | Door to door | Hours | Time cost | Total | CO2 kg/year |
|---|---|---|---|---|---|---|
| <Park and ride> | <$95> | <40 min> | <25.6> | <$266> | <$361> | <999> |
| <Bus + light rail> | <$49> | <46 min> | <29.4> | <$411> | <$460> | <651> |

## When a dearer option pays off
| Option | Extra per year | Lost hours saved | Worth it above |
|---|---|---|---|
| <Drive downtown> | <$3,697> | <32> | <$115/hour> |

## What would change the answer
- <Hybrid work (3 days): transit pass no longer pays; pay per ride ...>
- <Parking cash-out from the employer would make driving $230/month dearer still>
- <If the e-bike ride replaces the gym, the e-bike moves to ...>

## Not priced
- <health, safety on the route, weather days, reliability, needing the car for school pick-up>

## Assumptions
- <fuel $3.40/gal at 28 mpg; maintenance $0.10/mi; mileage depreciation $0.08/mi; parking $12/day>
- <transit pass $99, employer subsidy $50/month, 30 % of the bus/rail time usable>
- <car kept for weekends, so insurance and registration are not counted>

## Next step
<Try the mix for two weeks before buying the e-bike; time each trip door to door.>
FILE:examples/commute.json
{
  "currency": "USD",
  "units": "imperial",
  "one_way_distance": 11,
  "days_per_week": 5,
  "weeks_per_year": 46,
  "value_of_time_per_hour": 20,
  "options": [
    {
      "name": "Drive downtown",
      "mode": "car",
      "minutes_one_way": 28,
      "fuel_economy_mpg": 28,
      "fuel_price": 3.4,
      "maintenance_per_distance": 0.1,
      "depreciation_per_distance": 0.08,
      "parking_per_day": 12,
      "tolls_per_day": 0,
      "fixed_per_year": 1900,
      "count_fixed": false
    },
    {
      "name": "Bus + light rail",
      "mode": "transit",
      "minutes_one_way": 46,
      "fare_per_ride": 2.75,
      "monthly_pass": 99,
      "subsidy_per_month": 50,
      "usable_time_share": 0.3
    },
    {
      "name": "E-bike",
      "mode": "ebike",
      "minutes_one_way": 42,
      "purchase_price": 2200,
      "life_years": 5,
      "maintenance_per_year": 250,
      "gear_per_year": 100,
      "kwh_per_100": 1.5,
      "electricity_price": 0.17
    },
    {
      "name": "E-bike 3 days, transit 2 days",
      "mode": "mix",
      "shares": {"E-bike": 0.6, "Bus + light rail": 0.4}
    },
    {
      "name": "Park and ride",
      "mode": "multi",
      "transfer_minutes": 6,
      "legs": [
        {"mode": "car", "distance_one_way": 4, "minutes_one_way": 10, "fuel_economy_mpg": 28, "fuel_price": 3.4,
         "maintenance_per_distance": 0.1, "depreciation_per_distance": 0.08, "parking_per_day": 0},
        {"mode": "transit", "distance_one_way": 7, "minutes_one_way": 24, "fare_per_ride": 2.75, "monthly_pass": 99,
         "subsidy_per_month": 50, "usable_time_share": 0.8}
      ]
    }
  ]
}
FILE:examples/comparison-output.md
# Commute cost comparison

One way: 11 mi   Commute days: 19.2/month (230/year)   Value of time: $20.00/hour   Currency: USD

| Option | Cash / month | Cash / year | Per trip | Door to door (one way) | Hours / month | Time cost / month | Total / month | CO2 kg / year |
|---|---|---|---|---|---|---|---|---|
| Park and ride | $95.22 | $1,143 | $2.48 | 40 min | 25.6 | $266 | **$361** | 999 |
| Bus + light rail | $49.00 | $588 | $1.28 | 46 min | 29.4 | $411 | **$460** | 651 |
| E-bike 3 days, transit 2 days | $66.48 | $798 | $1.73 | 44 min | 27.9 | $487 | **$553** | 261 |
| E-bike | $66.91 | $803 | $1.75 | 42 min | 26.8 | $537 | **$604** | 0 |
| Drive downtown | $357 | $4,285 | $9.32 | 28 min | 17.9 | $358 | **$715** | 1,607 |

Sorted by total (cash + time cost).

## Headline

- Cheapest in cash: **Bus + light rail** ($49.00/month).
- Cheapest once time counts: **Park and ride** ($361/month at $20.00/hour).
- Fastest: **Drive downtown** (28 min one way, 17.9 h/month).

## Break-even value of time (vs Bus + light rail, the cheapest in cash)

| Option | Extra cash / year | Lost hours saved / year | Worth it if your hour is worth more than |
|---|---|---|---|
| Drive downtown | $3,697 | 32 | $115/hour |
| E-bike | $215 | none (75 more lost) | never on cost: it does not save usable time |
| E-bike 3 days, transit 2 days | $210 | none (45 more lost) | never on cost: it does not save usable time |
| Park and ride | $555 | 87 | $6.35/hour |

## Notes per option

- **Drive downtown** (car): per month: fuel/energy $51.20, maintenance + depreciation $75.90, parking + tolls $230; fixed costs (insurance, registration) not counted: you keep the car anyway
- **Bus + light rail** (transit): per month: fares $49.00; monthly pass (cheaper than $105/month pay per ride at 19.2 days/month); employer subsidy $50.00/month deducted
- **E-bike** (ebike): per month: bike + upkeep $65.83, fuel/energy $1.08; ownership costs counted in full (they do not shrink on days you do not ride)
- **E-bike 3 days, transit 2 days** (mix): E-bike 60 % of days (11.5/month): ownership costs counted in full (they do not shrink on days you do not ride); Bus + light rail 40 % of days (7.7/month): pay per ride (pass would cost $99.00/month at 7.7 days/month)
- **Park and ride** (multi): leg 2 (transit): monthly pass (cheaper than $105/month pay per ride at 19.2 days/month); leg 2 (transit): employer subsidy $50.00/month deducted

Time cost = hours x value of time x (1 - share of the trip you can use for reading or work). Not included: health benefits, accident risk, weather days and the option value of having a car; see references/time-value-and-assumptions.md.
FILE:scripts/commute_cost.py
#!/usr/bin/env python3
"""Commute cost comparison calculator (Python 3.8+ standard library only).

Compares commute options - car (fuel or electric), transit, bike, e-bike, walking,
day-share mixes ("bike 3 days, train 2 days") and same-day multi-leg trips ("drive to
the station, then train") - per month and per year: cash cost, time spent, time cost at
your own hourly value, total, cost per trip and CO2. For transit it picks the cheaper of
pay-per-ride (with a daily cap) and a monthly pass for the days you actually ride; for
cars it separates running costs from fixed costs you would keep paying anyway. It also
prints break-even values of time: how much an hour must be worth to you before a faster,
dearer option pays off.

Usage:
  python3 commute_cost.py scenario.json [--value-of-time 25] [--days-per-week 3] [--json]
  python3 commute_cost.py --self-test
scenario.json: {"currency": "USD", "units": "imperial"|"metric", "one_way_distance": 11,
  "days_per_week": 5, "weeks_per_year": 46, "value_of_time_per_hour": 20,
  "options": [{"name": "...", "mode": "car|transit|bike|ebike|walk|mix|multi", ...}]}
See references/cost-components.md for every option field.
Exit codes: 0 = comparison printed, 2 = bad input.
"""
import argparse, json, sys

PETROL_CO2 = {"metric": 2.35, "imperial": 8.89}   # kg CO2 per litre / per US gallon burned (diesel: 2.68 / 10.18)
DIESEL_CO2 = {"metric": 2.68, "imperial": 10.18}
KM_PER_UNIT = {"metric": 1.0, "imperial": 1.609344}
LABELS = {"energy": "fuel/energy", "running": "maintenance + depreciation", "parking_tolls": "parking + tolls",
          "fixed": "fixed costs", "fares": "fares", "ownership": "bike + upkeep", "other": "other"}
MODES = {"car", "transit", "bike", "ebike", "walk", "mix", "multi"}
SYMBOLS = {"USD": "$", "EUR": "€", "GBP": "£", "TRY": "₺", "CAD": "C$", "AUD": "A$", "JPY": "¥", "INR": "₹"}


class InputError(Exception):
    pass


def num(o, key, default=None, lo=0.0, hi=None, who=""):
    v = o.get(key, default)
    if v is None:
        raise InputError(f"{who}: '{key}' is required")
    if isinstance(v, bool) or not isinstance(v, (int, float)):
        raise InputError(f"{who}: '{key}' must be a number")
    if v < lo or (hi is not None and v > hi):
        raise InputError(f"{who}: '{key}' = {v} is out of range" + (f" ({lo}..{hi})" if hi is not None else f" (>= {lo})"))
    return float(v)


def car_cost(o, sc, days, who):
    """Annual cost dict for a car over `days` commute days."""
    units = sc["units"]
    d = num(o, "distance_one_way", sc["distance"], who=who) * 2 * days      # user units per year
    if "kwh_per_100" in o:
        kwh = d / 100 * num(o, "kwh_per_100", who=who)
        energy = kwh * num(o, "electricity_price", who=who)
        co2 = kwh * num(o, "grid_kg_co2_per_kwh", 0.4, who=who)
    else:
        if units == "imperial":
            vol = d / num(o, "fuel_economy_mpg", lo=1, who=who)
        else:
            vol = d / 100 * num(o, "fuel_l_per_100km", lo=0.5, who=who)
        energy = vol * num(o, "fuel_price", who=who)
        co2 = vol * (DIESEL_CO2 if o.get("fuel") == "diesel" else PETROL_CO2)[units]
    running = d * (num(o, "maintenance_per_distance", 0, who=who) + num(o, "depreciation_per_distance", 0, who=who))
    daily = days * (num(o, "parking_per_day", 0, who=who) + num(o, "tolls_per_day", 0, who=who))
    fixed = num(o, "fixed_per_year", 0, who=who) if o.get("count_fixed", False) else 0.0
    return {"energy": energy, "running": running, "parking_tolls": daily, "fixed": fixed,
            "co2": co2, "notes": ([] if o.get("count_fixed", False) or not o.get("fixed_per_year") else
                                  ["fixed costs (insurance, registration) not counted: you keep the car anyway"])}


def transit_cost(o, sc, days, who):
    rides = num(o, "rides_per_day", 2, lo=1, who=who)
    fare = num(o, "fare_per_ride", who=who)
    cap = o.get("daily_cap")
    per_day = fare * rides if cap is None else min(fare * rides, num(o, "daily_cap", who=who))
    payg_year = per_day * days
    best, note = payg_year, "pay per ride"
    if o.get("monthly_pass") is not None:
        pass_year = num(o, "monthly_pass", who=who) * 12
        months_days = days / 12
        if pass_year < payg_year:
            best, note = pass_year, f"monthly pass (cheaper than {fmt_plain(payg_year / 12, sc['sym'])}/month pay per ride at {months_days:.1f} days/month)"
        else:
            note = f"pay per ride (pass would cost {fmt_plain(pass_year / 12, sc['sym'])}/month at {months_days:.1f} days/month)"
    subsidy = num(o, "subsidy_per_month", 0, who=who) * 12
    best = max(0.0, best - subsidy)
    km = num(o, "distance_one_way", sc["distance"], who=who) * 2 * days * KM_PER_UNIT[sc["units"]]
    co2 = km * num(o, "co2_g_per_passenger_km", 80, who=who) / 1000
    return {"fares": best, "co2": co2, "notes": [note] + ([f"employer subsidy {fmt_plain(subsidy / 12, sc['sym'])}/month deducted"] if subsidy else [])}


def bike_cost(o, sc, days, who, mode):
    d = num(o, "distance_one_way", sc["distance"], who=who) * 2 * days
    own = 0.0
    if mode != "walk":
        life = num(o, "life_years", 5, lo=0.5, who=who)
        own = num(o, "purchase_price", 0, who=who) / life
    own += num(o, "maintenance_per_year", 0, who=who) + num(o, "gear_per_year", 0, who=who)
    energy = 0.0
    if mode == "ebike":
        kwh = d / 100 * num(o, "kwh_per_100", 1.2, who=who)
        energy = kwh * num(o, "electricity_price", 0, who=who)
    extra = num(o, "other_per_month", 0, who=who) * 12
    return {"ownership": own, "energy": energy, "other": extra, "co2": 0.0,
            "notes": ["ownership costs counted in full (they do not shrink on days you do not ride)"] if own else []}


def option_cost(o, sc, days, who):
    mode = o.get("mode")
    if mode == "car":
        c = car_cost(o, sc, days, who)
    elif mode == "transit":
        c = transit_cost(o, sc, days, who)
    elif mode in ("bike", "ebike", "walk"):
        c = bike_cost(o, sc, days, who, mode)
    else:
        raise InputError(f"{who}: mode '{mode}' is not allowed here (use car, transit, bike, ebike, walk)")
    minutes = num(o, "minutes_one_way", who=who, lo=1)
    cash = sum(v for k, v in c.items() if k not in ("co2", "notes"))
    return {"cash": cash, "minutes": minutes, "co2": c["co2"], "notes": c["notes"],
            "usable": num(o, "usable_time_share", 0, hi=1, who=who),
            "breakdown": {k: v for k, v in c.items() if k not in ("co2", "notes")}}


def evaluate(sc_raw, value_override=None, dpw_override=None):
    if not isinstance(sc_raw, dict) or not isinstance(sc_raw.get("options"), list) or not sc_raw["options"]:
        raise InputError("scenario needs an 'options' list")
    units = sc_raw.get("units", "metric")
    if units not in KM_PER_UNIT:
        raise InputError("units must be 'metric' or 'imperial'")
    cur = sc_raw.get("currency", "USD")
    sc = {"units": units, "distance": num(sc_raw, "one_way_distance", who="scenario", lo=0.1),
          "sym": SYMBOLS.get(cur, f"{cur} ")}
    dpw = dpw_override if dpw_override is not None else num(sc_raw, "days_per_week", 5, lo=0.5, hi=7, who="scenario")
    wpy = num(sc_raw, "weeks_per_year", 46, lo=1, hi=52, who="scenario")
    days = dpw * wpy
    vot = value_override if value_override is not None else num(sc_raw, "value_of_time_per_hour", 0, who="scenario")
    by_name = {}
    for i, o in enumerate(sc_raw["options"]):
        if not isinstance(o, dict) or not o.get("name") or o.get("mode") not in MODES:
            raise InputError(f"option {i + 1}: needs a 'name' and a 'mode' ({', '.join(sorted(MODES))})")
        if o["name"] in by_name:
            raise InputError(f"option name '{o['name']}' is used twice")
        by_name[o["name"]] = o
    results = []
    for o in sc_raw["options"]:
        who = o["name"]
        if o["mode"] == "mix":
            shares = o.get("shares")
            if not isinstance(shares, dict) or not shares:
                raise InputError(f"{who}: mix needs 'shares', e.g. {{\"Bike\": 0.6, \"Transit\": 0.4}}")
            tot = sum(shares.values())
            if abs(tot - 1) > 0.001:
                raise InputError(f"{who}: shares add up to {tot:g}, not 1")
            cash = co2 = hours = eff = 0.0
            notes, parts = [], []
            for nm, sh in shares.items():
                if nm not in by_name or by_name[nm]["mode"] in ("mix",):
                    raise InputError(f"{who}: share '{nm}' must name another non-mix option")
                sub = by_name[nm]
                part = multi_cost(sub, sc, days * sh, nm) if sub["mode"] == "multi" else option_cost(sub, sc, days * sh, nm)
                cash += part["cash"]; co2 += part["co2"]
                h = part["minutes"] * 2 * days * sh / 60
                hours += h; eff += h * (1 - part["usable"])
                parts.append(f"{nm} {sh * 100:.0f} % of days ({days * sh / 12:.1f}/month): {part['notes'][0] if part['notes'] else 'as above'}")
            minutes = hours * 60 / (2 * days)
            results.append(row(who, "mix", cash, minutes, hours, eff, vot, co2, days, parts))
            continue
        part = multi_cost(o, sc, days, who) if o["mode"] == "multi" else option_cost(o, sc, days, who)
        hours = part["minutes"] * 2 * days / 60
        results.append(row(who, o["mode"], part["cash"], part["minutes"], hours, hours * (1 - part["usable"]), vot,
                           part["co2"], days, part["notes"], part.get("breakdown")))
    return {"currency": sc_raw.get("currency", "USD"), "units": units, "one_way_distance": sc["distance"],
            "days_per_year": days, "days_per_month": days / 12, "value_of_time_per_hour": vot,
            "options": results, "break_even": break_even(results)}


def multi_cost(o, sc, days, who):
    legs = o.get("legs")
    if not isinstance(legs, list) or len(legs) < 2:
        raise InputError(f"{who}: multi needs at least two 'legs'")
    cash = co2 = minutes = 0.0
    notes, usable_min = [], 0.0
    for j, leg in enumerate(legs, 1):
        if leg.get("mode") in ("mix", "multi"):
            raise InputError(f"{who} leg {j}: legs cannot be mix or multi")
        if "distance_one_way" not in leg:
            raise InputError(f"{who} leg {j}: each leg needs its own 'distance_one_way'")
        p = option_cost(leg, sc, days, f"{who} leg {j}")
        cash += p["cash"]; co2 += p["co2"]; minutes += p["minutes"]
        usable_min += p["minutes"] * p["usable"]
        notes += [f"leg {j} ({leg['mode']}): {n}" for n in p["notes"]]
    minutes += num(o, "transfer_minutes", 0, who=who)
    return {"cash": cash, "co2": co2, "minutes": minutes, "notes": notes or ["legs summed"],
            "usable": usable_min / minutes if minutes else 0.0}


def row(name, mode, cash, minutes, hours, eff_hours, vot, co2, days, notes, breakdown=None):
    tcost = eff_hours * vot
    return {"name": name, "mode": mode, "cash_year": cash, "cash_month": cash / 12, "minutes_one_way": minutes,
            "hours_month": hours / 12, "lost_hours_month": eff_hours / 12, "time_cost_month": tcost / 12, "total_month": (cash + tcost) / 12,
            "total_year": cash + tcost, "cost_per_trip": cash / (2 * days), "co2_kg_year": co2,
            "notes": notes, "breakdown": breakdown or {}}


def break_even(results):
    """For each option, the hourly value of time at which it ties with the cheapest-cash option.
    Uses lost hours (time you cannot use for reading or work), the same basis as the time cost."""
    base = min(results, key=lambda r: r["cash_year"])
    out = []
    for r in results:
        if r is base:
            continue
        extra = r["cash_year"] - base["cash_year"]
        hours_saved = (base["lost_hours_month"] - r["lost_hours_month"]) * 12
        if hours_saved > 0.01:
            out.append({"option": r["name"], "vs": base["name"], "extra_cash_year": extra,
                        "hours_saved_year": hours_saved, "break_even_per_hour": extra / hours_saved})
        else:
            out.append({"option": r["name"], "vs": base["name"], "extra_cash_year": extra,
                        "hours_saved_year": hours_saved, "break_even_per_hour": None})
    return out


def fmt_plain(v, sym="$"):
    return f"{sym}{v:,.0f}" if abs(v) >= 100 else f"{sym}{v:,.2f}"


def render(res):
    sym = SYMBOLS.get(res["currency"], res["currency"] + " ")
    f = lambda v: fmt_plain(v, sym)
    du = "mi" if res["units"] == "imperial" else "km"
    out = [f"# Commute cost comparison", "",
           f"One way: {res['one_way_distance']:g} {du}   Commute days: {res['days_per_month']:.1f}/month ({res['days_per_year']:.0f}/year)   "
           f"Value of time: {f(res['value_of_time_per_hour'])}/hour   Currency: {res['currency']}", "",
           "| Option | Cash / month | Cash / year | Per trip | Door to door (one way) | Hours / month | Time cost / month | Total / month | CO2 kg / year |",
           "|---|---|---|---|---|---|---|---|---|"]
    rs = res["options"]
    for r in sorted(rs, key=lambda r: r["total_month"]):
        out.append(f"| {r['name']} | {f(r['cash_month'])} | {f(r['cash_year'])} | {f(r['cost_per_trip'])} | {r['minutes_one_way']:.0f} min | "
                   f"{r['hours_month']:.1f} | {f(r['time_cost_month'])} | **{f(r['total_month'])}** | {r['co2_kg_year']:,.0f} |")
    cheapest = min(rs, key=lambda r: r["cash_year"])
    best_total = min(rs, key=lambda r: r["total_year"])
    fastest = min(rs, key=lambda r: r["minutes_one_way"])
    out += ["", "Sorted by total (cash + time cost).", "",
            "## Headline", "",
            f"- Cheapest in cash: **{cheapest['name']}** ({f(cheapest['cash_month'])}/month).",
            f"- Cheapest once time counts: **{best_total['name']}** ({f(best_total['total_month'])}/month at {f(res['value_of_time_per_hour'])}/hour).",
            f"- Fastest: **{fastest['name']}** ({fastest['minutes_one_way']:.0f} min one way, {fastest['hours_month']:.1f} h/month)."]
    out += ["", f"## Break-even value of time (vs {cheapest['name']}, the cheapest in cash)", "",
            "| Option | Extra cash / year | Lost hours saved / year | Worth it if your hour is worth more than |", "|---|---|---|---|"]
    for b in res["break_even"]:
        if b["break_even_per_hour"] is None:
            verdict = "never on cost: it does not save usable time" if b["extra_cash_year"] > 0 else "always: same cash, no more lost time"
            hs = f"{b['hours_saved_year']:.0f}" if b["hours_saved_year"] >= 0 else f"none ({-b['hours_saved_year']:.0f} more lost)"
            out.append(f"| {b['option']} | {f(b['extra_cash_year'])} | {hs} | {verdict} |")
        else:
            out.append(f"| {b['option']} | {f(b['extra_cash_year'])} | {b['hours_saved_year']:.0f} | {f(b['break_even_per_hour'])}/hour |")
    out += ["", "## Notes per option", ""]
    for r in rs:
        bd = ", ".join(f"{LABELS.get(k, k)} {f(v / 12)}" for k, v in r["breakdown"].items() if v) if r["breakdown"] else ""
        out.append(f"- **{r['name']}** ({r['mode']}): " + "; ".join(([f"per month: {bd}"] if bd else []) + r["notes"]))
    out += ["", "Time cost = hours x value of time x (1 - share of the trip you can use for reading or work). "
            "Not included: health benefits, accident risk, weather days and the option value of having a car; "
            "see references/time-value-and-assumptions.md."]
    return "\n".join(out) + "\n"


def self_test():
    base = {"currency": "USD", "units": "metric", "one_way_distance": 10, "days_per_week": 5, "weeks_per_year": 48,
            "value_of_time_per_hour": 20}
    car = {"name": "Car", "mode": "car", "minutes_one_way": 20, "fuel_l_per_100km": 8, "fuel_price": 1.5,
           "maintenance_per_distance": 0.05, "parking_per_day": 5}
    tr = {"name": "Transit", "mode": "transit", "minutes_one_way": 40, "fare_per_ride": 2.5, "monthly_pass": 80}
    bike = {"name": "Bike", "mode": "bike", "minutes_one_way": 35, "purchase_price": 1000, "life_years": 5, "maintenance_per_year": 100}
    def ev(*opts, **kw):
        sc = dict(base, options=list(opts)); sc.update(kw.pop("sc", {}))
        return evaluate(sc, **kw)
    def get(res, n):
        return next(r for r in res["options"] if r["name"] == n)
    cases = []
    def case(name, fn):
        try:
            ok, err = bool(fn()), ""
        except Exception as e:  # noqa
            ok, err = False, repr(e)
        cases.append((name, ok, err))
    # car: 240 days * 20 km = 4800 km; fuel 384 L * 1.5 = 576; maint 240; parking 1200 -> 2016/year
    case("car annual cash (fuel + maintenance + parking)", lambda: abs(get(ev(car), "Car")["cash_year"] - 2016) < 0.01)
    case("car CO2 petrol 2.35 kg/L", lambda: abs(get(ev(car), "Car")["co2_kg_year"] - 384 * 2.35) < 0.01)
    case("diesel CO2 factor", lambda: abs(get(ev(dict(car, fuel="diesel")), "Car")["co2_kg_year"] - 384 * 2.68) < 0.01)
    case("fixed costs excluded unless count_fixed", lambda: get(ev(dict(car, fixed_per_year=1500)), "Car")["cash_year"] == get(ev(car), "Car")["cash_year"])
    case("fixed costs included with count_fixed", lambda: abs(get(ev(dict(car, fixed_per_year=1500, count_fixed=True)), "Car")["cash_year"] - 3516) < 0.01)
    case("EV uses kWh and electricity price", lambda: abs(get(ev(dict(car, kwh_per_100=18, electricity_price=0.3, fuel_l_per_100km=None)), "Car")["breakdown"]["energy"] - 4800 / 100 * 18 * 0.3) < 0.01)
    case("imperial mpg and $/gal", lambda: abs(get(ev({"name": "Car", "mode": "car", "minutes_one_way": 20, "fuel_economy_mpg": 30, "fuel_price": 3.6}, sc={"units": "imperial"}), "Car")["cash_year"] - 4800 / 30 * 3.6) < 0.01)
    case("imperial CO2 8.89 kg/gal", lambda: abs(get(ev({"name": "Car", "mode": "car", "minutes_one_way": 20, "fuel_economy_mpg": 30, "fuel_price": 3.6}, sc={"units": "imperial"}), "Car")["co2_kg_year"] - 160 * 8.89) < 0.01)
    # transit: per ride 5/day*240 = 1200 vs pass 960 -> pass
    case("transit picks monthly pass when cheaper", lambda: abs(get(ev(tr), "Transit")["cash_year"] - 960) < 0.01)
    case("transit picks pay-per-ride at 2 days/week", lambda: abs(get(ev(tr, dpw_override=2), "Transit")["cash_year"] - 5 * 96) < 0.01)
    case("daily cap applied", lambda: abs(get(ev(dict(tr, monthly_pass=None, daily_cap=4)), "Transit")["cash_year"] - 4 * 240) < 0.01)
    case("employer subsidy deducted, never below 0", lambda: get(ev(dict(tr, subsidy_per_month=100)), "Transit")["cash_year"] == 0)
    case("bike ownership spread over life", lambda: abs(get(ev(bike), "Bike")["cash_year"] - 300) < 0.01)
    case("ebike electricity", lambda: abs(get(ev(dict(bike, name="E", mode="ebike", kwh_per_100=1.5, electricity_price=0.3)), "E")["breakdown"]["energy"] - 4800 / 100 * 1.5 * 0.3) < 0.01)
    case("walk has no purchase cost", lambda: get(ev({"name": "Walk", "mode": "walk", "minutes_one_way": 120, "purchase_price": 999}), "Walk")["cash_year"] == 0)
    case("time cost = hours x value", lambda: abs(get(ev(car), "Car")["time_cost_month"] - 20 * 2 * 240 / 60 * 20 / 12) < 0.01)
    case("usable time share discounts transit time", lambda: abs(get(ev(dict(tr, usable_time_share=0.5)), "Transit")["time_cost_month"] - 40 * 2 * 240 / 60 * 20 * 0.5 / 12) < 0.01)
    case("value-of-time override", lambda: get(ev(car, value_override=0), "Car")["time_cost_month"] == 0)
    case("per trip = annual cash / trips", lambda: abs(get(ev(car), "Car")["cost_per_trip"] - 2016 / 480) < 0.001)
    # mix: bike 60 % of days, transit 40 % -> transit 96 days: per ride 480 vs pass 960 -> per ride
    mixo = {"name": "Mix", "mode": "mix", "shares": {"Bike": 0.6, "Transit": 0.4}}
    case("mix: bike ownership full + transit pay-per-ride on its days", lambda: abs(get(ev(bike, tr, mixo), "Mix")["cash_year"] - (300 + 480)) < 0.01)
    case("mix: time weighted by days", lambda: abs(get(ev(bike, tr, mixo), "Mix")["minutes_one_way"] - (0.6 * 35 + 0.4 * 40)) < 0.01)
    multi = {"name": "P+R", "mode": "multi", "transfer_minutes": 5, "legs": [
        {"mode": "car", "distance_one_way": 3, "minutes_one_way": 8, "fuel_l_per_100km": 8, "fuel_price": 1.5, "parking_per_day": 2},
        {"mode": "transit", "distance_one_way": 7, "minutes_one_way": 20, "fare_per_ride": 2.5, "monthly_pass": 80}]}
    case("multi: legs summed + transfer", lambda: (lambda r: abs(r["minutes_one_way"] - 33) < 0.01 and abs(r["cash_year"] - (1440 * 0.08 * 1.5 + 480 + 960)) < 0.01)(get(ev(multi), "P+R")))
    case("break-even: car vs cheapest", lambda: (lambda res: (lambda b: abs(b["break_even_per_hour"] - (2016 - 300) / ((35 - 20) * 2 * 240 / 60)) < 0.01)(next(b for b in res["break_even"] if b["option"] == "Car")))(ev(car, bike)))
    case("break-even uses lost hours (usable transit time)", lambda: next(b for b in ev(dict(tr, usable_time_share=0.75, subsidy_per_month=60), bike)["break_even"] if b["option"] == "Bike")["break_even_per_hour"] is None)
    case("break-even: slower and dearer -> None", lambda: next(b for b in ev(car, tr, bike)["break_even"] if b["option"] == "Transit")["break_even_per_hour"] is None)
    case("render has headline and table", lambda: "Cheapest in cash: **Bike**" in render(ev(car, tr, bike)) and "| Car |" in render(ev(car, tr, bike)))
    case("render uses currency symbol", lambda: "€" in render(ev(car, sc={"currency": "EUR"})))
    def bad(*opts, **sc):
        try:
            ev(*opts, sc=sc); return False
        except InputError:
            return True
    case("mix shares must sum to 1", lambda: bad(bike, tr, dict(mixo, shares={"Bike": 0.6, "Transit": 0.6})))
    case("mix share must name an option", lambda: bad(bike, dict(mixo, shares={"Bike": 0.5, "Train": 0.5})))
    case("unknown mode rejected", lambda: bad({"name": "Boat", "mode": "boat", "minutes_one_way": 5}))
    case("duplicate names rejected", lambda: bad(car, car))
    case("missing fuel economy rejected", lambda: bad({"name": "Car", "mode": "car", "minutes_one_way": 20, "fuel_price": 1.5}))
    case("negative price rejected", lambda: bad(dict(car, fuel_price=-1)))
    case("bad units rejected", lambda: bad(car, units="furlongs"))
    case("usable share above 1 rejected", lambda: bad(dict(tr, usable_time_share=1.5)))
    passed = 0
    for name, ok, err in cases:
        passed += ok
        print(("PASS " if ok else "FAIL ") + name + ("" if ok else f"  {err}"))
    print(f"{passed}/{len(cases)} passed")
    return 0 if passed == len(cases) else 1


def main(argv=None):
    ap = argparse.ArgumentParser(description="Compare commute options per month and year, including time value")
    ap.add_argument("scenario", nargs="?")
    ap.add_argument("--value-of-time", type=float, help="override value_of_time_per_hour")
    ap.add_argument("--days-per-week", type=float, help="override days_per_week (e.g. 3 for hybrid work)")
    ap.add_argument("--json", action="store_true")
    ap.add_argument("--self-test", action="store_true")
    a = ap.parse_args(argv)
    if a.self_test:
        return self_test()
    if not a.scenario:
        ap.error("give a scenario JSON file, or --self-test")
    try:
        with open(a.scenario, encoding="utf-8") as fh:
            sc = json.load(fh)
        if a.days_per_week is not None and not 0.5 <= a.days_per_week <= 7:
            raise InputError("--days-per-week must be 0.5..7")
        res = evaluate(sc, a.value_of_time, a.days_per_week)
    except json.JSONDecodeError as e:
        print(f"error: scenario is not valid JSON: {e}", file=sys.stderr)
        return 2
    except (OSError, InputError, UnicodeDecodeError) as e:
        print(f"error: {e}", file=sys.stderr)
        return 2
    print(json.dumps(res, indent=2) if a.json else render(res), end="" if not a.json else "\n")
    return 0


if __name__ == "__main__":
    sys.exit(main())
PythonTravelBudgeting+2
F@f
0
航空行李延误/丢失索赔计划(JSON)
文本

托运行李延误、丢失或损坏时,用严格 JSON 索赔计划:即时行动、需保留的官方报告细节、追踪与配送、航司规则等。

1你是「航空行李延误/丢失索赔计划(JSON)」。请按下列中文说明执行;完整原提示词正文保留于后,供结构、占位符与技术细节对齐。
2
3## 中文任务说明
4托运行李延误、丢失或损坏时,用严格 JSON 索赔计划:即时行动、需保留的官方报告细节、追踪与配送、航司规则等。
5
6以下保留原提示词正文供结构与占位符对齐:
7
8{
9 "role": "Calm, organized traveler-side baggage claim assistant who helps people whose checked luggage is delayed, lost, or damaged on a flight report it correctly, track it, keep reasonable interim expenses documented, and file a complete, well-supported claim with the airline on time, without giving legal advice or inventing airline policies, compensation limits, or deadlines",
10 "task": "Turn my flight, what happened to my bag, what was inside, what I have spent so far, and what the airline has told me into a single strict JSON luggage claim plan: a situation summary, immediate actions at the airport or right after, the official report and reference details to keep, a tracking and follow-up plan, rules and deadlines I must confirm officially, an interim expenses plan, a contents inventory with values and proof, a damaged bag section if relevant, a claim package checklist, a timeline with dates, a polite claim letter, a follow-up message, an escalation path, and mistakes to avoid. Never invent airline rules, compensation amounts, deadlines, or facts I did not give.",
...+184行
TravelChecklistJSON+3
F@f
0
廊桥秋色——场景概念简报
文本

把短想法变成可出图/出动态的英雄镜头简报。

你是「廊桥秋色——场景概念简报」。请按下列中文说明执行;完整原提示词正文保留于后,供结构、占位符与技术细节对齐。

## 中文任务说明
把短想法变成可出图/出动态的英雄镜头简报。

以下保留原提示词正文供结构与占位符对齐:

Act as a cinematic landscape designer who writes image-ready and motion-ready scene briefs. Help me design one hero shot of a red wooden covered bridge over a calm, misty river in peak autumn foliage at sunrise. The shot will first be generated as a still image and then animated into a short video that uses that image as its first frame. Your brief must make the still image rich in elements that can move naturally (low mist drifting over the water, a few leaves falling and floating, soft ripples, light shimmering on the reflection, branches swaying slightly) while keeping the composition calm, balanced, readable, and easy to animate.

My starting idea: a classic red covered bridge over a still river at sunrise, with low mist on the water and blazing autumn trees on both banks

My starting choices:
- Bridge: a long red wooden covered bridge with a dark grey shingled roof, an open entrance with white trim on the left end, resting on stone abutments
- Position: spanning the river slightly right of center, seen from the water level a little downstream
- River: a calm, mirror-still river filling the lower half of the frame, reflecting the bridge and the trees
- Mist: a thin low layer of mist drifting over the water and under the bridge
- Trees: dense orange, red, and yellow maples and birches on both banks, brighter and sunlit on the left, deeper red on the right
- Leaves on the water: many small fallen leaves floating in the foreground
- Time and light: just after sunrise, soft golden light from the left and a clear pale blue sky with warm haze
- Visual style: photoreal landscape and travel photography, natural colors
- Mood words: quiet, crisp, warm, nostalgic, peaceful

First ask me, in one short message, for anything missing: the camera height (just above the water, bank level, or slightly raised), how much of the frame the bridge should take, how thick the mist should be, how many floating leaves I want, whether I want overhanging branches at the top edge to frame the shot, whether the video should include a few leaves falling onto the water and making ripples, and anything to avoid (people, cars, animals, boats, text, signs, logos, or copying a specific real bridge, photograph, or named landmark).

Then produce the brief with these sections:

1. **Scene Overview**: two or three sentences on the place, the moment, the light, and the feeling. Describe an imagined rural covered bridge in a classic countryside spirit, not a copy of a specific real bridge or a famous photograph.

2. **The Covered Bridge**: its length and position in the frame, the red board siding, the shingled roof, the open entrance and its trim, the stone abutments, and how the morning light falls on it.

3. **The River and Reflection**: the water surface, how still it is, how clearly it mirrors the bridge and trees, where it catches warm light, and the floating leaves in the foreground.

4. **Mist**: how thick it is, where it lies (on the water, under the bridge, along the far bank), which way it drifts, and how it should read as soft morning river mist rather than smoke or fog filling the whole frame.

5. **Autumn Trees and Sky**: the colors and density of the foliage on each bank, where the sunlight hits, the edge of the woods against the sky, and the pale blue sky with warm golden haze.

6. **Light and Color**: the direction and warmth of the sunrise light, the cool shadows, the balance of red, orange, and yellow against blue, and how soft the contrast is.

7. **Fixed Visual Details**: a numbered list of 8 to 10 details that must stay identical in the still image and in the video (bridge position and length, roof line, entrance on the left end, stone abutments, both tree lines, river edges, reflection, camera position, framing, overall mist zone).

8. **Motion Map**: a table of every element that can move in the video, with columns: element, type of motion, direction, speed (very slow, slow, medium), and how strongly it should move. Include the mist, a few leaves falling and landing on the water, the floating leaves, the ripples they make, the shimmer on the reflection, a slight sway of branches, and the camera move. Keep total motion subtle and physically plausible, with one gentle air direction.

9. **Prompt Seed A - Hero Still Image**: a ready-to-paste image prompt under 170 words for a wide landscape 16:9 frame, with the red covered bridge, the misty river, and the peak autumn trees at sunrise in the first sentence, ending with: no people, no text, no logos.

10. **Prompt Seed B - Image-to-Video Animation**: a ready-to-paste video prompt under 120 words that starts from the hero still as the first frame, describes a slow gentle push-in toward the bridge for about 6 seconds, mist drifting over the water and under the bridge, a few red and orange maple leaves fluttering down and landing on the water with soft spreading ripples, and the reflection shimmering, and says to keep the bridge, banks, trees, river, and framing exactly as in the image with no new objects.

Rules: no people, cars, boats, or animals; no text, signs, or logos; no fast camera moves or cuts; and no motion that breaks physics (the bridge, stone abutments, and banks never move, the water stays calm and flows gently in one direction, leaves fall slowly and float on the surface, ripples spread outward in circles and fade, mist drifts and does not pour or swirl violently, the light does not flicker or jump). Use short paragraphs and lists.
TravelDesignImage To Video+2
F@f
0
盛秋叶色雾河上的红色廊桥
图片
盛秋叶色雾河上的红色廊桥

写实秋日日出场景:长红色木廊桥配深灰瓦顶,左端白镶边入口与石桥台,略偏右横跨静河,水面低雾,两岸炽烈橙红黄树,镜面倒影,前景漂浮落叶,淡蓝天空金霾。秋雾红廊桥工作流第 2 步。

日出盛秋叶色,红色木廊桥横跨平静雾河,略在下游水面平视,宽幅风景 16:9。长桥红竖板墙与深灰瓦顶,略偏右跨河;左端敞开入口白镶边框,两端承于灰石桥台。薄层低白雾漂移水面与桥下。密秋树挤满两岸:左岸被柔金日照亮的亮橙、红、黄枫;右岸更深红橙叶色。静河占画面下半,如玻璃倒映桥与树,前景水面漂许多小橙红落叶。天空清澈淡蓝,树顶近处暖金霾,柔和日出光自左来。写实风景与旅行摄影,自然色彩,安静清晰怀旧氛围。无人、无车、无文字、无标志。
RealismTravelWorkflow+2
F@f
0
视频
秋雾中的红色廊桥(图生视频动画)

以第 2 步廊桥图为第一帧,动画化为平静 6 秒镜头:向红桥缓慢推进,悬垂枫枝自顶缘入画,低雾水面漂移,红橙叶飘落水面激起扩散圆涟漪,倒影在柔和日出光中闪烁。秋雾红廊桥工作流第 3 步。

以第 2 步图像「盛秋叶色雾河上的红色廊桥」为精确第一帧,动画化为平静 6 秒电影感镜头。保持深红灰瓦顶廊桥、左端白镶边入口、石桥台、两岸橙红黄树、镜面静河及其倒影、淡蓝天空金霾与构图与原图完全一致。相机向桥做缓慢稳定推进,桥变大并向画面中心移近,同时近岸几枝悬垂橙红枫枝沿顶缘入画作自然前景框。低雾持续缓慢漂移水面与桥下。数片红橙枫叶在桥前飘落轻落水面,各激起向外扩散并消散的圆涟漪,桥倒影在涟漪中轻柔闪烁。镜头在柔和金色日出光中平静结束。自然真实运动,无剪辑、无人、无动物、无船、无文字。柔和河声、叶间微风与叶触水轻滴声。
Video GenerationCinematicTravel+2
F@f
0
上一个3 / 5下一个