mrwilson 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mrwilson-0.2.0/.gitignore +14 -0
- mrwilson-0.2.0/PKG-INFO +104 -0
- mrwilson-0.2.0/README.md +71 -0
- mrwilson-0.2.0/pyproject.toml +67 -0
- mrwilson-0.2.0/src/mrwilson/__init__.py +7 -0
- mrwilson-0.2.0/src/mrwilson/__main__.py +5 -0
- mrwilson-0.2.0/src/mrwilson/audit.py +55 -0
- mrwilson-0.2.0/src/mrwilson/audit_questions.py +320 -0
- mrwilson-0.2.0/src/mrwilson/audit_report.py +255 -0
- mrwilson-0.2.0/src/mrwilson/brand.py +470 -0
- mrwilson-0.2.0/src/mrwilson/claude_client.py +451 -0
- mrwilson-0.2.0/src/mrwilson/cli.py +416 -0
- mrwilson-0.2.0/src/mrwilson/connectors/__init__.py +0 -0
- mrwilson-0.2.0/src/mrwilson/connectors/_keys.py +30 -0
- mrwilson-0.2.0/src/mrwilson/connectors/airquality.py +148 -0
- mrwilson-0.2.0/src/mrwilson/connectors/bea.py +98 -0
- mrwilson-0.2.0/src/mrwilson/connectors/bls.py +297 -0
- mrwilson-0.2.0/src/mrwilson/connectors/cdc_arbonet.py +62 -0
- mrwilson-0.2.0/src/mrwilson/connectors/cdc_wonder.py +70 -0
- mrwilson-0.2.0/src/mrwilson/connectors/census.py +114 -0
- mrwilson-0.2.0/src/mrwilson/connectors/census_cbp.py +93 -0
- mrwilson-0.2.0/src/mrwilson/connectors/cfpb.py +70 -0
- mrwilson-0.2.0/src/mrwilson/connectors/cms_fee.py +77 -0
- mrwilson-0.2.0/src/mrwilson/connectors/cms_npi.py +74 -0
- mrwilson-0.2.0/src/mrwilson/connectors/congress.py +81 -0
- mrwilson-0.2.0/src/mrwilson/connectors/courtlistener.py +78 -0
- mrwilson-0.2.0/src/mrwilson/connectors/drought_monitor.py +81 -0
- mrwilson-0.2.0/src/mrwilson/connectors/eia.py +89 -0
- mrwilson-0.2.0/src/mrwilson/connectors/epa_echo.py +78 -0
- mrwilson-0.2.0/src/mrwilson/connectors/epa_ppls.py +67 -0
- mrwilson-0.2.0/src/mrwilson/connectors/eviction_lab.py +38 -0
- mrwilson-0.2.0/src/mrwilson/connectors/fda.py +103 -0
- mrwilson-0.2.0/src/mrwilson/connectors/fda_cosmetics.py +63 -0
- mrwilson-0.2.0/src/mrwilson/connectors/fda_greenbook.py +68 -0
- mrwilson-0.2.0/src/mrwilson/connectors/fda_shortage.py +71 -0
- mrwilson-0.2.0/src/mrwilson/connectors/federal_register.py +84 -0
- mrwilson-0.2.0/src/mrwilson/connectors/fema.py +165 -0
- mrwilson-0.2.0/src/mrwilson/connectors/fmcsa.py +79 -0
- mrwilson-0.2.0/src/mrwilson/connectors/fred.py +82 -0
- mrwilson-0.2.0/src/mrwilson/connectors/fsis_recall.py +73 -0
- mrwilson-0.2.0/src/mrwilson/connectors/fueleconomy.py +74 -0
- mrwilson-0.2.0/src/mrwilson/connectors/geocoder.py +95 -0
- mrwilson-0.2.0/src/mrwilson/connectors/google_places.py +270 -0
- mrwilson-0.2.0/src/mrwilson/connectors/hud.py +116 -0
- mrwilson-0.2.0/src/mrwilson/connectors/nhtsa.py +151 -0
- mrwilson-0.2.0/src/mrwilson/connectors/noaa.py +178 -0
- mrwilson-0.2.0/src/mrwilson/connectors/nrel.py +87 -0
- mrwilson-0.2.0/src/mrwilson/connectors/openfda.py +99 -0
- mrwilson-0.2.0/src/mrwilson/connectors/osha.py +90 -0
- mrwilson-0.2.0/src/mrwilson/connectors/propublica_np.py +58 -0
- mrwilson-0.2.0/src/mrwilson/connectors/sec_edgar.py +120 -0
- mrwilson-0.2.0/src/mrwilson/connectors/sunlight.py +110 -0
- mrwilson-0.2.0/src/mrwilson/connectors/usda.py +84 -0
- mrwilson-0.2.0/src/mrwilson/connectors/usda_plants.py +120 -0
- mrwilson-0.2.0/src/mrwilson/connectors/usgs_water.py +93 -0
- mrwilson-0.2.0/src/mrwilson/connectors/uspto.py +86 -0
- mrwilson-0.2.0/src/mrwilson/connectors/va_gravesite.py +72 -0
- mrwilson-0.2.0/src/mrwilson/connectors/weather.py +96 -0
- mrwilson-0.2.0/src/mrwilson/data/bls_cache.json +246 -0
- mrwilson-0.2.0/src/mrwilson/data/nh_zip_cache.json +84 -0
- mrwilson-0.2.0/src/mrwilson/demo.py +646 -0
- mrwilson-0.2.0/src/mrwilson/menu_data.py +117 -0
- mrwilson-0.2.0/src/mrwilson/pdf_render.py +516 -0
- mrwilson-0.2.0/src/mrwilson/submit.py +87 -0
- mrwilson-0.2.0/src/mrwilson/verticals/__init__.py +185 -0
- mrwilson-0.2.0/src/mrwilson/verticals/_base.py +126 -0
- mrwilson-0.2.0/src/mrwilson/verticals/auto_repair.py +89 -0
- mrwilson-0.2.0/src/mrwilson/verticals/chiropractic.py +60 -0
- mrwilson-0.2.0/src/mrwilson/verticals/electrical.py +61 -0
- mrwilson-0.2.0/src/mrwilson/verticals/funeral_home.py +61 -0
- mrwilson-0.2.0/src/mrwilson/verticals/hvac.py +84 -0
- mrwilson-0.2.0/src/mrwilson/verticals/landscape.py +109 -0
- mrwilson-0.2.0/src/mrwilson/verticals/pest_control.py +60 -0
- mrwilson-0.2.0/src/mrwilson/verticals/plumbing.py +110 -0
- mrwilson-0.2.0/src/mrwilson/verticals/restaurant.py +92 -0
- mrwilson-0.2.0/src/mrwilson/verticals/roofing.py +59 -0
- mrwilson-0.2.0/src/mrwilson/verticals/salon.py +80 -0
- mrwilson-0.2.0/src/mrwilson/verticals/vet.py +107 -0
- mrwilson-0.2.0/src/mrwilson/voice.py +211 -0
mrwilson-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mrwilson
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Mr. Wilson stops by — a knowledgeable neighbor who knows the trade. Free 60-90 second proof of capability for small trades businesses using real federal data.
|
|
5
|
+
Project-URL: Homepage, https://bryanbartlett.com
|
|
6
|
+
Project-URL: Repository, https://github.com/bryanbartlett/mrwilson
|
|
7
|
+
Project-URL: Documentation, https://github.com/bryanbartlett/mrwilson/blob/main/README.md
|
|
8
|
+
Project-URL: Strategy & Mission Control, https://bryanbartlett.com/mrwilson-mission-control.html
|
|
9
|
+
Author-email: Bryan Bartlett <bry.bartlett@gmail.com>
|
|
10
|
+
License: Proprietary
|
|
11
|
+
Keywords: ai,hvac,sbdc,small-business,smb,trades
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: License :: Other/Proprietary License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Office/Business
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: anthropic>=0.40
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: pydantic>=2.6
|
|
25
|
+
Requires-Dist: pyfiglet>=1.0
|
|
26
|
+
Requires-Dist: reportlab>=4.0
|
|
27
|
+
Requires-Dist: rich>=13.7
|
|
28
|
+
Requires-Dist: typer>=0.12
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# mrwilson
|
|
35
|
+
|
|
36
|
+
> *Hey, I'm Mr. Wilson. Got a minute?*
|
|
37
|
+
|
|
38
|
+
A knowledgeable neighbor who happens to be unusually good at building small, targeted AI systems for shops.
|
|
39
|
+
|
|
40
|
+
This is the **Mr. Wilson stop-by CLI** — a free 60-90 second proof-of-capability for small trades business owners, paired with a 30-minute discovery audit that lands a lead in Bryan's inbox. The owner pays nothing for discovery; Bryan ships the bespoke fix for $500 once they've seen what we'd build.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# uv (recommended — zero-friction)
|
|
46
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
47
|
+
uvx mrwilson
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
mrwilson # full flow: demo + audit + submit
|
|
54
|
+
mrwilson demo # just the 60-90 second capability demo
|
|
55
|
+
mrwilson audit # skip the demo, go straight to the audit
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## What it does
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
1. Demo (free, ~90 sec) — Generates a sample Tuesday morning brief tuned
|
|
62
|
+
to your zip code. PDF lands in your Downloads.
|
|
63
|
+
2. Audit (free, ~30 min) — 12-15 questions about your shop. Submission
|
|
64
|
+
goes to Bryan's review queue.
|
|
65
|
+
3. Bryan reviews + pitches — Custom proposal in 24-48 hours. $500 per fix.
|
|
66
|
+
4. Mr. Wilson moves in — Bespoke deployment runs at your shop.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The demo runs at near-zero cost on Bryan's side because the per-zip data is pre-baked and only one live LLM call fires after the email gate. See `mrwilson-distribution-v3.md` in the workspace for the full architecture.
|
|
70
|
+
|
|
71
|
+
## Verticals supported
|
|
72
|
+
|
|
73
|
+
- **HVAC** ✓
|
|
74
|
+
- *Salon (coming soon)*
|
|
75
|
+
- *Auto repair (coming soon)*
|
|
76
|
+
|
|
77
|
+
## Project layout
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
src/mrwilson/
|
|
81
|
+
├── cli.py # Typer entrypoint
|
|
82
|
+
├── voice.py # brand voice strings (every line is Mr. Wilson speaking)
|
|
83
|
+
├── audit_questions.py # 12-15 questions, branch-aware on vertical
|
|
84
|
+
├── demo.py # T0 + T1 + T2 demo flow
|
|
85
|
+
├── audit.py # interactive audit Q&A
|
|
86
|
+
├── submit.py # T3 submission to work-mode /api/audit-intake
|
|
87
|
+
├── pdf_render.py # PDF generation with reportlab + brand styling
|
|
88
|
+
├── claude_client.py # T2 live Claude call (email-gated)
|
|
89
|
+
├── data/
|
|
90
|
+
│ ├── nh_zip_cache.json # T1 pre-baked content per NH zip
|
|
91
|
+
│ └── bls_metro_cache.json # cached BLS labor data per metro
|
|
92
|
+
└── styles/
|
|
93
|
+
└── pdf.css # brand styling (cream header, slab serif name)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Brand voice principle
|
|
97
|
+
|
|
98
|
+
**Every command is a sentence in Mr. Wilson's vocabulary.** Every progress line is something he'd actually say. If a line sounds like an engineering log, it's wrong.
|
|
99
|
+
|
|
100
|
+
See `SOUL.md` and `STYLE.md` in `~/.openclaw/workspace/mrwilson/` for the source character spec.
|
|
101
|
+
|
|
102
|
+
## Status
|
|
103
|
+
|
|
104
|
+
**MVP — HVAC + NH only.** Phase 2a of the distribution roadmap. See `mrwilson-distribution-v3.md` for full sequencing.
|
mrwilson-0.2.0/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# mrwilson
|
|
2
|
+
|
|
3
|
+
> *Hey, I'm Mr. Wilson. Got a minute?*
|
|
4
|
+
|
|
5
|
+
A knowledgeable neighbor who happens to be unusually good at building small, targeted AI systems for shops.
|
|
6
|
+
|
|
7
|
+
This is the **Mr. Wilson stop-by CLI** — a free 60-90 second proof-of-capability for small trades business owners, paired with a 30-minute discovery audit that lands a lead in Bryan's inbox. The owner pays nothing for discovery; Bryan ships the bespoke fix for $500 once they've seen what we'd build.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# uv (recommended — zero-friction)
|
|
13
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
14
|
+
uvx mrwilson
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
mrwilson # full flow: demo + audit + submit
|
|
21
|
+
mrwilson demo # just the 60-90 second capability demo
|
|
22
|
+
mrwilson audit # skip the demo, go straight to the audit
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## What it does
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
1. Demo (free, ~90 sec) — Generates a sample Tuesday morning brief tuned
|
|
29
|
+
to your zip code. PDF lands in your Downloads.
|
|
30
|
+
2. Audit (free, ~30 min) — 12-15 questions about your shop. Submission
|
|
31
|
+
goes to Bryan's review queue.
|
|
32
|
+
3. Bryan reviews + pitches — Custom proposal in 24-48 hours. $500 per fix.
|
|
33
|
+
4. Mr. Wilson moves in — Bespoke deployment runs at your shop.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The demo runs at near-zero cost on Bryan's side because the per-zip data is pre-baked and only one live LLM call fires after the email gate. See `mrwilson-distribution-v3.md` in the workspace for the full architecture.
|
|
37
|
+
|
|
38
|
+
## Verticals supported
|
|
39
|
+
|
|
40
|
+
- **HVAC** ✓
|
|
41
|
+
- *Salon (coming soon)*
|
|
42
|
+
- *Auto repair (coming soon)*
|
|
43
|
+
|
|
44
|
+
## Project layout
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
src/mrwilson/
|
|
48
|
+
├── cli.py # Typer entrypoint
|
|
49
|
+
├── voice.py # brand voice strings (every line is Mr. Wilson speaking)
|
|
50
|
+
├── audit_questions.py # 12-15 questions, branch-aware on vertical
|
|
51
|
+
├── demo.py # T0 + T1 + T2 demo flow
|
|
52
|
+
├── audit.py # interactive audit Q&A
|
|
53
|
+
├── submit.py # T3 submission to work-mode /api/audit-intake
|
|
54
|
+
├── pdf_render.py # PDF generation with reportlab + brand styling
|
|
55
|
+
├── claude_client.py # T2 live Claude call (email-gated)
|
|
56
|
+
├── data/
|
|
57
|
+
│ ├── nh_zip_cache.json # T1 pre-baked content per NH zip
|
|
58
|
+
│ └── bls_metro_cache.json # cached BLS labor data per metro
|
|
59
|
+
└── styles/
|
|
60
|
+
└── pdf.css # brand styling (cream header, slab serif name)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Brand voice principle
|
|
64
|
+
|
|
65
|
+
**Every command is a sentence in Mr. Wilson's vocabulary.** Every progress line is something he'd actually say. If a line sounds like an engineering log, it's wrong.
|
|
66
|
+
|
|
67
|
+
See `SOUL.md` and `STYLE.md` in `~/.openclaw/workspace/mrwilson/` for the source character spec.
|
|
68
|
+
|
|
69
|
+
## Status
|
|
70
|
+
|
|
71
|
+
**MVP — HVAC + NH only.** Phase 2a of the distribution roadmap. See `mrwilson-distribution-v3.md` for full sequencing.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mrwilson"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Mr. Wilson stops by — a knowledgeable neighbor who knows the trade. Free 60-90 second proof of capability for small trades businesses using real federal data."
|
|
5
|
+
authors = [{ name = "Bryan Bartlett", email = "bry.bartlett@gmail.com" }]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = { text = "Proprietary" }
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
keywords = ["ai", "smb", "trades", "hvac", "small-business", "sbdc"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Intended Audience :: End Users/Desktop",
|
|
13
|
+
"License :: Other/Proprietary License",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"Topic :: Office/Business",
|
|
19
|
+
"Topic :: Utilities",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
dependencies = [
|
|
23
|
+
"typer>=0.12",
|
|
24
|
+
"rich>=13.7",
|
|
25
|
+
"httpx>=0.27",
|
|
26
|
+
"anthropic>=0.40",
|
|
27
|
+
"reportlab>=4.0",
|
|
28
|
+
"pydantic>=2.6",
|
|
29
|
+
"pyfiglet>=1.0", # Yellow Pages chunky slab type (cover, recap, ad header)
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=8.0",
|
|
35
|
+
"ruff>=0.5",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
mrwilson = "mrwilson.cli:app"
|
|
40
|
+
|
|
41
|
+
[build-system]
|
|
42
|
+
requires = ["hatchling"]
|
|
43
|
+
build-backend = "hatchling.build"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/mrwilson"]
|
|
47
|
+
|
|
48
|
+
# Keep the sdist to just the package + its docs. Without this, hatchling sweeps
|
|
49
|
+
# the whole repo into the tarball — including the marketing site under landing/
|
|
50
|
+
# (vintage Yellow Pages scans of uncertain license, the dev server, etc.), which
|
|
51
|
+
# has no business on public PyPI.
|
|
52
|
+
[tool.hatch.build.targets.sdist]
|
|
53
|
+
include = [
|
|
54
|
+
"src/mrwilson",
|
|
55
|
+
"README.md",
|
|
56
|
+
"pyproject.toml",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[project.urls]
|
|
60
|
+
Homepage = "https://bryanbartlett.com"
|
|
61
|
+
Repository = "https://github.com/bryanbartlett/mrwilson"
|
|
62
|
+
Documentation = "https://github.com/bryanbartlett/mrwilson/blob/main/README.md"
|
|
63
|
+
"Strategy & Mission Control" = "https://bryanbartlett.com/mrwilson-mission-control.html"
|
|
64
|
+
|
|
65
|
+
[tool.ruff]
|
|
66
|
+
line-length = 100
|
|
67
|
+
target-version = "py311"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""mrwilson — a knowledgeable neighbor who stops by your shop."""
|
|
2
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
__version__ = version("mrwilson")
|
|
6
|
+
except PackageNotFoundError:
|
|
7
|
+
__version__ = "0.2.0" # fallback during development / editable installs before first build
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Audit Q&A flow — 12-15 questions, branch-aware, with the Pushback Moment
|
|
3
|
+
codified into the wizard's logic.
|
|
4
|
+
|
|
5
|
+
The Pushback Moment: when the owner gives a vague answer to a pushback-eligible
|
|
6
|
+
question, Mr. Wilson asks one follow-up before moving on. He does NOT hammer —
|
|
7
|
+
one follow-up only, then accepts whatever comes back.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import random
|
|
13
|
+
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.prompt import Prompt
|
|
16
|
+
from rich.text import Text
|
|
17
|
+
|
|
18
|
+
from . import voice
|
|
19
|
+
from .audit_questions import questions_for
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _ask(prompt: str, *, default: str | None = None) -> str:
|
|
25
|
+
"""Render a question in Mr. Wilson's voice and read input."""
|
|
26
|
+
console.print()
|
|
27
|
+
console.print(Text(f"[Mr. Wilson] {prompt}", style="bold"))
|
|
28
|
+
return Prompt.ask("›", default=default or "").strip()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run_audit(*, vertical: str) -> dict[str, str]:
|
|
32
|
+
"""
|
|
33
|
+
Run the audit Q&A. Returns a dict mapping question keys to answers.
|
|
34
|
+
"""
|
|
35
|
+
console.print()
|
|
36
|
+
console.print(Text(f"[Mr. Wilson] {voice.AUDIT_OPENING}", style="bold dim"))
|
|
37
|
+
|
|
38
|
+
answers: dict[str, str] = {}
|
|
39
|
+
questions = questions_for(vertical)
|
|
40
|
+
|
|
41
|
+
for q in questions:
|
|
42
|
+
ans = _ask(q.prompt)
|
|
43
|
+
|
|
44
|
+
# Pushback Moment — one follow-up if the answer is vague
|
|
45
|
+
if q.pushback_eligible and voice.is_vague_answer(ans):
|
|
46
|
+
pushback = random.choice(voice.PUSHBACK_LINES)
|
|
47
|
+
follow_up = _ask(pushback)
|
|
48
|
+
if follow_up:
|
|
49
|
+
ans = f"{ans} | (follow-up) {follow_up}" if ans else follow_up
|
|
50
|
+
|
|
51
|
+
if not ans:
|
|
52
|
+
ans = "(no answer)"
|
|
53
|
+
answers[q.key] = ans
|
|
54
|
+
|
|
55
|
+
return answers
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Audit question schema. 12-15 questions per vertical, branch-aware.
|
|
3
|
+
|
|
4
|
+
Each question has a `key` (for the submission JSON), `prompt` (what Mr. Wilson
|
|
5
|
+
asks), `pushback_eligible` (whether vague answers should trigger the Pushback
|
|
6
|
+
Moment), and an optional `branch_on` for vertical-specific routing.
|
|
7
|
+
|
|
8
|
+
The questions are deliberately conversational. They're the kind of thing you'd
|
|
9
|
+
ask leaning over the counter at the parts shop, not what you'd hand someone on
|
|
10
|
+
a clipboard.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Question:
|
|
18
|
+
key: str
|
|
19
|
+
prompt: str
|
|
20
|
+
pushback_eligible: bool = True
|
|
21
|
+
follow_up: str | None = None # optional one-liner shown after answer
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Universal questions — asked regardless of vertical
|
|
25
|
+
UNIVERSAL = [
|
|
26
|
+
Question(
|
|
27
|
+
key="employee_count",
|
|
28
|
+
prompt="Tell me about your shop. How many people work there?",
|
|
29
|
+
pushback_eligible=False, # numbers are usually concrete
|
|
30
|
+
),
|
|
31
|
+
Question(
|
|
32
|
+
key="biggest_time_sink",
|
|
33
|
+
prompt="Walk me through last week. What part took longer than it should have?",
|
|
34
|
+
),
|
|
35
|
+
Question(
|
|
36
|
+
key="what_youve_tried",
|
|
37
|
+
prompt="Have you tried any tools or systems for this? What worked, what didn't?",
|
|
38
|
+
),
|
|
39
|
+
Question(
|
|
40
|
+
key="compliance_pressure",
|
|
41
|
+
prompt="Anything regulatory you're keeping an eye on lately? New rules, audits, paperwork?",
|
|
42
|
+
),
|
|
43
|
+
Question(
|
|
44
|
+
key="one_number_that_matters",
|
|
45
|
+
prompt="If you could see one number every Monday morning, what would it be?",
|
|
46
|
+
),
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# HVAC-specific
|
|
50
|
+
HVAC = [
|
|
51
|
+
Question(
|
|
52
|
+
key="contract_mix",
|
|
53
|
+
prompt="What's your maintenance-contract vs one-off-service mix? Roughly.",
|
|
54
|
+
),
|
|
55
|
+
Question(
|
|
56
|
+
key="refrigerant_tracking",
|
|
57
|
+
prompt="How are you tracking refrigerant right now — paper, spreadsheet, software?",
|
|
58
|
+
),
|
|
59
|
+
Question(
|
|
60
|
+
key="filter_inventory",
|
|
61
|
+
prompt="Filter inventory — how do you handle running out of common sizes?",
|
|
62
|
+
),
|
|
63
|
+
Question(
|
|
64
|
+
key="equipment_data_location",
|
|
65
|
+
prompt="Where does your client equipment data live? FSM software, sheets, paper notes?",
|
|
66
|
+
),
|
|
67
|
+
Question(
|
|
68
|
+
key="tech_count_experience",
|
|
69
|
+
prompt="How many techs, and what's the experience mix? Any newer ones still learning?",
|
|
70
|
+
),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
# Salon-specific
|
|
74
|
+
SALON = [
|
|
75
|
+
Question(
|
|
76
|
+
key="stylist_count",
|
|
77
|
+
prompt="How many stylists, and do they each manage their own products?",
|
|
78
|
+
),
|
|
79
|
+
Question(
|
|
80
|
+
key="booking_software",
|
|
81
|
+
prompt="What booking software are you using? Vagaro, Booksy, paper book?",
|
|
82
|
+
),
|
|
83
|
+
Question(
|
|
84
|
+
key="product_inventory",
|
|
85
|
+
prompt="How are you tracking color and chemical inventory across stylists?",
|
|
86
|
+
),
|
|
87
|
+
Question(
|
|
88
|
+
key="client_history",
|
|
89
|
+
prompt="When a regular comes back, how do you remember what they had last time?",
|
|
90
|
+
),
|
|
91
|
+
Question(
|
|
92
|
+
key="hazcom_compliance",
|
|
93
|
+
prompt="OSHA HazCom and SDS sheets — where do those live for your shop?",
|
|
94
|
+
),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
# Auto repair-specific
|
|
98
|
+
AUTO = [
|
|
99
|
+
Question(
|
|
100
|
+
key="ase_certs",
|
|
101
|
+
prompt="What certifications does your shop hold? ASE? EPA Section 609?",
|
|
102
|
+
),
|
|
103
|
+
Question(
|
|
104
|
+
key="r1234yf_machine",
|
|
105
|
+
prompt="Got an R-1234yf machine yet, or still on R-134a only?",
|
|
106
|
+
),
|
|
107
|
+
Question(
|
|
108
|
+
key="repair_info_software",
|
|
109
|
+
prompt="What repair info platform — ALLDATA, Mitchell1, Identifix?",
|
|
110
|
+
),
|
|
111
|
+
Question(
|
|
112
|
+
key="parts_supplier",
|
|
113
|
+
prompt="Who's your main parts supplier? Online or counter?",
|
|
114
|
+
),
|
|
115
|
+
Question(
|
|
116
|
+
key="diag_time_average",
|
|
117
|
+
prompt="When a customer brings in a check-engine light, what's a typical diag time?",
|
|
118
|
+
),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
# Veterinary clinic-specific (Doc Mabel)
|
|
122
|
+
VET = [
|
|
123
|
+
Question(
|
|
124
|
+
key="practice_type",
|
|
125
|
+
prompt="What kind of practice are we talking about — small animal only, mixed, exotics, large animal?",
|
|
126
|
+
),
|
|
127
|
+
Question(
|
|
128
|
+
key="pims_software",
|
|
129
|
+
prompt="What's your practice management system — Cornerstone, AVImark, ezyVet, ImproMed, something else?",
|
|
130
|
+
),
|
|
131
|
+
Question(
|
|
132
|
+
key="reminder_lapse",
|
|
133
|
+
prompt="When a patient is overdue for a recheck or vaccination, how do you find out — software, gut, the front desk noticing?",
|
|
134
|
+
),
|
|
135
|
+
Question(
|
|
136
|
+
key="controlled_substances",
|
|
137
|
+
prompt="DEA controlled-substance log — paper, software, the binder under the counter?",
|
|
138
|
+
),
|
|
139
|
+
Question(
|
|
140
|
+
key="dental_attach",
|
|
141
|
+
prompt="Dental cleanings — what share of your eligible patients actually book? Roughly.",
|
|
142
|
+
),
|
|
143
|
+
Question(
|
|
144
|
+
key="client_comm_channel",
|
|
145
|
+
prompt="How do you reach clients between visits — text, email, phone calls, the postcard?",
|
|
146
|
+
),
|
|
147
|
+
Question(
|
|
148
|
+
key="staff_burnout",
|
|
149
|
+
prompt="Compassion-fatigue and turnover — has it cost you in the last year? Be honest.",
|
|
150
|
+
pushback_eligible=False,
|
|
151
|
+
),
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
# Plumbing-specific (Murph)
|
|
155
|
+
PLUMBING = [
|
|
156
|
+
Question(
|
|
157
|
+
key="license_class",
|
|
158
|
+
prompt="What's your license class — master, journeyman, both on the truck? And how many state licenses across your crew?",
|
|
159
|
+
),
|
|
160
|
+
Question(
|
|
161
|
+
key="dispatch_software",
|
|
162
|
+
prompt="What's your dispatch and FSM stack — ServiceTitan, Jobber, Housecall Pro, paper?",
|
|
163
|
+
),
|
|
164
|
+
Question(
|
|
165
|
+
key="emergency_call_mix",
|
|
166
|
+
prompt="Roughly what split between emergency calls vs scheduled work? Last week's gut.",
|
|
167
|
+
),
|
|
168
|
+
Question(
|
|
169
|
+
key="backflow_program",
|
|
170
|
+
prompt="Backflow testing — do you run that as a program, or just when somebody calls?",
|
|
171
|
+
),
|
|
172
|
+
Question(
|
|
173
|
+
key="parts_truck_stock",
|
|
174
|
+
prompt="When a tech doesn't have the part on the truck, how often does it cost you a second trip? Once a week? Once a day?",
|
|
175
|
+
),
|
|
176
|
+
Question(
|
|
177
|
+
key="estimate_followup",
|
|
178
|
+
prompt="An estimate goes out and nothing happens — what's your follow-up rhythm?",
|
|
179
|
+
),
|
|
180
|
+
Question(
|
|
181
|
+
key="permit_pull",
|
|
182
|
+
prompt="Permit pulls — who does them and how often do they hold up a job?",
|
|
183
|
+
),
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
# Landscape-specific (Don Beto)
|
|
187
|
+
LANDSCAPE = [
|
|
188
|
+
Question(
|
|
189
|
+
key="crew_count",
|
|
190
|
+
prompt="How many crews are running right now, and how many guys per crew?",
|
|
191
|
+
pushback_eligible=False,
|
|
192
|
+
),
|
|
193
|
+
Question(
|
|
194
|
+
key="route_density",
|
|
195
|
+
prompt="On a Tuesday route — how many properties does one crew hit? And how much windshield time between stops?",
|
|
196
|
+
),
|
|
197
|
+
Question(
|
|
198
|
+
key="contract_mix",
|
|
199
|
+
prompt="What's your mix — recurring maintenance contracts vs. one-off jobs? Roughly.",
|
|
200
|
+
),
|
|
201
|
+
Question(
|
|
202
|
+
key="applicator_license",
|
|
203
|
+
prompt="Pesticide and fertilizer applicator license — who on the crew is certified, and when does it renew?",
|
|
204
|
+
),
|
|
205
|
+
Question(
|
|
206
|
+
key="estimate_software",
|
|
207
|
+
prompt="How do you put together an estimate — software, spreadsheet, back of an envelope?",
|
|
208
|
+
),
|
|
209
|
+
Question(
|
|
210
|
+
key="snow_or_seasonal",
|
|
211
|
+
prompt="Off-season — snow removal, holiday lights, or do you just power down? How does that line look?",
|
|
212
|
+
),
|
|
213
|
+
Question(
|
|
214
|
+
key="rain_day_plan",
|
|
215
|
+
prompt="When it rains and the crews can't go out, what happens? Paid time, send them home, indoor work?",
|
|
216
|
+
),
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# Closing questions — universal, asked at the end
|
|
221
|
+
CLOSING = [
|
|
222
|
+
Question(
|
|
223
|
+
key="smallest_possible_win",
|
|
224
|
+
prompt="If we shipped one thing in 2 weeks that you could see working, what would make the biggest difference?",
|
|
225
|
+
),
|
|
226
|
+
Question(
|
|
227
|
+
key="budget_reality",
|
|
228
|
+
prompt="What's a fair monthly cost for something that actually saved you time?",
|
|
229
|
+
pushback_eligible=False,
|
|
230
|
+
),
|
|
231
|
+
Question(
|
|
232
|
+
key="decision_power",
|
|
233
|
+
prompt="Who decides on something like this — just you, or a partner?",
|
|
234
|
+
pushback_eligible=False,
|
|
235
|
+
),
|
|
236
|
+
Question(
|
|
237
|
+
key="timing",
|
|
238
|
+
prompt="When could you actually use this — next month, this quarter, or 'soon'?",
|
|
239
|
+
pushback_eligible=False,
|
|
240
|
+
),
|
|
241
|
+
Question(
|
|
242
|
+
key="anything_else",
|
|
243
|
+
prompt="Anything you wanted to ask me, while I'm here?",
|
|
244
|
+
pushback_eligible=False,
|
|
245
|
+
),
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# Roofing-specific (Buck)
|
|
250
|
+
ROOFING = [
|
|
251
|
+
Question(key="crew_and_license", prompt="How many roofing crews are running, and what's your state license + insurance situation?", pushback_eligible=False),
|
|
252
|
+
Question(key="storm_vs_retail", prompt="What's your mix — storm/insurance claim work vs. retail re-roofs? Roughly."),
|
|
253
|
+
Question(key="claim_workflow", prompt="When a hail or wind claim comes in, walk me through how you get from inspection to approved scope."),
|
|
254
|
+
Question(key="material_ordering", prompt="How do you order material — measure off the roof, EagleView, guess-and-pad?"),
|
|
255
|
+
Question(key="warranty_tracking", prompt="Manufacturer warranties — how do you track which roofs are registered and still covered?"),
|
|
256
|
+
Question(key="osha_safety", prompt="Fall protection and OSHA — how's that documented when an inspector or GC asks?"),
|
|
257
|
+
Question(key="backlog", prompt="What's your backlog look like right now — weeks out, or chasing work?"),
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
# Pest-control-specific (Cap'n Roach)
|
|
261
|
+
PEST = [
|
|
262
|
+
Question(key="recurring_mix", prompt="What's your recurring-contract vs. one-off mix? Recurring is the whole game here — be honest."),
|
|
263
|
+
Question(key="applicator_license", prompt="Pesticide applicator license — who's certified, what categories, and when's renewal?"),
|
|
264
|
+
Question(key="product_tracking", prompt="How are you tracking what product went down where — paper service ticket, software, memory?"),
|
|
265
|
+
Question(key="route_density", prompt="On a typical route day, how many stops per tech, and how much drive time between them?"),
|
|
266
|
+
Question(key="seasonal_swing", prompt="What does your call volume do May-to-August vs. December? How do you staff the swing?"),
|
|
267
|
+
Question(key="callback_rate", prompt="When a treatment doesn't take and they call back — how often, and what does a re-treat cost you?"),
|
|
268
|
+
Question(key="regulatory_records", prompt="State pesticide recordkeeping — if the department showed up tomorrow, how ready are those records?"),
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
# Funeral-home-specific (Mort Goldfarb)
|
|
272
|
+
FUNERAL = [
|
|
273
|
+
Question(key="calls_per_year", prompt="Roughly how many calls a year, and is that trending up, flat, or down?", pushback_eligible=False),
|
|
274
|
+
Question(key="preneed_program", prompt="Pre-need — do you run it as a real program, or just when a family asks?"),
|
|
275
|
+
Question(key="price_list_status", prompt="Your General Price List — when was it last updated, and is it posted online yet?"),
|
|
276
|
+
Question(key="arrangement_workflow", prompt="Walk me through an arrangement conference — what paperwork and decisions happen, in what order?"),
|
|
277
|
+
Question(key="veteran_benefits", prompt="When a decedent's a veteran, how do you currently surface the VA benefits to the family?"),
|
|
278
|
+
Question(key="aftercare", prompt="Aftercare and follow-up with families — is there a system, or does it depend who's working?"),
|
|
279
|
+
Question(key="succession", prompt="Succession — is the next generation or a buyer in the picture, or is that an open question?", pushback_eligible=False),
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
# Chiropractic-specific (Doc Rosie)
|
|
283
|
+
CHIRO = [
|
|
284
|
+
Question(key="provider_count", prompt="How many DCs and where are you on capacity — booked solid or room to grow?", pushback_eligible=False),
|
|
285
|
+
Question(key="payer_mix", prompt="What's your payer mix — cash, Medicare, commercial? Roughly the split."),
|
|
286
|
+
Question(key="denial_rate", prompt="Insurance denials — what's your rough denial rate, and what's the #1 reason they bounce?"),
|
|
287
|
+
Question(key="documentation", prompt="Medicare documentation — AT modifier, subluxation, treatment plan: confident or shaky?"),
|
|
288
|
+
Question(key="ehr_system", prompt="What EHR are you on — ChiroTouch, Jane, paper SOAP notes?"),
|
|
289
|
+
Question(key="recall_reactivation", prompt="Patients who drop off mid-plan — how do you find them and bring them back?"),
|
|
290
|
+
Question(key="fee_schedule", prompt="When did you last check your cash fees against what Medicare actually allows in your area?"),
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
# Electrical-specific (Sparky Sullivan)
|
|
294
|
+
ELECTRICAL = [
|
|
295
|
+
Question(key="license_class", prompt="License class — master, journeyman, both on the truck? And how many licensed hands?", pushback_eligible=False),
|
|
296
|
+
Question(key="resi_comm_mix", prompt="Residential service vs. commercial vs. new-construction — what's the rough mix?"),
|
|
297
|
+
Question(key="permit_workflow", prompt="Permits and inspections — who pulls them and how often does one hold up a job?"),
|
|
298
|
+
Question(key="ev_solar_work", prompt="EV chargers and solar interconnect — are you doing that work, turning it away, or want more of it?"),
|
|
299
|
+
Question(key="material_pricing", prompt="Copper and gear prices move fast — how do you handle a bid written 60 days before the work?"),
|
|
300
|
+
Question(key="code_cycle", prompt="Which NEC cycle is your jurisdiction on, and how do you keep the crew current on changes?"),
|
|
301
|
+
Question(key="service_vs_project", prompt="Service calls vs. project work — which carries the business, and which do you wish carried more?"),
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def questions_for(vertical: str) -> list[Question]:
|
|
306
|
+
"""Return the ordered question list for a given vertical."""
|
|
307
|
+
vertical_specific = {
|
|
308
|
+
"hvac": HVAC,
|
|
309
|
+
"salon": SALON,
|
|
310
|
+
"auto": AUTO,
|
|
311
|
+
"vet": VET,
|
|
312
|
+
"plumbing": PLUMBING,
|
|
313
|
+
"landscape": LANDSCAPE,
|
|
314
|
+
"roofing": ROOFING,
|
|
315
|
+
"pest": PEST,
|
|
316
|
+
"funeral": FUNERAL,
|
|
317
|
+
"chiro": CHIRO,
|
|
318
|
+
"electrical": ELECTRICAL,
|
|
319
|
+
}.get(vertical, [])
|
|
320
|
+
return UNIVERSAL + vertical_specific + CLOSING
|