Artifex/agents/venture_discovery.py
2026-08-15 20:56:56 +07:00

273 lines
34 KiB
Python

from __future__ import annotations
import hashlib
import json
import re
from decimal import Decimal
from typing import Any
from django.utils import timezone
from control_plane.events.bus import EventBus
from control_plane.ventures.models import CapabilityPriority, CapabilityStatus, CompanyBoardReview, CompanyCapabilityRequirement, CompanyMandate, CompanyProposal, CompanyProposalStatus, ICDecision, ICDecisionType, ICDiligence, ICQuestion, ICResponse, PortfolioCapabilityGap, VentureArtifact, VentureThesis
from model_router.router import ModelCapability, ModelRequestContract, ModelRouter
PITCH_SECTIONS = ["Company name", "One-line thesis", "Problem", "ICP", "Why now", "Product / service", "Business model", "Pricing", "Route to first customer", "Validation plan", "$50 capital allocation proposal", "Time to first dollar", "Path to $500 net cash", "Competition", "Differentiation", "Build requirements", "Distribution requirements", "Risks", "What would falsify the thesis", "Confidence"]
SCORE_DIMENSIONS = ["Demand evidence", "Time to first dollar", "Capital efficiency", "Validation cost", "Gross margin", "Distribution difficulty", "Build complexity", "Defensibility", "Market size", "Competition", "Risk", "Probability of reaching $500"]
class VentureDiscoveryService:
def __init__(self, router: ModelRouter | None = None, bus: EventBus | None = None, web_research_available: bool = False) -> None:
self.router = router
self.bus = bus or EventBus()
self.web_research_available = web_research_available
def create_v0_mandate(self) -> CompanyMandate:
mandate = CompanyMandate.objects.create(
objective="Design a business that could plausibly turn at most $50 of external validation capital into $500 net new cash.",
constraints={"external_validation_capital_max": 50, "target_net_new_cash": 500, "target_window_days": 30, "no_equity_raise": True, "no_debt": True, "no_illegal_or_deceptive_activity": True, "no_spam": True, "no_fake_traction": True, "no_fabricated_customer_evidence": True, "no_real_spend_in_v0": True, "no_real_customer_outreach_in_v0": True, "existing_artifex_compute_sunk_available": True},
optimization_targets=["time to first dollar", "capital efficiency", "real demand evidence", "high gross margin", "realistic execution", "low external dependency", "ability to validate cheaply"],
metadata={"milestone": "VENTURE_DISCOVERY_V0", "spend_authorized": False, "customer_outreach_authorized": False},
)
self._artifact(None, mandate, "VENTURE_MANDATE", "Venture Discovery V0 Mandate", {"objective": mandate.objective, "constraints": mandate.constraints, "optimization_targets": mandate.optimization_targets}, self._readable_mandate(mandate), "venture_discovery")
return mandate
def generate_single_company(self, mandate: CompanyMandate, *, graph_run=None) -> CompanyProposal:
payload, source = self._company_payload(mandate)
pitch = self._pitch(payload, fallback=source != "sol")
confidence = float(payload.get("confidence", 0.55))
evidence = self._as_list(payload.get("market_evidence", []))
if not self.web_research_available:
evidence.append({"type": "capability_gap", "source": "internal", "summary": "Public web market research is not configured; demand/competitor evidence is unverified.", "fallback_evidence": True})
confidence = min(confidence, 0.58)
thesis = VentureThesis.objects.create(mandate=mandate, title=str(payload["title"]), thesis=str(payload["one_line_thesis"]), similarity_fingerprint=self._fingerprint(payload), metadata={"source": source, "exactly_one_company_generated": True})
proposal = CompanyProposal.objects.create(
mandate=mandate,
thesis=thesis,
title=str(payload["title"]),
description=str(payload["description"]),
problem=str(payload["problem"]),
target_customer=str(payload["target_customer"]),
proposed_solution=str(payload["proposed_solution"]),
business_model=str(payload["business_model"]),
pricing_hypothesis=str(payload["pricing_hypothesis"]),
acquisition_strategy=str(payload["acquisition_strategy"]),
validation_plan=str(payload["validation_plan"]),
capital_requested=self._money(payload.get("capital_requested", "50")),
time_to_first_dollar_estimate=str(payload["time_to_first_dollar_estimate"]),
expected_margin=str(payload["expected_margin"]),
build_complexity=str(payload["build_complexity"]),
market_evidence=evidence,
differentiation=str(payload["differentiation"]),
major_risks=self._as_list(payload["major_risks"]),
confidence=confidence,
status=CompanyProposalStatus.SUBMITTED,
pitch=pitch,
metadata={"generation_source": source, "fallback_evidence": source != "sol", "web_research_available": self.web_research_available, "real_spend": 0, "real_customer_outreach": False},
)
self._artifact(proposal, mandate, "STANDARDIZED_COMPANY_PITCH", "Standardized Company Pitch", pitch, self.readable_pitch(pitch), "Company Brain/Sol" if source == "sol" else "deterministic_fallback", graph_run=graph_run)
self.bus.publish("VENTURE_COMPANY_PROPOSED", payload={"proposal_id": str(proposal.id), "source": source})
return proposal
def board_review(self, proposal: CompanyProposal, *, graph_run=None) -> CompanyBoardReview:
observations = {
"CEO": ["Mandate fit is strongest if validation sells a narrow paid audit before product build.", "Keep the first dollar path service-led, not SaaS-led."],
"CTO": ["Build can use existing Artifex analysis, reporting, and frontend capabilities.", "Avoid integrations until willingness-to-pay evidence exists."],
"CFO": ["$50 cap is adequate only for lightweight landing page/listing tests, not paid acquisition learning.", "High margin is plausible because delivery is mostly labor/compute already available."],
"CRO": ["Founder/operator communities are reachable manually, but V0 cannot contact customers.", "Pricing must start as a paid diagnostic to avoid long SaaS evaluation cycles."],
"Independent Director": ["Demand evidence is weak without public research or customer conversations.", "The company must prove urgency before building automation."],
}
weaknesses = ["No customer outreach or paid test has occurred.", "Public web research is unavailable, so market evidence remains partial.", "First customers may require trust and examples before paying."]
revisions = ["Frame offer as a productized validation audit with optional Artifex-assisted build plan.", "Make falsification criteria explicit before spend."]
pitch = dict(proposal.pitch)
pitch["Product / service"] = "Productized validation and launch-readiness audit for AI-assisted microbusiness ideas, delivered as a concise paid report before any software build."
pitch["What would falsify the thesis"] = "Fewer than 5 credible target-customer responses or zero willingness-to-pay signals after a compliant validation test."
proposal.pitch = pitch
proposal.metadata = {**proposal.metadata, "board_revised_pitch": True}
proposal.save(update_fields=["pitch", "metadata", "updated_at"])
review = CompanyBoardReview.objects.create(proposal=proposal, observations=observations, strengths=["Service-led revenue path can precede product build.", "Uses current Artifex planning, engineering, and review capabilities.", "Small validation budget aligns with a narrow paid offer."], weaknesses=weaknesses, key_assumptions=["Target customers feel enough urgency to pay for validation clarity.", "Manual outbound or community posting can generate credible responses without spam.", "Artifex can produce a differentiated audit faster than generic consultants."], required_revisions=revisions, recommendation="PROCEED_TO_IC_WITH_REVISIONS", revised_pitch=pitch, metadata={"roles": list(observations), "company_does_not_grade_itself": True})
self._artifact(proposal, proposal.mandate, "COMPANY_BOARD_REVIEW", "Company Board Review", {"observations": observations, "strengths": review.strengths, "weaknesses": weaknesses, "required_revisions": revisions, "recommendation": review.recommendation, "revised_pitch": pitch}, self._readable_board(review), "Company Board", graph_run=graph_run)
return review
def start_ic_diligence(self, proposal: CompanyProposal, *, graph_run=None) -> ICDiligence:
proposal.status = CompanyProposalStatus.UNDER_DILIGENCE
proposal.save(update_fields=["status", "updated_at"])
diligence = ICDiligence.objects.create(proposal=proposal, status="FIRST_PASS", rounds=["Initial Pitch", "Diligence Round 1", "Final Challenge", "Decision"], metadata={"bounded_rounds": True, "self_grading": False})
self._artifact(proposal, proposal.mandate, "IC_FIRST_PASS", "IC First-Pass Review", {"status": diligence.status, "initial_concerns": ["Demand evidence is unverified.", "Distribution assumptions need evidence.", "Need validation gate before any spend."]}, "IC first pass: proceed to evidence-seeking questions; do not score final decision yet.", "Independent IC", graph_run=graph_run)
return diligence
def generate_ic_questions(self, diligence: ICDiligence, *, graph_run=None) -> list[ICQuestion]:
pitch = diligence.proposal.pitch
raw_questions = self._questions_from_pitch(pitch)
questions = [ICQuestion.objects.create(diligence=diligence, question=item["question"], category=item["category"], evidence_required=True) for item in raw_questions[:8]]
self._artifact(diligence.proposal, diligence.proposal.mandate, "IC_QUESTIONS", "IC Diligence Questions", {"questions": [q.question for q in questions]}, "\n".join(f"- {q.question}" for q in questions), "Independent IC", graph_run=graph_run)
return questions
def answer_questions(self, diligence: ICDiligence, *, graph_run=None) -> list[ICResponse]:
responses = []
for question in diligence.questions.all():
answer = self._answer_question(question)
responses.append(ICResponse.objects.create(question=question, answer=answer["answer"], evidence=answer["evidence"], uncertainty=answer["uncertainty"], pitch_changes=answer.get("pitch_changes", {}), metadata={"no_customer_outreach": True, "no_spend": True}))
diligence.status = "COMPANY_RESPONSE"
diligence.save(update_fields=["status", "updated_at"])
self._artifact(diligence.proposal, diligence.proposal.mandate, "IC_RESPONSES", "Company Responses to IC", {"responses": [{"question": r.question.question, "answer": r.answer, "evidence": r.evidence, "uncertainty": r.uncertainty, "pitch_changes": r.pitch_changes} for r in responses]}, self._readable_responses(responses), "Company reasoning roles", graph_run=graph_run)
return responses
def red_team(self, diligence: ICDiligence, *, graph_run=None) -> dict[str, object]:
challenge = {"concerns": ["The offer may be perceived as generic consulting unless anchored to a painful, immediate decision.", "Without public research or customer contact, demand remains a hypothesis.", "Manual distribution could fail if the target audience distrusts AI-generated audits."], "required_final_response": ["Narrow ICP further.", "Specify willingness-to-pay proof.", "Define hard kill criteria."], "recommendation": "continue_to_final_response"}
diligence.red_team_challenge = challenge
diligence.status = "FINAL_CHALLENGE"
diligence.save(update_fields=["red_team_challenge", "status", "updated_at"])
self._artifact(diligence.proposal, diligence.proposal.mandate, "IC_RED_TEAM", "IC Red-Team Challenge", challenge, "Red-team concerns:\n" + "\n".join(f"- {c}" for c in challenge["concerns"]), "Independent IC Red Team", graph_run=graph_run)
return challenge
def final_company_response(self, diligence: ICDiligence, *, graph_run=None) -> dict[str, object]:
response = {"narrowed_icp": "Solo technical founders and small service operators deciding whether to spend time building an AI-assisted microbusiness.", "revised_validation_gate": "Before any spend, collect 5 credible target-customer responses or 1 explicit willingness-to-pay signal through compliant non-spam channels.", "kill_criteria": ["No credible responses after 10 targeted, compliant conversations/posts once outreach is approved.", "No willingness-to-pay signal at $49-$99.", "Customers only want free advice, not a paid report."], "pitch_changes": {"ICP": "Narrowed to solo technical founders and small service operators.", "Validation plan": "Gate spend behind credible response/willingness-to-pay evidence."}}
diligence.final_response = response
diligence.status = "FINAL_RESPONSE"
diligence.save(update_fields=["final_response", "status", "updated_at"])
self._artifact(diligence.proposal, diligence.proposal.mandate, "IC_FINAL_RESPONSE", "Final Company Response", response, json.dumps(response, indent=2), "Company reasoning roles", graph_run=graph_run)
return response
def score_and_decide(self, diligence: ICDiligence, *, graph_run=None) -> ICDecision:
scores = {"Demand evidence": 38, "Time to first dollar": 78, "Capital efficiency": 84, "Validation cost": 82, "Gross margin": 76, "Distribution difficulty": 48, "Build complexity": 72, "Defensibility": 34, "Market size": 58, "Competition": 44, "Risk": 42, "Probability of reaching $500": 45}
if not self.web_research_available:
scores["Demand evidence"] = 30
scores["Competition"] = 36
scores["Probability of reaching $500"] = 40
composite = round(sum(scores.values()) / len(scores), 1)
decision_type = ICDecisionType.CONDITIONAL_FUND if composite >= 55 else ICDecisionType.REVISE_AND_RESUBMIT
decision = ICDecision.objects.create(diligence=diligence, decision=decision_type, component_scores=scores, composite_score=composite, probability_500_within_30_days=scores["Probability of reaching $500"], initial_tranche=Decimal("10.00") if decision_type == ICDecisionType.CONDITIONAL_FUND else None, validation_condition="Obtain 5 credible target-customer responses or 1 explicit willingness-to-pay signal before any build or further spend.", evidence_required=["response transcripts or public thread URLs", "proof of willingness-to-pay signal", "no-spam/no-fabrication compliance note"], recommended_allocation={"initial_tranche": 10, "remaining_reserved": 40, "no_spend_in_v0": True}, kill_criteria=diligence.final_response.get("kill_criteria", []), next_decision_point="After validation evidence is collected and before any real spend or customer delivery.", metadata={"decision_vocabulary": [item.value for item in ICDecisionType], "no_actual_funding": True})
diligence.status = "DECISION"
diligence.save(update_fields=["status", "updated_at"])
diligence.proposal.status = CompanyProposalStatus.FUNDED_RECOMMENDED if decision.decision == ICDecisionType.CONDITIONAL_FUND else CompanyProposalStatus.REVISE
diligence.proposal.save(update_fields=["status", "updated_at"])
self._artifact(diligence.proposal, diligence.proposal.mandate, "IC_FINAL_SCORE", "IC Final Scoring and Decision", {"scores": scores, "decision": decision.decision, "composite_score": composite, "conditional_funding": {"initial_tranche": str(decision.initial_tranche), "condition": decision.validation_condition}}, self._readable_score(decision), "Independent IC", graph_run=graph_run)
return decision
def capability_analysis(self, proposal: CompanyProposal, *, graph_run=None) -> PortfolioCapabilityGap:
requirements = self._capability_requirements(proposal)
for item in requirements:
CompanyCapabilityRequirement.objects.create(proposal=proposal, **item)
available = [r["category"] for r in requirements if r["status"] == CapabilityStatus.AVAILABLE]
partial = [r["category"] for r in requirements if r["status"] == CapabilityStatus.PARTIAL]
missing = [r["category"] for r in requirements if r["status"] == CapabilityStatus.MISSING]
ranked = [{"category": r["category"], "priority": r["priority"], "rationale": r["rationale"]} for r in requirements if r["status"] == CapabilityStatus.MISSING]
priority_order = {CapabilityPriority.BEFORE_VALIDATION: 0, CapabilityPriority.BEFORE_FIRST_CUSTOMER: 1, CapabilityPriority.BEFORE_SCALING: 2}
ranked.sort(key=lambda item: priority_order[item["priority"]])
report = self._readable_capability_gap(available, partial, missing, ranked)
gap = PortfolioCapabilityGap.objects.create(proposal=proposal, available=available, partial=partial, missing=missing, ranked_missing=ranked, report=report, metadata={"web_market_research_status": "MISSING" if not self.web_research_available else "PARTIAL"})
self._artifact(proposal, proposal.mandate, "CAPABILITY_GAP_REPORT", "Capability Gap Report", {"available": available, "partial": partial, "missing": missing, "ranked_missing": ranked}, report, "Venture Discovery Capability Analysis", graph_run=graph_run)
return gap
def produce_investment_memo(self, diligence: ICDiligence, gap: PortfolioCapabilityGap, *, graph_run=None) -> VentureArtifact:
decision = diligence.decision
proposal = diligence.proposal
memo = {"Company": proposal.title, "Thesis": proposal.pitch["One-line thesis"], "Mandate": proposal.mandate.objective, "Requested capital": str(proposal.capital_requested), "Recommended allocation": decision.recommended_allocation, "IC decision": decision.decision, "Key metrics": {"P($500 within 30 days)": decision.probability_500_within_30_days, "estimated time to first dollar": proposal.time_to_first_dollar_estimate, "expected gross margin": proposal.expected_margin, "validation cost": "$10 initial tranche; $50 maximum after approval", "build effort": proposal.build_complexity, "distribution difficulty": decision.component_scores["Distribution difficulty"]}, "Why it may work": ["Revenue path starts with a paid diagnostic, not a full SaaS build.", "Artifex has planning, engineering, frontend, review, graph, and agent-control capabilities already.", "Validation budget can be gated behind evidence."], "Why it may fail": diligence.red_team_challenge.get("concerns", []), "Diligence questions": [q.question for q in diligence.questions.all()], "Company responses": [r.answer for r in ICResponse.objects.filter(question__diligence=diligence)], "Red-team concerns": diligence.red_team_challenge.get("concerns", []), "IC scoring": decision.component_scores, "Capital recommendation": decision.recommended_allocation, "Validation gates": [decision.validation_condition], "Kill criteria": decision.kill_criteria, "Next decision point": decision.next_decision_point, "Capability gap": {"available": gap.available, "partial": gap.partial, "missing": gap.missing}}
return self._artifact(proposal, proposal.mandate, "FINAL_INVESTMENT_MEMO", "Final IC Investment Memo", memo, self._readable_memo(memo), "Independent IC", graph_run=graph_run)
def request_human_approval(self, decision: ICDecision, action: str) -> ICDecision:
if action not in {"approve_for_validation", "reject", "request_more_diligence"}:
raise ValueError("Unsupported venture approval action")
decision.metadata = {**decision.metadata, "human_approval_action": action, "real_spend_still_blocked": True}
decision.save(update_fields=["metadata", "updated_at"])
return decision
def _company_payload(self, mandate: CompanyMandate) -> tuple[dict[str, Any], str]:
if self.router is not None:
try:
response = self.router.complete(ModelRequestContract(purpose=ModelCapability.PLANNING, model_hint="sol", prompt="Generate exactly ONE startup idea for Venture Discovery V0. Return a single JSON object, not a list. Respect no spend and no outreach in V0. Include title, one_line_thesis, description, problem, target_customer, proposed_solution, business_model, pricing_hypothesis, acquisition_strategy, validation_plan, capital_requested, time_to_first_dollar_estimate, expected_margin, build_complexity, market_evidence, differentiation, major_risks, confidence. Mandate: " + json.dumps({"objective": mandate.objective, "constraints": mandate.constraints, "optimization_targets": mandate.optimization_targets})))
parsed = json.loads(response.content)
if isinstance(parsed, dict) and parsed.get("title"):
return self._normalize_payload(parsed), "sol"
except Exception:
pass
payload = self._fallback_company_payload()
payload["market_evidence"] = [{"type": "fallback_hypothesis", "source": "deterministic_fallback", "summary": "No Sol/web research was used; this is an internal hypothesis for test/resilience only.", "fallback_evidence": True}]
payload["confidence"] = 0.52
return payload, "deterministic_fallback"
def _fallback_company_payload(self) -> dict[str, Any]:
return {"title": "LaunchLens", "one_line_thesis": "A productized validation audit helps solo builders decide whether an AI-enabled microbusiness is worth pursuing before they spend weeks building.", "description": "LaunchLens sells a concise validation and launch-readiness report for one microbusiness idea.", "problem": "Solo technical founders often overbuild AI products before proving willingness to pay.", "target_customer": "Solo technical founders and small service operators considering an AI-assisted microbusiness.", "proposed_solution": "A fixed-scope paid audit that evaluates ICP, first-dollar route, validation gates, build plan, risks, and Artifex capability gaps.", "business_model": "Productized service first, with optional later software tooling if demand is proven.", "pricing_hypothesis": "$49-$99 per audit, with a higher-touch $250 implementation planning upsell after validation.", "acquisition_strategy": "Compliant founder-community posts, personal network asks, and content showing anonymized example audits after outreach is approved.", "validation_plan": "Before any build, seek 5 credible target-customer responses or 1 willingness-to-pay signal through compliant channels once approved.", "capital_requested": "50", "time_to_first_dollar_estimate": "3-10 days after outreach is approved", "expected_margin": "70-85% gross margin after manual delivery time", "build_complexity": "LOW", "market_evidence": [], "differentiation": "Combines venture IC-style diligence with Artifex's software/agent execution awareness and explicit capability-gap reporting.", "major_risks": ["Demand may be consulting-like and hard to differentiate.", "Manual distribution may not produce urgent buyers.", "No V0 customer evidence exists yet."], "confidence": 0.52}
def _normalize_payload(self, payload: dict[str, Any]) -> dict[str, Any]:
fallback = self._fallback_company_payload()
normalized = {key: payload.get(key, value) for key, value in fallback.items()}
normalized["market_evidence"] = self._as_list(normalized.get("market_evidence"))
normalized["major_risks"] = self._as_list(normalized.get("major_risks"))
return normalized
def _as_list(self, value: Any) -> list[Any]:
if isinstance(value, list):
return value
if value in (None, ""):
return []
return [value]
def _money(self, value: Any) -> Decimal:
match = re.search(r"\d+(?:\.\d+)?", str(value))
if match is None:
return Decimal("50")
return min(Decimal(match.group(0)), Decimal("50"))
def _pitch(self, payload: dict[str, Any], *, fallback: bool) -> dict[str, Any]:
pitch = {"Company name": payload["title"], "One-line thesis": payload["one_line_thesis"], "Problem": payload["problem"], "ICP": payload["target_customer"], "Why now": "AI tooling lowers build cost, increasing the risk that founders overbuild before validating demand.", "Product / service": payload["proposed_solution"], "Business model": payload["business_model"], "Pricing": payload["pricing_hypothesis"], "Route to first customer": payload["acquisition_strategy"], "Validation plan": payload["validation_plan"], "$50 capital allocation proposal": {"initial": "$10 only after approval", "reserved": "$40 held until evidence gate", "v0_spend": "$0"}, "Time to first dollar": payload["time_to_first_dollar_estimate"], "Path to $500 net cash": "Sell 6-10 fixed-scope audits at $49-$99 while keeping delivery manual and using sunk Artifex compute.", "Competition": "Generic startup consultants, founder communities, AI business idea tools, and DIY validation templates. Public competitor research is unverified unless web research is configured.", "Differentiation": payload["differentiation"], "Build requirements": ["report template", "intake form", "manual analysis workflow", "optional landing page after validation approval"], "Distribution requirements": ["compliant outreach plan", "community/content channels", "CRM-lite tracking before first customers"], "Risks": payload["major_risks"], "What would falsify the thesis": "No willingness-to-pay signal at $49-$99 or fewer than 5 credible target-customer responses after approved compliant validation.", "Confidence": payload["confidence"], "Evidence caveat": "Deterministic fallback evidence only; not equivalent to Sol or public web research." if fallback else "Generated by Sol; public web research still only included if sources are present."}
return pitch
def _questions_from_pitch(self, pitch: dict[str, Any]) -> list[dict[str, str]]:
return [{"category": "demand", "question": f"What evidence supports demand for {pitch['Company name']} among the stated ICP?"}, {"category": "urgency", "question": "Why will this customer pay now instead of using free templates or advice?"}, {"category": "distribution", "question": "How do you reach the first 10 customers without spam or fake traction?"}, {"category": "validation", "question": "Can this be validated before building the full product?"}, {"category": "business_model", "question": "Why a productized service first instead of SaaS?"}, {"category": "falsification", "question": "What would falsify the thesis within the $50 and 30-day mandate?"}, {"category": "economics", "question": "What happens if acquisition cost or manual delivery time is 3x the estimate?"}, {"category": "competition", "question": "What is the main competitive threat and why is this worth funding over selling an existing Artifex capability?"}]
def _answer_question(self, question: ICQuestion) -> dict[str, Any]:
evidence = [{"source": "internal_reasoning", "summary": "No customer outreach, spend, or fabricated evidence used.", "fallback_evidence": True}]
if question.category in {"demand", "competition"} and not self.web_research_available:
evidence.append({"source": "capability_gap", "summary": "Public web research unavailable; demand/competition claims remain uncertain.", "fallback_evidence": True})
answers = {"demand": "Demand is not proven. The strongest V0 claim is that the problem is plausible and cheap to test, not that demand exists.", "urgency": "The buyer pays only if the report saves them build time or prevents wasted spend; urgency is weakest before a concrete launch decision.", "distribution": "After approval, use targeted compliant posts/conversations and track responses manually; V0 performs no outreach.", "validation": "Yes. The paid diagnostic can be validated with responses and willingness-to-pay before software build.", "business_model": "Service first reduces build risk and can reach first cash faster than SaaS; software should follow only if repeated demand appears.", "falsification": "Failure to collect credible responses or willingness-to-pay within the mandate falsifies near-term viability.", "economics": "If acquisition or delivery is 3x harder, the company should stop or raise price before building tooling.", "competition": "Main threat is generic consulting/free templates. The reason to fund this over selling raw Artifex capability is packaging a buyer-specific outcome."}
return {"answer": answers.get(question.category, "The assumption remains uncertain and must be tested before spend."), "evidence": evidence, "uncertainty": "High until public research and customer evidence are available.", "pitch_changes": {"confidence_adjustment": "reduced/held due missing external evidence"} if question.category in {"demand", "competition"} else {}}
def _capability_requirements(self, proposal: CompanyProposal) -> list[dict[str, Any]]:
web_status = CapabilityStatus.MISSING if not self.web_research_available else CapabilityStatus.PARTIAL
return [
{"category": "Company Brain", "status": CapabilityStatus.PARTIAL, "rationale": "Venture reasoning exists in V0 but is not a persistent operating brain.", "priority": CapabilityPriority.BEFORE_SCALING, "evidence": {}},
{"category": "Board", "status": CapabilityStatus.AVAILABLE, "rationale": "Structured CEO/CTO/CFO/CRO/Independent Director review exists for V0.", "priority": CapabilityPriority.BEFORE_VALIDATION, "evidence": {}},
{"category": "IC", "status": CapabilityStatus.AVAILABLE, "rationale": "Bounded IC diligence, questions, scoring, and decision vocabulary exist.", "priority": CapabilityPriority.BEFORE_VALIDATION, "evidence": {}},
{"category": "WEB_MARKET_RESEARCH", "status": web_status, "rationale": "No public web research tool is configured in this service; sources cannot be verified.", "priority": CapabilityPriority.BEFORE_VALIDATION, "evidence": {"web_research_available": self.web_research_available}},
{"category": "software build", "status": CapabilityStatus.AVAILABLE, "rationale": "Task execution, coding, review, tests, and graph runtime exist.", "priority": CapabilityPriority.BEFORE_FIRST_CUSTOMER, "evidence": {}},
{"category": "frontend design", "status": CapabilityStatus.AVAILABLE, "rationale": "Frontend agents and Django UI path exist.", "priority": CapabilityPriority.BEFORE_FIRST_CUSTOMER, "evidence": {}},
{"category": "deployment", "status": CapabilityStatus.PARTIAL, "rationale": "Deployment planning exists, but company-specific production deployment workflow is not implemented.", "priority": CapabilityPriority.BEFORE_FIRST_CUSTOMER, "evidence": {}},
{"category": "outbound sales", "status": CapabilityStatus.MISSING, "rationale": "No compliant outreach/sequence/customer contact system exists and V0 forbids outreach.", "priority": CapabilityPriority.BEFORE_VALIDATION, "evidence": {}},
{"category": "CRM", "status": CapabilityStatus.MISSING, "rationale": "No customer pipeline/contact tracking exists.", "priority": CapabilityPriority.BEFORE_VALIDATION, "evidence": {}},
{"category": "payments", "status": CapabilityStatus.MISSING, "rationale": "No payment collection system exists.", "priority": CapabilityPriority.BEFORE_FIRST_CUSTOMER, "evidence": {}},
{"category": "invoicing", "status": CapabilityStatus.MISSING, "rationale": "No invoicing workflow exists.", "priority": CapabilityPriority.BEFORE_FIRST_CUSTOMER, "evidence": {}},
{"category": "customer support", "status": CapabilityStatus.MISSING, "rationale": "No support inbox or customer service workflow exists.", "priority": CapabilityPriority.BEFORE_SCALING, "evidence": {}},
{"category": "company budget management", "status": CapabilityStatus.MISSING, "rationale": "V0 blocks spend but future validation needs tranche/budget controls.", "priority": CapabilityPriority.BEFORE_VALIDATION, "evidence": {}},
{"category": "legal/compliance", "status": CapabilityStatus.MISSING, "rationale": "No contracts, terms, privacy, or compliance review workflow exists.", "priority": CapabilityPriority.BEFORE_FIRST_CUSTOMER, "evidence": {}},
]
def _artifact(self, proposal, mandate, artifact_type: str, name: str, content: dict[str, Any], readable: str, generated_by: str, *, graph_run=None) -> VentureArtifact:
return VentureArtifact.objects.create(proposal=proposal, mandate=mandate, graph_run=graph_run, artifact_type=artifact_type, name=name, content=content, readable=readable, generated_by=generated_by)
def readable_pitch(self, pitch: dict[str, Any]) -> str:
return "\n\n".join(f"{section}\n{pitch.get(section, '')}" for section in PITCH_SECTIONS)
def _readable_mandate(self, mandate: CompanyMandate) -> str:
return f"Objective\n{mandate.objective}\n\nConstraints\n{json.dumps(mandate.constraints, indent=2)}\n\nOptimization targets\n" + "\n".join(f"- {item}" for item in mandate.optimization_targets)
def _readable_board(self, review: CompanyBoardReview) -> str:
return f"Recommendation: {review.recommendation}\n\nStrengths\n" + "\n".join(f"- {item}" for item in review.strengths) + "\n\nWeaknesses\n" + "\n".join(f"- {item}" for item in review.weaknesses)
def _readable_responses(self, responses: list[ICResponse]) -> str:
return "\n\n".join(f"Q: {response.question.question}\nA: {response.answer}\nUncertainty: {response.uncertainty}" for response in responses)
def _readable_score(self, decision: ICDecision) -> str:
scores = "\n".join(f"- {key}: {value}/100" for key, value in decision.component_scores.items())
return f"Decision: {decision.decision}\nComposite: {decision.composite_score}/100\nP($500 within 30 days): {decision.probability_500_within_30_days}%\n\nScores\n{scores}\n\nCondition\n{decision.validation_condition}"
def _readable_capability_gap(self, available: list[str], partial: list[str], missing: list[str], ranked: list[dict[str, str]]) -> str:
return "AVAILABLE\n" + "\n".join(f"- {item}" for item in available) + "\n\nPARTIAL\n" + "\n".join(f"- {item}" for item in partial) + "\n\nMISSING\n" + "\n".join(f"- {item}" for item in missing) + "\n\nNEXT ARTIFEX CAPABILITIES REQUIRED\n" + "\n".join(f"- {item['category']} ({item['priority']})" for item in ranked)
def _readable_memo(self, memo: dict[str, Any]) -> str:
return json.dumps(memo, indent=2)
def _fingerprint(self, payload: dict[str, Any]) -> str:
return hashlib.sha256((str(payload["title"]).lower() + str(payload["target_customer"]).lower() + str(payload["business_model"]).lower()).encode()).hexdigest()