Artifex/tests/test_venture_discovery_v0.py
2026-08-15 20:56:56 +07:00

156 lines
8.2 KiB
Python

from __future__ import annotations
import json
from decimal import Decimal
from agents.venture_discovery import PITCH_SECTIONS, SCORE_DIMENSIONS, VentureDiscoveryService
from control_plane.ventures.models import CapabilityPriority, CapabilityStatus, CompanyProposal, CompanyProposalStatus, ICDecisionType, VentureArtifact
from graph.bootstrap import champion_venture_discovery_graph_v1
from graph.langgraph_runtime import LangGraphRuntime
from graph.models import GraphRun, GraphRunStatus
from graph.venture_discovery import venture_discovery_registry
from model_router.router import ModelProvider, ModelRequestContract, ModelResponseContract, ModelRouter
class SolOneCompanyProvider(ModelProvider):
provider_name = "sol-test"
def complete(self, request: ModelRequestContract) -> ModelResponseContract:
return ModelResponseContract(
model="sol",
content=json.dumps(
{
"title": "LaunchLens",
"one_line_thesis": "A paid validation audit helps solo technical founders avoid wasting weeks on unvalidated AI microbusinesses.",
"description": "A fixed-scope validation and launch-readiness report for one startup idea.",
"problem": "Builders overbuild before proving demand.",
"target_customer": "Solo technical founders and small service operators considering an AI-assisted microbusiness.",
"proposed_solution": "A productized audit covering ICP, first-dollar path, validation gates, build plan, risks, and capability gaps.",
"business_model": "Productized service first, optional software later.",
"pricing_hypothesis": "$49-$99 per audit.",
"acquisition_strategy": "Compliant founder community posts and personal network conversations after approval.",
"validation_plan": "Collect 5 credible target-customer responses or 1 willingness-to-pay signal before build/spend.",
"capital_requested": "50",
"time_to_first_dollar_estimate": "3-10 days after outreach approval",
"expected_margin": "70-85% gross margin",
"build_complexity": "LOW",
"market_evidence": [{"type": "reasoning", "source": "sol", "summary": "Service-led validation minimizes build risk."}],
"differentiation": "IC-style diligence plus Artifex execution/capability-gap awareness.",
"major_risks": ["Demand unproven", "Distribution may fail", "Generic consulting competition"],
"confidence": 0.66,
}
),
metadata={"usage": {"prompt_tokens": 1, "completion_tokens": 1}},
)
def health(self) -> str:
return "AVAILABLE"
def service(*, web_research_available: bool = False) -> VentureDiscoveryService:
return VentureDiscoveryService(ModelRouter({"sol": SolOneCompanyProvider()}), web_research_available=web_research_available)
def test_company_proposal_lifecycle_mandate_limits_and_pitch_schema() -> None:
svc = service()
mandate = svc.create_v0_mandate()
proposal = svc.generate_single_company(mandate)
assert mandate.max_validation_capital == Decimal("50")
assert mandate.target_net_new_cash == Decimal("500")
assert mandate.target_window_days == 30
assert mandate.constraints["no_real_spend_in_v0"] is True
assert mandate.constraints["no_real_customer_outreach_in_v0"] is True
assert CompanyProposal.objects.count() == 1
assert proposal.status == CompanyProposalStatus.SUBMITTED
assert proposal.capital_requested <= Decimal("50")
assert all(section in proposal.pitch for section in PITCH_SECTIONS)
assert proposal.metadata["generation_source"] == "sol"
assert proposal.metadata["real_spend"] == 0
assert proposal.metadata["real_customer_outreach"] is False
def test_board_review_ic_questions_responses_and_bounded_diligence() -> None:
svc = service()
proposal = svc.generate_single_company(svc.create_v0_mandate())
board = svc.board_review(proposal)
diligence = svc.start_ic_diligence(proposal)
questions = svc.generate_ic_questions(diligence)
responses = svc.answer_questions(diligence)
challenge = svc.red_team(diligence)
final = svc.final_company_response(diligence)
assert set(board.observations) == {"CEO", "CTO", "CFO", "CRO", "Independent Director"}
assert board.metadata["company_does_not_grade_itself"] is True
assert diligence.rounds == ["Initial Pitch", "Diligence Round 1", "Final Challenge", "Decision"]
assert diligence.metadata["bounded_rounds"] is True
assert len(questions) == 8
assert all(q.evidence_required for q in questions)
assert len(responses) == len(questions)
assert all(r.metadata["no_customer_outreach"] and r.metadata["no_spend"] for r in responses)
assert challenge["recommendation"] == "continue_to_final_response"
assert final["kill_criteria"]
def test_ic_scoring_decision_capability_gap_and_memo_artifact() -> None:
svc = service()
proposal = svc.generate_single_company(svc.create_v0_mandate())
svc.board_review(proposal)
diligence = svc.start_ic_diligence(proposal)
svc.generate_ic_questions(diligence)
svc.answer_questions(diligence)
svc.red_team(diligence)
svc.final_company_response(diligence)
decision = svc.score_and_decide(diligence)
gap = svc.capability_analysis(proposal)
memo = svc.produce_investment_memo(diligence, gap)
assert decision.decision in ICDecisionType.values
assert set(SCORE_DIMENSIONS) == set(decision.component_scores)
assert decision.decision == ICDecisionType.CONDITIONAL_FUND
assert decision.initial_tranche == Decimal("10.00")
assert "5 credible target-customer responses" in decision.validation_condition
assert decision.metadata["no_actual_funding"] is True
assert "WEB_MARKET_RESEARCH" in gap.missing
assert gap.metadata["web_market_research_status"] == "MISSING"
assert {item["priority"] for item in gap.ranked_missing}.issuperset({CapabilityPriority.BEFORE_VALIDATION, CapabilityPriority.BEFORE_FIRST_CUSTOMER})
assert memo.artifact_type == "FINAL_INVESTMENT_MEMO"
assert memo.content["Company"] == proposal.title
assert "Capability gap" in memo.content
def test_fallback_is_marked_and_reduces_confidence_when_research_unavailable() -> None:
svc = VentureDiscoveryService(web_research_available=False)
proposal = svc.generate_single_company(svc.create_v0_mandate())
gap = svc.capability_analysis(proposal)
assert proposal.metadata["generation_source"] == "deterministic_fallback"
assert proposal.metadata["fallback_evidence"] is True
assert proposal.confidence <= 0.58
assert any(item.get("fallback_evidence") for item in proposal.market_evidence)
assert "WEB_MARKET_RESEARCH" in gap.missing
def test_venture_discovery_v1_graph_lineage_and_no_automatic_execution() -> None:
version = champion_venture_discovery_graph_v1()
graph_run = GraphRun.objects.create(execution_graph_version=version, current_node=version.graph_spec["entry"])
LangGraphRuntime(venture_discovery_registry(service())).run_until_terminal_or_paused(graph_run)
graph_run.refresh_from_db()
assert graph_run.status == GraphRunStatus.COMPLETE
assert graph_run.execution_graph_version.graph.name == "venture_discovery"
assert graph_run.execution_graph_version.version == 1
assert CompanyProposal.objects.count() == 1
proposal = CompanyProposal.objects.get()
assert proposal.metadata["real_spend"] == 0
assert proposal.metadata["real_customer_outreach"] is False
assert graph_run.node_runs.count() == 13
assert graph_run.edge_traversals.count() == 13
artifact_types = set(VentureArtifact.objects.values_list("artifact_type", flat=True))
assert {"STANDARDIZED_COMPANY_PITCH", "COMPANY_BOARD_REVIEW", "IC_QUESTIONS", "IC_RESPONSES", "IC_RED_TEAM", "IC_FINAL_RESPONSE", "IC_FINAL_SCORE", "CAPABILITY_GAP_REPORT", "FINAL_INVESTMENT_MEMO"}.issubset(artifact_types)
decision = proposal.ic_diligence.get().decision
svc = service()
svc.request_human_approval(decision, "approve_for_validation")
decision.refresh_from_db()
assert decision.metadata["real_spend_still_blocked"] is True