# Compounding Offer Audit: five rules and a five-check script (free sample)

Version 1.0, 2026-10-29. By PaperChaseWebb. For owners of small service businesses who sell more than one thing and want to know which one to fix first. One Markdown file and one Python 3 script, standard library only. The script was tested on its worked example (5 findings, then 0) and on a missing file (exit 2). It was not run inside any agent harness or on a real business.

Most offer audits end in a total out of 100. A total hides the one thing that will hurt: an offer that only the owner can deliver, or a line where one client pays most of the bills. And most are scored from memory, which is how every offer ends up looking fine. This sample scores one offer on six tests, from 0 to 3: recurring, productized, systemized, owned, compounding, and whether growing it would make the owner's week better. Each score needs a fact. Then it names the weakest test and asks for one change. The life test (if this line doubled, would the owner's week get better or worse?) comes from a post by Pascio, credited in the full pack.

## Install in one line

Save this page as FREE-SAMPLE.md, then:

```
python3 -c "import re;t=open('FREE-SAMPLE.md').read();open('offer_mini.py','w').write(re.search(r'~~~python\n(.*?)~~~',t,re.S).group(1))"
python3 offer_mini.py offer-lite.json
```

## Five rules

1. **One owner.** One person says yes to changes. Everyone else, agents included, proposes.
2. **Six scores, no total.** Score recurring, productized, systemized, owned, compounding and life from 0 to 3, or write unknown. Never add them up. An offer is as strong as its weakest test.
3. **A fact for every score.** One sentence someone else could check, and where it came from (invoices, calendar, written steps, analytics, a client's own words). If nobody has the fact, write unknown and name what is missing. A guessed 2 is worse than an honest unknown.
4. **No 3 on say-so.** The owner's memory can support a 0, 1 or 2. A 3 needs a record.
5. **One change, on the weakest test.** Unknown counts as weakest. If life is 1 or less, the change is about the owner's week first, before anything that brings in more work.

Agents score, draft and propose. They never contact a client, change a live price, post or spend. The owner does those.

## The five checks

| Id | Check |
|---|---|
| M1 | the audit names an owner |
| M2 | every offer has all six tests scored 0 to 3 or unknown |
| M3 | every score has evidence of 12 characters or more and a source; every unknown names the missing fact |
| M4 | no 3 rests on the owner's say-so |
| M5 | the next step is on the weakest test (unknown counts as weakest), or on life if life is 1 or less |

## The script

~~~python
#!/usr/bin/env python3
"""offer_mini.py: five checks on a small offer audit (offer-lite.json). Free sample of the Compounding Offer Audit.
Usage: python3 offer_mini.py offer-lite.json
Exit 0 clean, 1 findings, 2 missing or unreadable file. Standard library only. Reads one file, writes nothing."""
import json, sys

SIX = ("recurring", "productized", "systemized", "owned", "compounding", "life")


def rank(s):
    return -1 if s.get("score") == "unknown" else s.get("score")


def check(j):
    F = []
    if not str(j.get("owner", "")).strip():
        F.append("M1 name the one person who says yes to changes")
    for o in j.get("offers") or []:
        oid = o.get("id") or "?"
        sc = o.get("scores") or {}
        miss = [k for k in SIX if not isinstance(sc.get(k), dict) or sc[k].get("score") not in (0, 1, 2, 3, "unknown")]
        if miss:
            F.append(f"M2 offer {oid}: score {', '.join(miss)} as 0, 1, 2, 3 or unknown")
        good = {k: v for k, v in sc.items() if k in SIX and k not in miss}
        for k, v in good.items():
            if v["score"] == "unknown" and not str(v.get("missing", "")).strip():
                F.append(f"M3 offer {oid}, {k}: unknown must name the fact you are missing")
            elif v["score"] != "unknown" and (len(str(v.get("evidence", "")).strip()) < 12 or not str(v.get("source", "")).strip()):
                F.append(f"M3 offer {oid}, {k}: a score needs evidence someone can check and where it came from")
        for k, v in good.items():
            if v["score"] == 3 and v.get("source") == "owner-said":
                F.append(f"M4 offer {oid}, {k}: a 3 needs a record, not the owner's say-so")
        lows = {k: rank(v) for k, v in good.items() if rank(v) <= 1}
        if lows:
            life = good.get("life")
            want = ["life"] if life and rank(life) <= 1 else [k for k, r in lows.items() if r == min(lows.values())]
            step = (o.get("next_step") or {}).get("dimension")
            if step not in want or len(str((o.get("next_step") or {}).get("change", "")).strip()) < 12:
                F.append(f"M5 offer {oid}: the next step should be one change on {' or '.join(want)}" + (" (life is a veto)" if want == ["life"] else " (the weakest)"))
    return F


def main(a):
    if len(a) < 2:
        print(__doc__); return 2
    try:
        j = json.load(open(a[1]))
    except (OSError, ValueError) as e:
        print(f"cannot read {a[1]}: {e}", file=sys.stderr); return 2
    F = check(j)
    for f in F:
        print(f)
    print(f"5 checks, {len(F)} findings")
    return 1 if F else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))
~~~

## Worked example

Marlow Tidy Books is made up, and so are its owner and every fact about it. Its offer-lite.json before:

```json
{
 "owner": "",
 "business": "Marlow Tidy Books, a one-person bookkeeping practice (made up)",
 "offers": [
  {"id": "catch-up", "name": "Year-end catch-up",
   "scores": {
    "recurring": {"score": 0, "evidence": "Every catch-up last year was a single invoice; none turned into monthly work", "source": "invoices"},
    "productized": {"score": 3, "evidence": "Fixed price per year of records", "source": "owner-said"},
    "systemized": {"score": 1, "evidence": "", "source": ""},
    "owned": {"score": "unknown", "missing": "Where the last ten catch-up clients heard about the practice"},
    "compounding": {"score": 1, "evidence": "Nothing from one catch-up is reused in the next", "source": "owner-said"}
   },
   "next_step": {"dimension": "compounding", "change": "Turn the catch-up checklist into a template"}}
 ]
}
```

Run:

```
M1 name the one person who says yes to changes
M2 offer catch-up: score life as 0, 1, 2, 3 or unknown
M3 offer catch-up, systemized: a score needs evidence someone can check and where it came from
M4 offer catch-up, productized: a 3 needs a record, not the owner's say-so
M5 offer catch-up: the next step should be one change on owned (the weakest)
5 checks, 5 findings
```

The fixes: Rosa Marlow is named as the owner. Life is scored from her calendar. The productized 3 gets a record behind it (the scope page sent with every quote). Systemized gets a fact instead of a blank. And the next step moves off compounding: owned is unknown, so it is the weakest test, and the first change is finding out where clients come from. After:

```json
{
 "owner": "Rosa Marlow",
 "business": "Marlow Tidy Books, a one-person bookkeeping practice (made up)",
 "offers": [
  {"id": "catch-up", "name": "Year-end catch-up",
   "scores": {
    "recurring": {"score": 0, "evidence": "Every catch-up last year was a single invoice; none turned into monthly work", "source": "invoices"},
    "productized": {"score": 3, "evidence": "Price list and scope page sent with every quote since March", "source": "record"},
    "systemized": {"score": 1, "evidence": "Rosa does every reconciliation herself; no written steps exist", "source": "calendar"},
    "owned": {"score": "unknown", "missing": "Where the last ten catch-up clients heard about the practice"},
    "compounding": {"score": 1, "evidence": "Nothing from one catch-up is reused in the next", "source": "owner-said"},
    "life": {"score": 2, "evidence": "Catch-ups fit in the quiet months; the calendar shows no weekend work", "source": "calendar"}
   },
   "next_step": {"dimension": "owned", "change": "Add a where-did-you-hear-about-us line to the intake form and fill it in for the last ten clients"}}
 ]
}
```

```
5 checks, 0 findings
```

Recurring is still 0, and that is fine. It is the next change after the fact is in, not this one.

## One prompt to try

Paste into your agent: "Score my offer [name] on six tests from 0 to 3: recurring, productized, systemized, owned, compounding, and whether doubling it would make my week better. Use only my invoices, my calendar and our written procedures. Give one sentence of evidence per test and where it came from. Write unknown where you do not have the fact, and tell me what is missing. Do not give a 3 on anything I only told you. Do not add the scores up. Then name the weakest test and propose one change for it, with how I will know it worked."

## The full pack

The paid Compounding Offer Audit (pp-compounding-offer-audit, $19) grows this into an audit you can run every quarter: a written 0 to 3 scale for each test, a worksheet, every offer in one folder, audits kept as before pictures, life as a veto on growth, an action type for each kind of low score with a success test and a date, a cap on open changes, fifteen checks, a one-page audit, and every change waiting for the owner's yes, tied to its sha256 and typed at their own terminal. If you would rather have it set up with you, book a call from the home page at paperchasewebb.ai.

Made-up example; the business, people and facts are invented.

PaperChaseWebb 💸
