Case Study 2: Maya's Project Intake Screening Tool
Character: Maya Reyes (independent business consultant) Setting: Maya's home office, early morning
The Situation
Maya Reyes has been a freelance business consultant for six years. She specializes in process improvement and business systems for mid-size companies — the kind of work that pays well, challenges her intellectually, and fits her schedule. Over those six years, she has developed a strong intuition for which projects will be rewarding and which will become draining, scope-creeping nightmares.
The problem: she has gotten busy enough that she now receives ten to fifteen project inquiries per month, and she has been saying yes to too many of them. Three months ago she accepted a project that checked every box on paper but turned out to be a terrible fit — unclear decision-making structure, unrealistic timeline expectations, and a budget that felt generous until the scope expanded. It cost her two months of stress and an opportunity cost she is still calculating.
Maya makes a decision: before she invests two hours in an exploratory call with a prospect, she wants a systematic screening process. She already knows her criteria intuitively. Now she wants to write them down — and then write a Python script that can run a quick pre-screening so she knows whether to proceed to a call, flag it for closer review, or decline gracefully.
Step 1: Defining Maya's Criteria
Maya sits down with a notebook and writes out every factor she considers when evaluating a project. After thirty minutes she has a list:
Project Budget (must be in the right range) - Below $8,000: Too small — the project economics do not work for her rate and the overhead of client management - $8,000–$25,000: Sweet spot — meaningful work, manageable scope - $25,001–$60,000: Acceptable — larger projects are fine with the right structure in place - Above $60,000: Requires special consideration — she wants a strong team structure before taking on a project this size
Project Type (must be in her wheelhouse) - Process improvement, systems design, change management, training design, organizational assessment: Yes - Software development, graphic design, legal services, accounting/tax: No — these are not her expertise and she will not pretend otherwise
Client Fit Factors (a mix of positives and red flags) Positive signals: - Client has a clear decision-maker identified - Client has a defined budget (not "we'll figure it out") - Client has experience working with external consultants before - Project has a defined timeline
Red flags (each one adds risk): - Client wants to start "immediately" with no defined scope - Client has had more than one previous consultant disengage from the project - Client's stated timeline is less than half of what Maya estimates as realistic - The client contact has no authority to approve the final deliverable
Geographic fit: - Maya works remotely-first; local travel is acceptable - She requires virtual-first capability; if the client refuses to work remotely at all, it is a no-fit for her current practice model
Exclusions (absolute no-go regardless of other factors): - Industries she does not serve: weapons manufacturing, gambling operations, tobacco - Projects requiring her to misrepresent information to third parties
Step 2: Translating to Python
"""
project_intake_screener.py
Maya Reyes Consulting — Project Intake Pre-Screening Tool
Before investing two hours in an exploratory call, run a prospective
project through this screener. It applies Maya's documented project
selection criteria and returns one of three recommendations:
PROCEED — Strong fit across all dimensions, schedule the call
REVIEW — Mixed signals, worth a call but go in with open eyes
DECLINE — Clear misfit, decline gracefully
This script encodes Maya's business judgment from her 6 years of
consulting. It is a tool, not a final answer — her judgment overrides
the recommendation in edge cases.
"""
from dataclasses import dataclass, field
# ---------------------------------------------------------------------------
# Maya's Policy Constants
# (Update these as her practice evolves — they should reflect real criteria)
# ---------------------------------------------------------------------------
BUDGET_MINIMUM = 8_000 # Below this: economics do not work
BUDGET_SWEET_SPOT_MAX = 25_000 # Ideal upper bound for straightforward engagement
BUDGET_STRETCH_MAX = 60_000 # Acceptable but requires additional vetting
# Above BUDGET_STRETCH_MAX: requires special consideration
# Project types Maya accepts
ACCEPTED_PROJECT_TYPES = {
"process improvement",
"systems design",
"change management",
"training design",
"organizational assessment",
"workflow optimization",
"business analysis",
}
# Absolute exclusions — no amount of good fit elsewhere overrides these
EXCLUDED_INDUSTRIES = {
"weapons manufacturing",
"gambling",
"tobacco",
}
# Number of red flags that escalates a REVIEW to a DECLINE
RED_FLAG_DECLINE_THRESHOLD = 3
# ---------------------------------------------------------------------------
# Data Structures
# ---------------------------------------------------------------------------
@dataclass
class ProjectInquiry:
"""
All the information Maya gathers from an initial inquiry form or email
before deciding whether to schedule an exploratory call.
"""
# Basic project info
client_name: str
project_type: str # e.g., "process improvement"
industry: str # e.g., "healthcare", "retail"
budget_usd: float # Stated budget (0 if not disclosed)
timeline_weeks_stated: int # Client's stated timeline
timeline_weeks_realistic: int # Maya's estimate of realistic timeline
# Client fit factors
has_clear_decision_maker: bool # Is there one person who can say yes?
has_defined_budget: bool # Did they actually name a number?
has_consultant_experience: bool # Have they hired a consultant before?
has_defined_timeline: bool # Is there an actual end date?
requires_only_virtual: bool # Can all work be done remotely?
# Red flags (each True adds to the red flag count)
wants_immediate_start_no_scope: bool # "Can you start Monday?" with nothing defined
previous_consultants_disengaged: int # How many prior consultants left the project
client_contact_lacks_authority: bool # Contact cannot approve final deliverable
requests_misrepresentation: bool # Would require Maya to mislead a third party
@dataclass
class ScreeningResult:
"""The output of Maya's intake screener for a single project inquiry."""
client_name: str
recommendation: str # "PROCEED", "REVIEW", or "DECLINE"
summary: str # One-sentence summary for Maya's notes
positive_signals: list = field(default_factory=list) # What looks good
concerns: list = field(default_factory=list) # What warrants caution
disqualifiers: list = field(default_factory=list) # Hard no-go reasons
# ---------------------------------------------------------------------------
# Core Screening Function
# ---------------------------------------------------------------------------
def screen_project(inquiry: ProjectInquiry) -> ScreeningResult:
"""
Apply Maya's project selection criteria to a project inquiry.
Evaluation order:
1. Absolute disqualifiers (any one of these = DECLINE immediately)
2. Budget check
3. Project type check
4. Client fit factors (positive signals and red flags)
5. Final recommendation based on accumulated signals
Args:
inquiry: A ProjectInquiry dataclass.
Returns:
A ScreeningResult with recommendation and full signal inventory.
"""
result = ScreeningResult(client_name=inquiry.client_name, recommendation="", summary="")
disqualifiers = []
concerns = []
positive_signals = []
# -----------------------------------------------------------------------
# STAGE 1: Absolute Disqualifiers
# These are non-negotiable. Any one of them ends the screening immediately.
# -----------------------------------------------------------------------
# Hard exclusion: industry
if inquiry.industry.lower() in EXCLUDED_INDUSTRIES:
disqualifiers.append(
f"Industry '{inquiry.industry}' is on Maya's excluded industry list."
)
# Hard exclusion: misrepresentation request
if inquiry.requests_misrepresentation:
disqualifiers.append(
"Project would require Maya to misrepresent information to a third party. "
"Non-negotiable exclusion."
)
# Hard exclusion: no virtual work option
if not inquiry.requires_only_virtual:
disqualifiers.append(
"Client requires exclusively in-person work. Maya's practice is remote-first "
"and does not accommodate this model."
)
# If any disqualifiers found — stop here
if disqualifiers:
result.disqualifiers = disqualifiers
result.recommendation = "DECLINE"
result.summary = (
f"Hard disqualifier(s) found. Decline {inquiry.client_name} gracefully "
"without extensive explanation."
)
return result
# -----------------------------------------------------------------------
# STAGE 2: Budget Assessment
# -----------------------------------------------------------------------
if inquiry.budget_usd == 0 or not inquiry.has_defined_budget:
concerns.append(
"Budget not disclosed or undefined. Undefined budgets often lead to scope creep "
"and difficult renegotiations."
)
elif inquiry.budget_usd < BUDGET_MINIMUM:
disqualifiers.append(
f"Budget (${inquiry.budget_usd:,.0f}) is below Maya's minimum engagement "
f"threshold (${BUDGET_MINIMUM:,.0f})."
)
elif inquiry.budget_usd <= BUDGET_SWEET_SPOT_MAX:
positive_signals.append(
f"Budget (${inquiry.budget_usd:,.0f}) is in the ideal range "
f"(${BUDGET_MINIMUM:,.0f}–${BUDGET_SWEET_SPOT_MAX:,.0f})."
)
elif inquiry.budget_usd <= BUDGET_STRETCH_MAX:
positive_signals.append(
f"Budget (${inquiry.budget_usd:,.0f}) is in the acceptable range — "
"confirm team/resource expectations before proceeding."
)
else:
concerns.append(
f"Budget (${inquiry.budget_usd:,.0f}) exceeds the typical engagement size. "
"Verify scope and team structure expectations."
)
# Check again for budget disqualifier after the detailed assessment
if disqualifiers:
result.disqualifiers = disqualifiers
result.recommendation = "DECLINE"
result.summary = f"Budget disqualifier. Decline {inquiry.client_name}."
return result
# -----------------------------------------------------------------------
# STAGE 3: Project Type Fit
# -----------------------------------------------------------------------
normalized_type = inquiry.project_type.lower().strip()
# Check for exact match or partial match in accepted types
type_matched = normalized_type in ACCEPTED_PROJECT_TYPES or any(
accepted_type in normalized_type
for accepted_type in ACCEPTED_PROJECT_TYPES
)
if type_matched:
positive_signals.append(
f"Project type '{inquiry.project_type}' is within Maya's core competencies."
)
else:
disqualifiers.append(
f"Project type '{inquiry.project_type}' is outside Maya's service offerings. "
"Decline and refer to an appropriate specialist."
)
result.disqualifiers = disqualifiers
result.recommendation = "DECLINE"
result.summary = (
f"Project type mismatch. Decline {inquiry.client_name} and offer a referral."
)
return result
# -----------------------------------------------------------------------
# STAGE 4: Client Fit — Positive Signals
# -----------------------------------------------------------------------
if inquiry.has_clear_decision_maker:
positive_signals.append("Clear decision-maker identified — reduces approval delays.")
if inquiry.has_consultant_experience:
positive_signals.append(
"Client has prior consulting experience — likely understands engagement norms."
)
if inquiry.has_defined_timeline:
positive_signals.append("Project has a defined timeline — indicates planning maturity.")
# -----------------------------------------------------------------------
# STAGE 5: Red Flags — Risk Signals
# -----------------------------------------------------------------------
if inquiry.wants_immediate_start_no_scope:
concerns.append(
"'Start immediately' with undefined scope: high risk of scope creep "
"and unmet expectations."
)
if inquiry.previous_consultants_disengaged == 1:
concerns.append(
"One prior consultant disengaged from this project. Worth asking why."
)
elif inquiry.previous_consultants_disengaged >= 2:
concerns.append(
f"{inquiry.previous_consultants_disengaged} prior consultants have disengaged. "
"Strong indicator of a difficult client dynamic or chronic scope problem."
)
if inquiry.client_contact_lacks_authority:
concerns.append(
"Contact cannot approve final deliverable. Maya will need to navigate additional "
"stakeholders she may never meet. Risk of misaligned expectations."
)
# Timeline realism check
if inquiry.has_defined_timeline and inquiry.timeline_weeks_realistic > 0:
timeline_ratio = inquiry.timeline_weeks_stated / inquiry.timeline_weeks_realistic
if timeline_ratio < 0.5:
concerns.append(
f"Client's stated timeline ({inquiry.timeline_weeks_stated} weeks) is less than "
f"half of what Maya estimates as realistic ({inquiry.timeline_weeks_realistic} weeks). "
"High risk of timeline conflict."
)
elif timeline_ratio < 0.75:
concerns.append(
f"Stated timeline ({inquiry.timeline_weeks_stated} weeks) is tight against "
f"Maya's estimate ({inquiry.timeline_weeks_realistic} weeks). "
"Should clarify expectations on the call."
)
else:
positive_signals.append(
f"Client timeline ({inquiry.timeline_weeks_stated} weeks) is reasonably aligned "
f"with Maya's estimate ({inquiry.timeline_weeks_realistic} weeks)."
)
# -----------------------------------------------------------------------
# STAGE 6: Final Recommendation
# -----------------------------------------------------------------------
red_flag_count = len(concerns)
positive_count = len(positive_signals)
# Decision logic: balance positive signals against concerns
if red_flag_count >= RED_FLAG_DECLINE_THRESHOLD:
result.recommendation = "DECLINE"
result.summary = (
f"{inquiry.client_name}: {red_flag_count} concerns identified. "
"Risk profile too high. Decline gracefully."
)
elif red_flag_count >= 1 or positive_count < 2:
result.recommendation = "REVIEW"
result.summary = (
f"{inquiry.client_name}: Mixed signals ({positive_count} positive, "
f"{red_flag_count} concerns). Schedule a call, but review concerns first."
)
else:
result.recommendation = "PROCEED"
result.summary = (
f"{inquiry.client_name}: Strong fit ({positive_count} positive signals, "
f"{red_flag_count} concerns). Schedule exploratory call."
)
result.positive_signals = positive_signals
result.concerns = concerns
result.disqualifiers = disqualifiers
return result
# ---------------------------------------------------------------------------
# Display
# ---------------------------------------------------------------------------
def print_screening_result(result: ScreeningResult) -> None:
"""Display a formatted screening result."""
rec_display = {
"PROCEED": "[PROCEED] Schedule the call",
"REVIEW": "[REVIEW] Proceed with caution",
"DECLINE": "[DECLINE] Pass on this project",
}.get(result.recommendation, result.recommendation)
print(f" Client : {result.client_name}")
print(f" Recommendation : {rec_display}")
print(f" Summary : {result.summary}")
if result.disqualifiers:
print(" Disqualifiers:")
for item in result.disqualifiers:
print(f" ✗ {item}")
if result.positive_signals:
print(" Positive signals:")
for item in result.positive_signals:
print(f" + {item}")
if result.concerns:
print(" Concerns:")
for item in result.concerns:
print(f" ~ {item}")
print()
# ---------------------------------------------------------------------------
# Demo
# ---------------------------------------------------------------------------
if __name__ == "__main__":
print("\nMaya Reyes Consulting — Project Intake Screener")
print("=" * 65)
print()
inquiries = [
# Ideal project: right budget, right type, great client fit
ProjectInquiry(
client_name="Clearwater Health Systems",
project_type="process improvement",
industry="healthcare",
budget_usd=18_000,
timeline_weeks_stated=12,
timeline_weeks_realistic=12,
has_clear_decision_maker=True,
has_defined_budget=True,
has_consultant_experience=True,
has_defined_timeline=True,
requires_only_virtual=True,
wants_immediate_start_no_scope=False,
previous_consultants_disengaged=0,
client_contact_lacks_authority=False,
requests_misrepresentation=False,
),
# Red-flag project: too many warning signs
ProjectInquiry(
client_name="FastGrowth Ventures",
project_type="organizational assessment",
industry="technology",
budget_usd=22_000,
timeline_weeks_stated=4,
timeline_weeks_realistic=12, # Stated is 1/3 of realistic
has_clear_decision_maker=False,
has_defined_budget=True,
has_consultant_experience=False,
has_defined_timeline=True,
requires_only_virtual=True,
wants_immediate_start_no_scope=True,
previous_consultants_disengaged=2,
client_contact_lacks_authority=True,
requests_misrepresentation=False,
),
# Hard exclusion: wrong industry
ProjectInquiry(
client_name="Titan Defense Contractors",
project_type="process improvement",
industry="weapons manufacturing",
budget_usd=45_000,
timeline_weeks_stated=20,
timeline_weeks_realistic=18,
has_clear_decision_maker=True,
has_defined_budget=True,
has_consultant_experience=True,
has_defined_timeline=True,
requires_only_virtual=True,
wants_immediate_start_no_scope=False,
previous_consultants_disengaged=0,
client_contact_lacks_authority=False,
requests_misrepresentation=False,
),
# Mixed signals: good project, one concern
ProjectInquiry(
client_name="Meridian Logistics Group",
project_type="workflow optimization",
industry="logistics",
budget_usd=15_000,
timeline_weeks_stated=10,
timeline_weeks_realistic=12,
has_clear_decision_maker=True,
has_defined_budget=True,
has_consultant_experience=False,
has_defined_timeline=True,
requires_only_virtual=True,
wants_immediate_start_no_scope=False,
previous_consultants_disengaged=1,
client_contact_lacks_authority=False,
requests_misrepresentation=False,
),
# Budget too small
ProjectInquiry(
client_name="Two-Person Startup",
project_type="business analysis",
industry="technology",
budget_usd=3_500,
timeline_weeks_stated=6,
timeline_weeks_realistic=6,
has_clear_decision_maker=True,
has_defined_budget=True,
has_consultant_experience=False,
has_defined_timeline=True,
requires_only_virtual=True,
wants_immediate_start_no_scope=False,
previous_consultants_disengaged=0,
client_contact_lacks_authority=False,
requests_misrepresentation=False,
),
]
for inquiry in inquiries:
print("-" * 65)
result = screen_project(inquiry)
print_screening_result(result)
Step 3: Maya Reviews the Results
Maya runs the script and reviews each recommendation against her gut feel:
- Clearwater Health Systems → PROCEED. "Yes, exactly. That's a great project."
- FastGrowth Ventures → DECLINE. "Honestly, I already knew that one was trouble."
- Titan Defense Contractors → DECLINE. "Correct. No discussion needed."
- Meridian Logistics Group → REVIEW. "Right call. I'd want to understand that previous consultant situation."
- Two-Person Startup → DECLINE. "Sad but true. The economics just don't work."
She is satisfied. The screener does not replace her judgment — it organizes it.
Step 4: What Maya Learned (And What You Can Learn From Maya)
Your Business Judgment Is Encodable
Maya's most important insight: the knowledge she had built over six years of consulting was already structured as conditional logic. She just had not written it down. The act of writing it in Python forced her to be explicit about thresholds she had previously left vague ("not enough budget" → budget < 8000).
This is true for almost any professional domain. Underwriters encode risk assessment. Sales managers encode deal qualification. HR professionals encode compensation banding. If you can write it in "if this, then that" language, you can write it in Python.
Encoding Your Rules Has Compounding Benefits
Once Maya's screening logic was in code, she got several things for free:
- Consistency. The same criteria apply to every inquiry, regardless of whether she is tired, busy, or excited about the client's logo.
- Documentation. The
ACCEPTED_PROJECT_TYPESset and the constants are a living policy document. When her criteria change, she updates the code, and the change is tracked. - Speed. A pre-screening that took twenty minutes of mental energy now takes thirty seconds.
- Communication. When she needs to explain to a prospect why she is declining, she can reference the specific criterion, not just say "it's not the right fit."
The Screener Is a Tool, Not a Boss
Maya is explicit with herself: the screener gives a recommendation. She can override it. The point is not to remove her judgment — it is to make her judgment faster, more consistent, and less subject to the bad days when she might say yes to something she knows in her gut is wrong.
This is the correct relationship to have with any decision-support system you build with code. The code handles the clear cases. The human handles the edge cases. That division of labor is what makes automation valuable without making it dangerous.
Extending the Screener
Consider how you might extend Maya's screener for additional scenarios:
-
Scoring model: Instead of discrete signals, assign point values to each factor (e.g.,
has_clear_decision_makeris worth +2 points,wants_immediate_start_no_scopeis worth −3) and use a total score to drive the recommendation. -
Weighted red flags: Not all red flags are equal. A misrepresentation request is more serious than a tight timeline. Modify the code so some flags count double.
-
Referral logic: When Maya declines because the project type is outside her expertise, she wants to recommend a specific colleague. Extend the
ScreeningResultto include an optionalreferral_suggestionfield. -
Report generation: Instead of printing to the console, have the screener write a markdown summary to a file so Maya can review all inquiries from a given week in one document.