# Problem Mining Loop: five rules and a five-check script (free sample)

Version 1.0, 2026-10-29. By PaperChaseWebb. For founders, consultants and small agencies who want to know what one kind of buyer complains about, in the buyer's own words, before deciding what to build or offer. 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 research from a real community.

A list of pain points is easy to make and hard to trust. Some entries were written by a model and never said by anyone. Some were said by one person, several times. Some are paraphrases that drifted from what the poster meant. These five rules keep a small complaint log honest, and the script checks them in one file.

## Install in one line

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

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

## Five rules

1. **One owner, one buyer type.** A person owns the log and decides what happens next. Each log is about one kind of buyer, so the counts mean something.
2. **Every complaint has a link and a date.** The link goes to the post itself, and the date is when the poster wrote it. No link, no row.
3. **Quote, never paraphrase.** The poster's exact words, 280 characters or fewer. If you cannot quote it, do not record it.
4. **Money is shown, not guessed.** A complaint graded budget (they pay or would pay) or buyer (they also say they decide) carries the words from the quote that show it.
5. **Count people, not posts.** A cluster is only worth building for when enough different posters, at pain or higher, say so. One person posting three times is one poster.

The grades, lowest to highest: gripe (annoyed), pain (it costs them and they have tried something), budget, buyer. Read public pages only, follow each site's robots.txt and terms, and never post, reply or message anyone in a community you are reading.

## The five checks

| Id | Check |
|---|---|
| M1 | the log names an owner and one buyer type |
| M2 | every complaint has an https link and the date it was posted |
| M3 | every quote is verbatim and 280 characters or fewer |
| M4 | every budget or buyer grade carries evidence copied from the quote |
| M5 | a cluster marked build has at least min_posters (default 3) different posters at pain or higher |

Exit 0 clean, 1 findings, 2 missing or unreadable file. The script only reads.

~~~python
#!/usr/bin/env python3
"""mine_mini.py: five checks on a small complaint log (complaints-lite.json). Free sample of the Problem Mining Loop.
Usage: python3 mine_mini.py complaints-lite.json
Exit 0 clean, 1 findings, 2 missing or unreadable file. Standard library only. Reads one file, writes nothing."""
import datetime as dt, json, sys

GRADES = ("gripe", "pain", "budget", "buyer")


def check(j):
    F = []
    if not str(j.get("owner", "")).strip() or not str(j.get("buyer_type", "")).strip():
        F.append("M1 name an owner and one buyer type")
    need = j.get("min_posters") if isinstance(j.get("min_posters"), int) else 3
    cs = j.get("complaints") or []
    for i, c in enumerate(cs, 1):
        cid = c.get("id") or f"#{i}"
        try:
            dt.date.fromisoformat(str(c.get("date", "")))
            dated = True
        except ValueError:
            dated = False
        if not str(c.get("link", "")).startswith("https://") or not dated:
            F.append(f"M2 complaint {cid}: needs an https link and the date it was posted")
        q = str(c.get("quote", ""))
        if c.get("verbatim") is not True or not q or len(q) > 280:
            F.append(f"M3 complaint {cid}: quote the poster's exact words, 280 characters or fewer")
        if c.get("grade") in ("budget", "buyer") and (not c.get("evidence") or c.get("evidence") not in q):
            F.append(f"M4 complaint {cid}: graded {c.get('grade')}, so the evidence must be words from the quote")
    for k in j.get("clusters") or []:
        if k.get("decision") == "build":
            posters = {c.get("poster") for c in cs if c.get("cluster") == k.get("name") and c.get("grade") in GRADES[1:]}
            if len(posters) < need:
                F.append(f"M5 cluster '{k.get('name')}' says build with {len(posters)} different posters at pain or higher; needs {need}")
    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

A made-up consultancy is looking at small landscaping companies. Its log has no owner, one complaint with no link, budget evidence that is a guess rather than words from the post, a paraphrase marked as not verbatim, and a cluster marked build on the strength of one poster who posted three times plus one other. Before (complaints-lite.json):

```json
{
 "owner": "",
 "buyer_type": "owners of small landscaping companies",
 "min_posters": 3,
 "complaints": [
  {"id": "k1", "link": "", "date": "2026-09-19", "poster": "poster-a", "verbatim": true,
   "quote": "I spend every Sunday night going through who hasn't paid and texting them one by one.", "grade": "pain", "evidence": "", "cluster": "invoice chasing"},
  {"id": "k2", "link": "https://answers.example.net/questions/late-payers-301", "date": "2026-08-25", "poster": "poster-b", "verbatim": true,
   "quote": "We pay a part-time bookkeeper $400 a month mostly to chase late invoices and it still slips.", "grade": "budget", "evidence": "they have budget", "cluster": "invoice chasing"},
  {"id": "k3", "link": "https://forum.example.org/t/45-days-late-19", "date": "2026-07-31", "poster": "poster-a", "verbatim": false,
   "quote": "Owner says clients pay late and reminders do not work.", "grade": "pain", "evidence": "", "cluster": "invoice chasing"},
  {"id": "k4", "link": "https://forum.example.org/t/same-three-clients-40", "date": "2026-10-04", "poster": "poster-a", "verbatim": true,
   "quote": "Another week of chasing the same three clients for payment.", "grade": "pain", "evidence": "", "cluster": "invoice chasing"}
 ],
 "clusters": [
  {"name": "invoice chasing", "decision": "build"}
 ]
}
```

Output:

```
M1 name an owner and one buyer type
M2 complaint k1: needs an https link and the date it was posted
M4 complaint k2: graded budget, so the evidence must be words from the quote
M3 complaint k3: quote the poster's exact words, 280 characters or fewer
M5 cluster 'invoice chasing' says build with 2 different posters at pain or higher; needs 3
5 checks, 5 findings
```

The fixes: Noor Castellan is named as the owner, k1 gets the link to its post, k2's evidence becomes "$400 a month" from the quote, and k3 is replaced with the poster's own words, which turn out to come from a third person. After:

```
5 checks, 0 findings
```

## One prompt to try

Paste into your agent: "For owners of [your buyer type], read public posts on [one forum or review site] from the last six months. Before reading, open its robots.txt and terms and tell me if they allow this; if not, stop. List complaints about their work in complaints-lite.json: the exact words, the link, the date posted, a made-up label per poster in place of their name, and a grade of gripe, pain, budget or buyer. Do not paraphrase, and do not post, reply or log in."

## The full pack

The paid Problem Mining Loop pack (pp-problem-mining-loop, $19) grows this into a loop you can run every month: sources checked against robots.txt and terms with a date, salted poster keys in place of handles, solicited answers and your own accounts kept out of the count, clusters with a propose rule that also needs more than one source and someone showing money, a fetcher that only sends GET requests and stops at logins, redirects and rate limits, fifteen checks, a one-page report, and proposals that wait for the owner's yes, tied to the set hash and typed at their own terminal. If you would rather have it run with you, book a call from the home page at paperchasewebb.ai.

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

PaperChaseWebb 💸
