Verified against ChatGPT · 2026-07-20
Turn a plain-English function spec into fully type-hinted Python
A prompt that converts a rough description of what a function should do into PEP 484-typed Python with explicit edge-case handling, a real docstring, and a typed return object — instead of untyped happy-path code that only handles the example in the spec.
The prompt
Ready to copy — highlighted parts are example details you can swap.
You are implementing a single Python function against a strict specification, not sketching a rough first draft. Treat this the way a senior reviewer would: nothing ships without full type hints, a real docstring, and explicit handling for every edge case named below — and for any edge case you notice that isn't named but would break this function in the calling context described. SPEC Given a list of shipment dicts (each with "weight_kg" and "destination_country"), return the total customs duty owed in USD, using a duty-rate table keyed by ISO country code. TARGET ENVIRONMENT Python 3.12+. Dependencies allowed beyond the standard library: stdlib only, no third-party packages. Do not import anything outside that list, and do not assume a package is available just because it is common — if you genuinely need something not listed, say so and explain why the standard library alone can't do the job. CALLING CONTEXT Called once per warehouse batch job, batch size up to 5,000 shipments — must not raise on a single bad record and abort the whole batch. REQUIREMENTS 1. Full type hints on every parameter, the return type, and any local variable whose type isn't immediately obvious from its assignment. No bare Any anywhere — if you truly can't narrow a type further, say so in an inline comment naming why. 2. A docstring in Google or NumPy style: one-line summary, then Args, Returns, and Raises sections listing every exception the function can actually raise, not a generic "may raise an exception." 3. Explicit, named handling for: empty shipment list, a shipment missing "weight_kg", a destination_country not present in the duty-rate table, a negative weight. Each one needs a specific, intentional outcome — a specific return value or a specific raised exception type — never a silent fallthrough to whatever the last line of the function happens to do. 4. If you notice an edge case not in empty shipment list, a shipment missing "weight_kg", a destination_country not present in the duty-rate table, a negative weight that would break this function given the calling context above, name it and handle it anyway. Don't treat the given list as the ceiling of what you're responsible for. 5. No print() calls anywhere for control flow, debugging, or status reporting — use the logging module at an appropriate level if the function needs to report anything during execution. 6. Prefer a typed dataclass or NamedTuple over a bare tuple or dict when the function returns more than one related value, so callers get named fields and type-checker support instead of guessing at positional order. OUTPUT FORMAT 1. The function, fully typed and documented, plus any small supporting type (a dataclass, an Enum, a TypedDict) it needs. 2. A table: each edge case from empty shipment list, a shipment missing "weight_kg", a destination_country not present in the duty-rate table, a negative weight plus anything you added, what triggers it, and exactly what the function does in that case. 3. Two runnable call examples: one on the happy path with a realistic input, and one that deliberately triggers a handled failure, showing what's raised or returned.
Customize
Optional — swap in your own details for the highlighted parts above.
Why this works
Naming the edge cases explicitly, then requiring the model to add any it notices beyond that list, splits the task into two different cognitive jobs instead of one: satisfy a checklist, and separately reason about the calling context enough to catch what the checklist missed. A model asked only to "write this function" defaults to the happy path implied by the spec's example, because that path has the least ambiguity to resolve — an explicit edge-case list removes that ambiguity for the listed cases, and the follow-up instruction removes the excuse for the unlisted ones. Requiring a docstring's Raises section to name every exception the function can actually raise, rather than a generic line, forces the model to trace its own control flow before calling the job finished — if it can't name what's raised where, the implementation isn't actually done, it just looks done. The calling_context variable does something the other fields can't: edge_cases describes what bad data looks like, but calling_context describes what happens to the whole system when this one function gets something unexpected, which is why "must not abort a 5,000-record batch on one bad row" changes the actual design toward catch-and-report-per-item rather than just changing which exception type gets raised. Preferring a typed dataclass or NamedTuple return over a bare tuple is a small requirement with an outsized payoff: a positional tuple return means every call site has to remember field order by convention, and a type checker cannot catch a caller who swaps two same-typed fields, where a named field can be both checked and autocompleted. Finally, banning print() for anything but the logging module matters specifically because a function with print-based status reporting can't be safely reused inside a library or service — printing to stdout is a side effect that pollutes output for every caller, including ones piping the function's actual return value somewhere structured, while a log call at an appropriate level respects whatever logging configuration the caller already has in place.
What you get back
@dataclass(frozen=True) class DutyResult: total_usd: float skipped: int def total_customs_duty(shipments: list[dict[str, float | str]], rate_table: dict[str, float]) -> DutyResult: """Sum customs duty owed across a batch of shipments. Args: shipments: Each dict must have "weight_kg" (float) and "destination_country" (ISO code). rate_table: Maps ISO country code to a per-kg USD duty rate. Returns: DutyResult with the total in USD and a count of skipped (invalid) records. Raises: Nothing — invalid records are counted as skipped, never raised, per the batch calling context. """ total = 0.0 skipped = 0 for s in shipments: weight = s.get("weight_kg") country = s.get("destination_country") if not isinstance(weight, (int, float)) or weight < 0 or country not in rate_table: skipped += 1 continue total += float(weight) * rate_table[str(country)] return DutyResult(total_usd=round(total, 2), skipped=skipped) Edge case notes: empty list returns DutyResult(0.0, 0); missing "weight_kg" or unknown country increments skipped rather than raising, per the batch context; negative weight also counts as skipped.
Verified against
ChatGPT GPT-5.2 · 2026-07-20
Claude Sonnet 4.6 · 2026-07-21
Changelog
- 2026-07-21 — Initial publish, verified against ChatGPT (GPT-5.2) and Claude (Sonnet 4.6).
Need this built into your business?
If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.
EXPLORE CUSTOM SOFTWARE
