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.
Files changed (79) hide show
  1. mrwilson-0.2.0/.gitignore +14 -0
  2. mrwilson-0.2.0/PKG-INFO +104 -0
  3. mrwilson-0.2.0/README.md +71 -0
  4. mrwilson-0.2.0/pyproject.toml +67 -0
  5. mrwilson-0.2.0/src/mrwilson/__init__.py +7 -0
  6. mrwilson-0.2.0/src/mrwilson/__main__.py +5 -0
  7. mrwilson-0.2.0/src/mrwilson/audit.py +55 -0
  8. mrwilson-0.2.0/src/mrwilson/audit_questions.py +320 -0
  9. mrwilson-0.2.0/src/mrwilson/audit_report.py +255 -0
  10. mrwilson-0.2.0/src/mrwilson/brand.py +470 -0
  11. mrwilson-0.2.0/src/mrwilson/claude_client.py +451 -0
  12. mrwilson-0.2.0/src/mrwilson/cli.py +416 -0
  13. mrwilson-0.2.0/src/mrwilson/connectors/__init__.py +0 -0
  14. mrwilson-0.2.0/src/mrwilson/connectors/_keys.py +30 -0
  15. mrwilson-0.2.0/src/mrwilson/connectors/airquality.py +148 -0
  16. mrwilson-0.2.0/src/mrwilson/connectors/bea.py +98 -0
  17. mrwilson-0.2.0/src/mrwilson/connectors/bls.py +297 -0
  18. mrwilson-0.2.0/src/mrwilson/connectors/cdc_arbonet.py +62 -0
  19. mrwilson-0.2.0/src/mrwilson/connectors/cdc_wonder.py +70 -0
  20. mrwilson-0.2.0/src/mrwilson/connectors/census.py +114 -0
  21. mrwilson-0.2.0/src/mrwilson/connectors/census_cbp.py +93 -0
  22. mrwilson-0.2.0/src/mrwilson/connectors/cfpb.py +70 -0
  23. mrwilson-0.2.0/src/mrwilson/connectors/cms_fee.py +77 -0
  24. mrwilson-0.2.0/src/mrwilson/connectors/cms_npi.py +74 -0
  25. mrwilson-0.2.0/src/mrwilson/connectors/congress.py +81 -0
  26. mrwilson-0.2.0/src/mrwilson/connectors/courtlistener.py +78 -0
  27. mrwilson-0.2.0/src/mrwilson/connectors/drought_monitor.py +81 -0
  28. mrwilson-0.2.0/src/mrwilson/connectors/eia.py +89 -0
  29. mrwilson-0.2.0/src/mrwilson/connectors/epa_echo.py +78 -0
  30. mrwilson-0.2.0/src/mrwilson/connectors/epa_ppls.py +67 -0
  31. mrwilson-0.2.0/src/mrwilson/connectors/eviction_lab.py +38 -0
  32. mrwilson-0.2.0/src/mrwilson/connectors/fda.py +103 -0
  33. mrwilson-0.2.0/src/mrwilson/connectors/fda_cosmetics.py +63 -0
  34. mrwilson-0.2.0/src/mrwilson/connectors/fda_greenbook.py +68 -0
  35. mrwilson-0.2.0/src/mrwilson/connectors/fda_shortage.py +71 -0
  36. mrwilson-0.2.0/src/mrwilson/connectors/federal_register.py +84 -0
  37. mrwilson-0.2.0/src/mrwilson/connectors/fema.py +165 -0
  38. mrwilson-0.2.0/src/mrwilson/connectors/fmcsa.py +79 -0
  39. mrwilson-0.2.0/src/mrwilson/connectors/fred.py +82 -0
  40. mrwilson-0.2.0/src/mrwilson/connectors/fsis_recall.py +73 -0
  41. mrwilson-0.2.0/src/mrwilson/connectors/fueleconomy.py +74 -0
  42. mrwilson-0.2.0/src/mrwilson/connectors/geocoder.py +95 -0
  43. mrwilson-0.2.0/src/mrwilson/connectors/google_places.py +270 -0
  44. mrwilson-0.2.0/src/mrwilson/connectors/hud.py +116 -0
  45. mrwilson-0.2.0/src/mrwilson/connectors/nhtsa.py +151 -0
  46. mrwilson-0.2.0/src/mrwilson/connectors/noaa.py +178 -0
  47. mrwilson-0.2.0/src/mrwilson/connectors/nrel.py +87 -0
  48. mrwilson-0.2.0/src/mrwilson/connectors/openfda.py +99 -0
  49. mrwilson-0.2.0/src/mrwilson/connectors/osha.py +90 -0
  50. mrwilson-0.2.0/src/mrwilson/connectors/propublica_np.py +58 -0
  51. mrwilson-0.2.0/src/mrwilson/connectors/sec_edgar.py +120 -0
  52. mrwilson-0.2.0/src/mrwilson/connectors/sunlight.py +110 -0
  53. mrwilson-0.2.0/src/mrwilson/connectors/usda.py +84 -0
  54. mrwilson-0.2.0/src/mrwilson/connectors/usda_plants.py +120 -0
  55. mrwilson-0.2.0/src/mrwilson/connectors/usgs_water.py +93 -0
  56. mrwilson-0.2.0/src/mrwilson/connectors/uspto.py +86 -0
  57. mrwilson-0.2.0/src/mrwilson/connectors/va_gravesite.py +72 -0
  58. mrwilson-0.2.0/src/mrwilson/connectors/weather.py +96 -0
  59. mrwilson-0.2.0/src/mrwilson/data/bls_cache.json +246 -0
  60. mrwilson-0.2.0/src/mrwilson/data/nh_zip_cache.json +84 -0
  61. mrwilson-0.2.0/src/mrwilson/demo.py +646 -0
  62. mrwilson-0.2.0/src/mrwilson/menu_data.py +117 -0
  63. mrwilson-0.2.0/src/mrwilson/pdf_render.py +516 -0
  64. mrwilson-0.2.0/src/mrwilson/submit.py +87 -0
  65. mrwilson-0.2.0/src/mrwilson/verticals/__init__.py +185 -0
  66. mrwilson-0.2.0/src/mrwilson/verticals/_base.py +126 -0
  67. mrwilson-0.2.0/src/mrwilson/verticals/auto_repair.py +89 -0
  68. mrwilson-0.2.0/src/mrwilson/verticals/chiropractic.py +60 -0
  69. mrwilson-0.2.0/src/mrwilson/verticals/electrical.py +61 -0
  70. mrwilson-0.2.0/src/mrwilson/verticals/funeral_home.py +61 -0
  71. mrwilson-0.2.0/src/mrwilson/verticals/hvac.py +84 -0
  72. mrwilson-0.2.0/src/mrwilson/verticals/landscape.py +109 -0
  73. mrwilson-0.2.0/src/mrwilson/verticals/pest_control.py +60 -0
  74. mrwilson-0.2.0/src/mrwilson/verticals/plumbing.py +110 -0
  75. mrwilson-0.2.0/src/mrwilson/verticals/restaurant.py +92 -0
  76. mrwilson-0.2.0/src/mrwilson/verticals/roofing.py +59 -0
  77. mrwilson-0.2.0/src/mrwilson/verticals/salon.py +80 -0
  78. mrwilson-0.2.0/src/mrwilson/verticals/vet.py +107 -0
  79. mrwilson-0.2.0/src/mrwilson/voice.py +211 -0
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ .venv/
6
+ venv/
7
+ dist/
8
+ build/
9
+ *.pdf
10
+ .env
11
+ .envrc
12
+ .pytest_cache/
13
+ .ruff_cache/
14
+ .DS_Store
@@ -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.
@@ -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,5 @@
1
+ """Allow running with `python -m mrwilson`."""
2
+ from .cli import app
3
+
4
+ if __name__ == "__main__":
5
+ app()
@@ -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