2026-08-15 20:56:56 +07:00
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 % g ross 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 "
2026-08-15 21:42:46 +07:00
class ResearchProvider ( ModelProvider ) :
provider_name = " luna-test "
def __init__ ( self , complete : bool = True ) - > None :
self . complete_coverage = complete
def complete ( self , request : ModelRequestContract ) - > ModelResponseContract :
categories = [ " competitors " , " pricing " , " customer_pain " , " market_alternatives " , " regulatory_platform_risks " ] if self . complete_coverage else [ " competitors " ]
return ModelResponseContract (
model = " luna " ,
content = json . dumps (
{
2026-08-16 17:48:04 +07:00
" sources " : [ { " url " : f " https://example.com/ { category } " , " title " : category . replace ( " _ " , " " ) . title ( ) , " category " : category , " summary " : f " Source-linked evidence for LaunchLens validation audit serving solo technical founders with pricing, competitors, customer pain, market alternatives, regulatory compliance, tools, services, and subscription cost data for { category } . " } for category in categories ] ,
2026-08-15 21:42:46 +07:00
" findings " : { category : f " Finding for { category } . " for category in categories } ,
" coverage " : { category : category in categories for category in [ " competitors " , " pricing " , " customer_pain " , " market_alternatives " , " regulatory_platform_risks " ] } ,
}
) ,
metadata = { } ,
)
def health ( self ) - > str :
return " AVAILABLE "
2026-08-16 15:35:55 +07:00
class FakeSearchClient :
def search ( self , query : str , * , category : str = " general " , limit : int = 5 ) - > list [ dict [ str , object ] ] :
return [
{
" type " : " public_web " ,
" source " : " searxng " ,
" url " : f " https://search.example/ { category } " ,
" title " : f " { category } source " ,
" category " : category ,
" summary " : f " Search result for { query } " ,
" fallback_evidence " : False ,
}
]
class FakePageFetcher :
def fetch_many ( self , sources : list [ dict [ str , object ] ] , * , max_pages : int = 8 ) - > list [ dict [ str , object ] ] :
return [ { " url " : source [ " url " ] , " category " : source [ " category " ] , " source_title " : source [ " title " ] , " text " : f " Fetched page text for { source [ ' category ' ] } with pricing, alternatives, and customer pain evidence. " , " fetched_chars " : 80 } for source in sources [ : max_pages ] ]
2026-08-15 21:42:46 +07:00
def service ( * , web_research_available : bool = False , complete_research : bool = True ) - > VentureDiscoveryService :
return VentureDiscoveryService ( ModelRouter ( { " sol " : SolOneCompanyProvider ( ) , " luna " : ResearchProvider ( complete_research ) } ) , web_research_available = web_research_available )
2026-08-15 20:56:56 +07:00
2026-08-16 15:35:55 +07:00
def test_venture_model_policy_overrides_ideation_and_research ( monkeypatch ) - > None :
monkeypatch . setenv ( " ARTIFEX_VENTURE_IDEATION_MODEL " , " terra " )
monkeypatch . setenv ( " ARTIFEX_VENTURE_RESEARCH_MODEL " , " qwen " )
router = ModelRouter ( { " terra " : SolOneCompanyProvider ( ) , " qwen " : ResearchProvider ( ) } )
svc = VentureDiscoveryService ( router , web_research_available = True )
proposal = svc . generate_single_company ( svc . create_v0_mandate ( ) )
research = svc . conduct_market_research ( proposal )
assert proposal . metadata [ " generation_source " ] == " terra "
assert proposal . metadata [ " fallback_evidence " ] is False
assert research [ " provider " ] == " qwen "
2026-08-15 20:56:56 +07:00
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 :
2026-08-15 21:42:46 +07:00
svc = service ( web_research_available = True )
2026-08-15 20:56:56 +07:00
proposal = svc . generate_single_company ( svc . create_v0_mandate ( ) )
2026-08-15 21:42:46 +07:00
research = svc . conduct_market_research ( proposal )
2026-08-15 20:56:56 +07:00
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
2026-08-15 21:42:46 +07:00
assert decision . initial_tranche in { Decimal ( " 10.00 " ) , Decimal ( " 20.00 " ) }
2026-08-15 20:56:56 +07:00
assert " 5 credible target-customer responses " in decision . validation_condition
assert decision . metadata [ " no_actual_funding " ] is True
2026-08-15 21:42:46 +07:00
assert len ( research [ " sources " ] ) == 5
assert " WEB_MARKET_RESEARCH " in gap . available
assert gap . metadata [ " web_market_research_status " ] == " AVAILABLE "
2026-08-15 20:56:56 +07:00
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
2026-08-16 15:35:55 +07:00
def test_searxng_search_and_page_fetch_sources_can_supply_market_research_evidence ( ) - > None :
svc = VentureDiscoveryService ( ModelRouter ( { } ) , web_research_available = True , search_client = FakeSearchClient ( ) , page_fetcher = FakePageFetcher ( ) )
proposal = svc . generate_single_company ( svc . create_v0_mandate ( ) )
research = svc . conduct_market_research ( proposal )
assert research [ " search_provider " ] == " searxng "
assert len ( research [ " sources " ] ) == 5
assert research [ " page_fetch_count " ] == 5
assert " Fetched page text " in research [ " page_corpus " ] [ 0 ] [ " text " ]
assert proposal . metadata [ " research " ] [ " page_fetch_count " ] == 5
assert all ( research [ " coverage " ] . values ( ) )
2026-08-15 20:56:56 +07:00
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
2026-08-15 21:42:46 +07:00
assert graph_run . node_runs . count ( ) == 14
assert graph_run . edge_traversals . count ( ) == 14
2026-08-15 20:56:56 +07:00
artifact_types = set ( VentureArtifact . objects . values_list ( " artifact_type " , flat = True ) )
2026-08-15 21:42:46 +07:00
assert { " STANDARDIZED_COMPANY_PITCH " , " MARKET_RESEARCH " , " 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 )
2026-08-15 20:56:56 +07:00
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
2026-08-15 21:42:46 +07:00
def test_materially_different_pitches_receive_different_scores_and_tranches ( ) - > None :
strong = service ( web_research_available = True , complete_research = True )
strong_proposal = strong . generate_single_company ( strong . create_v0_mandate ( ) )
strong . conduct_market_research ( strong_proposal )
strong_diligence = strong . start_ic_diligence ( strong_proposal )
strong . generate_ic_questions ( strong_diligence )
strong . answer_questions ( strong_diligence )
strong . red_team ( strong_diligence )
strong . final_company_response ( strong_diligence )
strong_decision = strong . score_and_decide ( strong_diligence )
weak = service ( web_research_available = True , complete_research = False )
weak_proposal = weak . generate_single_company ( weak . create_v0_mandate ( ) )
weak_proposal . title = " Enterprise RegTech Platform "
weak_proposal . business_model = " SaaS platform requiring integrations and long enterprise sales cycles. "
weak_proposal . build_complexity = " HIGH "
weak_proposal . expected_margin = " 35-45 % g ross margin before support burden "
weak_proposal . validation_plan = " Build a prototype and then seek feedback. "
weak_proposal . pitch = { * * weak_proposal . pitch , " Company name " : weak_proposal . title , " Business model " : weak_proposal . business_model , " Validation plan " : weak_proposal . validation_plan }
weak_proposal . save ( update_fields = [ " title " , " business_model " , " build_complexity " , " expected_margin " , " validation_plan " , " pitch " , " updated_at " ] )
weak . conduct_market_research ( weak_proposal )
weak_diligence = weak . start_ic_diligence ( weak_proposal )
weak . generate_ic_questions ( weak_diligence )
weak . answer_questions ( weak_diligence )
weak . red_team ( weak_diligence )
weak . final_company_response ( weak_diligence )
weak_decision = weak . score_and_decide ( weak_diligence )
assert strong_decision . component_scores [ " Demand Evidence " ] > weak_decision . component_scores [ " Demand Evidence " ]
assert strong_decision . component_scores [ " Build Simplicity " ] > weak_decision . component_scores [ " Build Simplicity " ]
assert strong_decision . composite_score - weak_decision . composite_score > = 10
assert strong_decision . probability_500_within_30_days > weak_decision . probability_500_within_30_days
assert strong_decision . initial_tranche != weak_decision . initial_tranche