|
|
|
@ -16,7 +16,7 @@ from django.db import close_old_connections, connection
|
|
|
|
from django.utils import timezone
|
|
|
|
from django.utils import timezone
|
|
|
|
|
|
|
|
|
|
|
|
from control_plane.events.bus import EventBus
|
|
|
|
from control_plane.events.bus import EventBus
|
|
|
|
from control_plane.ventures.models import CapabilityPriority, CapabilityStatus, CohortIdeationMandate, CompanyBoardReview, CompanyCapabilityRequirement, CompanyMandate, CompanyProposal, CompanyProposalStatus, EvidenceTier, ICDecision, ICDecisionType, ICDiligence, ICQuestion, ICResponse, NoveltyGateDecision, OpportunityTerritory, OverlapClassification, PortfolioCapabilityGap, PortfolioICReview, PortfolioSaturationAnalysis, PortfolioThesis, PortfolioThesisCluster, PortfolioThesisStatus, ThesisMatchClassification, VentureArtifact, VentureCapabilityDemand, VentureCohort, VentureCohortMember, VentureCollision, VentureGenerationRejection, VentureThesis, VentureThesisFingerprint
|
|
|
|
from control_plane.ventures.models import AutonomousCandidateStatus, AutonomousGateResult, AutonomousOperabilityAssessment, CapabilityPriority, CapabilityStatus, CohortIdeationMandate, CompanyBoardReview, CompanyCapabilityRequirement, CompanyMandate, CompanyProposal, CompanyProposalStatus, EvidenceTier, FounderDependencyLevel, ICDecision, ICDecisionType, ICDiligence, ICQuestion, ICResponse, NoveltyGateDecision, OpportunityTerritory, OverlapClassification, PortfolioCapabilityGap, PortfolioICReview, PortfolioSaturationAnalysis, PortfolioThesis, PortfolioThesisCluster, PortfolioThesisStatus, ThesisMatchClassification, VentureArtifact, VentureCapabilityDemand, VentureCohort, VentureCohortMember, VentureCollision, VentureGenerationRejection, VentureThesis, VentureThesisFingerprint, VentureTrack
|
|
|
|
from graph.models import GraphRun, GraphRunStatus
|
|
|
|
from graph.models import GraphRun, GraphRunStatus
|
|
|
|
from model_router.providers import extract_json_object
|
|
|
|
from model_router.providers import extract_json_object
|
|
|
|
from model_router.policy import model_for_role
|
|
|
|
from model_router.policy import model_for_role
|
|
|
|
@ -25,7 +25,7 @@ from research.searxng import SearxngSearchClient, WebPageFetcher
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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"]
|
|
|
|
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 Attractiveness", "Capital Efficiency", "Validation Affordability", "Gross Margin Potential", "Distribution Feasibility", "Build Simplicity", "Defensibility", "Market Opportunity", "Competitive Position", "Risk Manageability", "AI Leverage", "Platformization Potential", "Probability of Reaching $500"]
|
|
|
|
SCORE_DIMENSIONS = ["Demand Evidence", "Time-to-First-Dollar Attractiveness", "Capital Efficiency", "Validation Affordability", "Gross Margin Potential", "Distribution Feasibility", "Build Simplicity", "Defensibility", "Market Opportunity", "Competitive Position", "Risk Manageability", "AI Leverage", "Platformization Potential", "Autonomous Operability", "Probability of Reaching $500"]
|
|
|
|
SCORE_DEFINITIONS = {dimension: "100 = highly attractive; 0 = highly unattractive" for dimension in SCORE_DIMENSIONS}
|
|
|
|
SCORE_DEFINITIONS = {dimension: "100 = highly attractive; 0 = highly unattractive" for dimension in SCORE_DIMENSIONS}
|
|
|
|
EVIDENCE_CEILINGS = {EvidenceTier.TIER_0_THESIS: 35, EvidenceTier.TIER_1_PUBLIC_EVIDENCE: 55, EvidenceTier.TIER_2_CUSTOMER_SIGNAL: 70, EvidenceTier.TIER_3_WILLINGNESS_TO_PAY: 85, EvidenceTier.TIER_4_PAID_CUSTOMER: 95, EvidenceTier.TIER_5_REPEATABLE_TRACTION: 100}
|
|
|
|
EVIDENCE_CEILINGS = {EvidenceTier.TIER_0_THESIS: 35, EvidenceTier.TIER_1_PUBLIC_EVIDENCE: 55, EvidenceTier.TIER_2_CUSTOMER_SIGNAL: 70, EvidenceTier.TIER_3_WILLINGNESS_TO_PAY: 85, EvidenceTier.TIER_4_PAID_CUSTOMER: 95, EvidenceTier.TIER_5_REPEATABLE_TRACTION: 100}
|
|
|
|
AI_NATIVE_POLICY = {"preference": "Prefer AI-native opportunities where Artifex can build or deliver most value with existing agents, local inference, and owned workflow capabilities.", "preferred_classes": ["AI wrapper platforms", "AI-enabled services", "AI infrastructure / developer tools", "AI research / intelligence products", "AI automation for SMB / enterprise workflows"], "soft_portfolio_targets": {"ai_wrapper_platforms": "40-50%", "ai_enabled_services": "20-30%", "developer_infrastructure_intelligence": "10-20%", "non_ai_economic_outliers": "10-20%"}, "penalize_concentration": ["Shopify/ecommerce", "generic audits", "emergency fix services", "one-off consulting", "identical outbound-led service models"], "do_not": "Do not let AI novelty outweigh customer demand."}
|
|
|
|
AI_NATIVE_POLICY = {"preference": "Prefer AI-native opportunities where Artifex can build or deliver most value with existing agents, local inference, and owned workflow capabilities.", "preferred_classes": ["AI wrapper platforms", "AI-enabled services", "AI infrastructure / developer tools", "AI research / intelligence products", "AI automation for SMB / enterprise workflows"], "soft_portfolio_targets": {"ai_wrapper_platforms": "40-50%", "ai_enabled_services": "20-30%", "developer_infrastructure_intelligence": "10-20%", "non_ai_economic_outliers": "10-20%"}, "penalize_concentration": ["Shopify/ecommerce", "generic audits", "emergency fix services", "one-off consulting", "identical outbound-led service models"], "do_not": "Do not let AI novelty outweigh customer demand."}
|
|
|
|
@ -34,6 +34,7 @@ DEFAULT_SOFT_EXCLUSIONS = ["generic contract scanners", "Shopify audit products"
|
|
|
|
DEFAULT_TERRITORY_ALLOCATION = [OpportunityTerritory.VERTICAL_AI_WRAPPERS, OpportunityTerritory.VERTICAL_AI_WRAPPERS, OpportunityTerritory.ENTERPRISE_WORKFLOW_AUTOMATION, OpportunityTerritory.SMB_AUTOMATION, OpportunityTerritory.DEVELOPER_AI_INFRASTRUCTURE, OpportunityTerritory.INTELLIGENCE_MONITORING, OpportunityTerritory.DATA_DOCUMENT_AUTOMATION, OpportunityTerritory.AI_ENABLED_SERVICE_TO_PLATFORM, OpportunityTerritory.OPEN_CATEGORY, OpportunityTerritory.OPEN_CATEGORY]
|
|
|
|
DEFAULT_TERRITORY_ALLOCATION = [OpportunityTerritory.VERTICAL_AI_WRAPPERS, OpportunityTerritory.VERTICAL_AI_WRAPPERS, OpportunityTerritory.ENTERPRISE_WORKFLOW_AUTOMATION, OpportunityTerritory.SMB_AUTOMATION, OpportunityTerritory.DEVELOPER_AI_INFRASTRUCTURE, OpportunityTerritory.INTELLIGENCE_MONITORING, OpportunityTerritory.DATA_DOCUMENT_AUTOMATION, OpportunityTerritory.AI_ENABLED_SERVICE_TO_PLATFORM, OpportunityTerritory.OPEN_CATEGORY, OpportunityTerritory.OPEN_CATEGORY]
|
|
|
|
DEFAULT_DUPLICATE_POLICY = {"max_near_duplicate_per_thesis_per_cohort": 1, "max_competitive_per_thesis_per_cohort": 2, "max_attempts_per_slot": 3, "cohort_attempt_budget_multiplier": 4}
|
|
|
|
DEFAULT_DUPLICATE_POLICY = {"max_near_duplicate_per_thesis_per_cohort": 1, "max_competitive_per_thesis_per_cohort": 2, "max_attempts_per_slot": 3, "cohort_attempt_budget_multiplier": 4}
|
|
|
|
RESEARCH_CATEGORIES = ["competitors", "pricing", "customer_pain", "market_alternatives", "regulatory_platform_risks"]
|
|
|
|
RESEARCH_CATEGORIES = ["competitors", "pricing", "customer_pain", "market_alternatives", "regulatory_platform_risks"]
|
|
|
|
|
|
|
|
AUTONOMOUS_ARCHETYPES = ["SELF_SERVICE_AI_TOOL", "AUTOMATED_MONITORING_PRODUCT", "DIGITAL_ANALYSIS_SERVICE", "AUTOMATED_TRANSFORMATION_SERVICE", "MICRO_SAAS", "DATA_INTELLIGENCE_SUBSCRIPTION", "DEVELOPER_TOOL", "AUTONOMOUS_DIGITAL_PRODUCT", "MARKETPLACE_DELIVERED_SERVICE", "AGENT_AS_A_SERVICE"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VentureDiscoveryService:
|
|
|
|
class VentureDiscoveryService:
|
|
|
|
@ -49,10 +50,10 @@ class VentureDiscoveryService:
|
|
|
|
|
|
|
|
|
|
|
|
def create_v0_mandate(self) -> CompanyMandate:
|
|
|
|
def create_v0_mandate(self) -> CompanyMandate:
|
|
|
|
mandate = CompanyMandate.objects.create(
|
|
|
|
mandate = CompanyMandate.objects.create(
|
|
|
|
objective="Design a business that could plausibly turn at most $50 of external validation capital into $500 net new cash.",
|
|
|
|
objective="Design a business that Artifex itself could plausibly operate and turn at most $50 of external validation capital into at least $500 net new cash within 30 days, with no more than 30 minutes/week of routine human intervention.",
|
|
|
|
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},
|
|
|
|
constraints={"external_validation_capital_max": 50, "target_net_new_cash": 500, "target_window_days": 30, "max_human_routine_minutes_per_week": 30, "default_venture_track": VentureTrack.AUTONOMOUS, "routine_human_involvement_not_allowed": ["founder-led sales calls", "manual prospecting", "routine support", "manual fulfillment", "bespoke consulting", "routine QA", "manual payment chasing", "routine onboarding"], "allowed_human_involvement": ["approve account/legal setup", "approve initial spend", "approve material legal commitments", "exceptional safety/security escalation", "irreversible capital decisions"], "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, "validation_deployment": "SUBDOMAIN_DEPLOYMENT"},
|
|
|
|
optimization_targets=["time to first dollar", "capital efficiency", "real demand evidence", "high gross margin", "realistic execution", "low external dependency", "ability to validate cheaply", "AI leverage where economically justified", "service-to-platform evolution potential"],
|
|
|
|
optimization_targets=["autonomous fulfillment", "autonomous customer acquisition", "self-service onboarding", "standardized digital delivery", "AI leverage", "platformization", "recurring revenue", "simple payment flow", "low support burden", "low regulatory burden", "low integration burden", "rapid validation"],
|
|
|
|
metadata={"milestone": "VENTURE_DISCOVERY_V0", "spend_authorized": False, "customer_outreach_authorized": False, "ai_native_policy": AI_NATIVE_POLICY},
|
|
|
|
metadata={"milestone": "VENTURE_DISCOVERY_V0_AUTONOMOUS", "venture_track": VentureTrack.AUTONOMOUS, "spend_authorized": False, "customer_outreach_authorized": False, "ai_native_policy": AI_NATIVE_POLICY, "autonomous_archetypes": AUTONOMOUS_ARCHETYPES},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
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")
|
|
|
|
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
|
|
|
|
return mandate
|
|
|
|
@ -92,7 +93,7 @@ class VentureDiscoveryService:
|
|
|
|
confidence=confidence,
|
|
|
|
confidence=confidence,
|
|
|
|
status=CompanyProposalStatus.SUBMITTED,
|
|
|
|
status=CompanyProposalStatus.SUBMITTED,
|
|
|
|
pitch=pitch,
|
|
|
|
pitch=pitch,
|
|
|
|
metadata={"generation_source": source, "fallback_evidence": source == "deterministic_fallback", "web_research_available": self.web_research_available, "real_spend": 0, "real_customer_outreach": False},
|
|
|
|
metadata={"generation_source": source, "fallback_evidence": source == "deterministic_fallback", "web_research_available": self.web_research_available, "real_spend": 0, "real_customer_outreach": False, "validation_offer": payload.get("validation_offer", {}), "end_state_business_model": payload.get("end_state_business_model", {}), "fulfillment_contract": payload.get("fulfillment_contract", {}), "minutes_per_week_human": payload.get("minutes_per_week_human", None), "human_actions_required": self._as_list(payload.get("human_actions_required", []))},
|
|
|
|
evidence_tier=EvidenceTier.TIER_0_THESIS,
|
|
|
|
evidence_tier=EvidenceTier.TIER_0_THESIS,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
self._artifact(proposal, mandate, "STANDARDIZED_COMPANY_PITCH", "Standardized Company Pitch", pitch, self.readable_pitch(pitch), f"Company Brain/{source}" if source != "deterministic_fallback" else "deterministic_fallback", graph_run=graph_run)
|
|
|
|
self._artifact(proposal, mandate, "STANDARDIZED_COMPANY_PITCH", "Standardized Company Pitch", pitch, self.readable_pitch(pitch), f"Company Brain/{source}" if source != "deterministic_fallback" else "deterministic_fallback", graph_run=graph_run)
|
|
|
|
@ -104,6 +105,10 @@ class VentureDiscoveryService:
|
|
|
|
research = {"coverage": {category: False for category in required}, "sources": [], "findings": {}, "unverified_categories": required, "research_available": False}
|
|
|
|
research = {"coverage": {category: False for category in required}, "sources": [], "findings": {}, "unverified_categories": required, "research_available": False}
|
|
|
|
search_sources = self._searxng_sources(proposal, required, depth=depth) if self.web_research_available else []
|
|
|
|
search_sources = self._searxng_sources(proposal, required, depth=depth) if self.web_research_available else []
|
|
|
|
page_corpus = self._page_corpus(search_sources) if search_sources else []
|
|
|
|
page_corpus = self._page_corpus(search_sources) if search_sources else []
|
|
|
|
|
|
|
|
diagnostics = {"search_result_count": len(search_sources), "page_fetch_count": len(page_corpus), "model_provider": self.research_model_hint if self.router is not None else "none", "search_provider": "searxng" if search_sources else "none"}
|
|
|
|
|
|
|
|
search_diagnostics = getattr(self, "_last_search_diagnostics", {})
|
|
|
|
|
|
|
|
if search_diagnostics:
|
|
|
|
|
|
|
|
diagnostics["search_diagnostics"] = search_diagnostics
|
|
|
|
if self.web_research_available and self.router is not None:
|
|
|
|
if self.web_research_available and self.router is not None:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
response = self.router.complete(ModelRequestContract(purpose=ModelCapability.REASONING, model_hint=self.research_model_hint, prompt="Bounded public web market research for exactly one startup pitch. Return JSON with keys: sources (list of {url,title,category,summary}), findings (object keyed by competitors, pricing, customer_pain, market_alternatives, regulatory_platform_risks), and coverage (object with each required key true/false). Use only the provided SearXNG search context and fetched page excerpts for source URLs; do not fabricate URLs. If a category has no strong relevant source, mark coverage false. Research depth: " + depth + ". Search context: " + json.dumps(search_sources, default=str) + " Page excerpts: " + json.dumps(page_corpus, default=str) + " Pitch: " + json.dumps(proposal.pitch, default=str)))
|
|
|
|
response = self.router.complete(ModelRequestContract(purpose=ModelCapability.REASONING, model_hint=self.research_model_hint, prompt="Bounded public web market research for exactly one startup pitch. Return JSON with keys: sources (list of {url,title,category,summary}), findings (object keyed by competitors, pricing, customer_pain, market_alternatives, regulatory_platform_risks), and coverage (object with each required key true/false). Use only the provided SearXNG search context and fetched page excerpts for source URLs; do not fabricate URLs. If a category has no strong relevant source, mark coverage false. Research depth: " + depth + ". Search context: " + json.dumps(search_sources, default=str) + " Page excerpts: " + json.dumps(page_corpus, default=str) + " Pitch: " + json.dumps(proposal.pitch, default=str)))
|
|
|
|
@ -119,7 +124,7 @@ class VentureDiscoveryService:
|
|
|
|
if page_corpus:
|
|
|
|
if page_corpus:
|
|
|
|
research = {**research, "page_corpus": page_corpus, "page_fetch_count": len(page_corpus)}
|
|
|
|
research = {**research, "page_corpus": page_corpus, "page_fetch_count": len(page_corpus)}
|
|
|
|
if not research.get("sources"):
|
|
|
|
if not research.get("sources"):
|
|
|
|
research = self._missing_research(required, research.get("failure", "public web research unavailable or returned no source-linked evidence"))
|
|
|
|
research = {**self._missing_research(required, research.get("failure", "public web research unavailable or returned no source-linked evidence")), **diagnostics}
|
|
|
|
quality = self.filter_research_sources(proposal, research.get("sources", []), required)
|
|
|
|
quality = self.filter_research_sources(proposal, research.get("sources", []), required)
|
|
|
|
research["sources"] = quality["accepted_sources"]
|
|
|
|
research["sources"] = quality["accepted_sources"]
|
|
|
|
research["source_rejections"] = quality["rejected_sources"]
|
|
|
|
research["source_rejections"] = quality["rejected_sources"]
|
|
|
|
@ -134,7 +139,8 @@ class VentureDiscoveryService:
|
|
|
|
proposal.confidence = round(min(float(proposal.confidence), 0.45 + 0.35 * coverage_ratio), 2)
|
|
|
|
proposal.confidence = round(min(float(proposal.confidence), 0.45 + 0.35 * coverage_ratio), 2)
|
|
|
|
existing_research = proposal.metadata.get("research", {}) if isinstance(proposal.metadata, dict) else {}
|
|
|
|
existing_research = proposal.metadata.get("research", {}) if isinstance(proposal.metadata, dict) else {}
|
|
|
|
proposal.market_evidence = [*self._as_list(proposal.market_evidence), *research.get("sources", []), {"type": "research_coverage", "source": "venture_research", "summary": f"{depth.title()} source-linked research coverage: {round(coverage_ratio * 100)}%", "coverage": coverage, "unverified_categories": research["unverified_categories"], "depth": depth}]
|
|
|
|
proposal.market_evidence = [*self._as_list(proposal.market_evidence), *research.get("sources", []), {"type": "research_coverage", "source": "venture_research", "summary": f"{depth.title()} source-linked research coverage: {round(coverage_ratio * 100)}%", "coverage": coverage, "unverified_categories": research["unverified_categories"], "depth": depth}]
|
|
|
|
proposal.metadata = {**proposal.metadata, "research": {**existing_research, "coverage_ratio": coverage_ratio, "coverage": coverage, "unverified_categories": research["unverified_categories"], "source_count": len(research.get("sources", [])), "source_rejection_count": len(quality["rejected_sources"]), "page_fetch_count": research.get("page_fetch_count", 0), "provider": research.get("provider", "none"), "search_provider": research.get("search_provider", "none"), "depth": depth, "explicit_research_failure": research.get("failure", "") if coverage_ratio < 0.8 else "", "before_deep_coverage_ratio": existing_research.get("coverage_ratio") if depth == "deep" else existing_research.get("before_deep_coverage_ratio")}}
|
|
|
|
explicit_failure = "" if research.get("sources") else research.get("failure", "public web research unavailable or returned no source-linked evidence")
|
|
|
|
|
|
|
|
proposal.metadata = {**proposal.metadata, "research": {**existing_research, "coverage_ratio": coverage_ratio, "coverage": coverage, "unverified_categories": research["unverified_categories"], "source_count": len(research.get("sources", [])), "source_rejection_count": len(quality["rejected_sources"]), "search_result_count": research.get("search_result_count", len(search_sources)), "page_fetch_count": research.get("page_fetch_count", len(page_corpus)), "provider": research.get("provider", diagnostics.get("model_provider", "none")), "search_provider": research.get("search_provider", diagnostics["search_provider"]), "search_diagnostics": research.get("search_diagnostics", diagnostics.get("search_diagnostics", {})), "model_research_failure": research.get("failure", "") if research.get("sources") else "", "depth": depth, "explicit_research_failure": explicit_failure, "before_deep_coverage_ratio": existing_research.get("coverage_ratio") if depth == "deep" else existing_research.get("before_deep_coverage_ratio")}}
|
|
|
|
proposal.evidence_tier = EvidenceTier.TIER_1_PUBLIC_EVIDENCE if coverage_ratio > 0 else EvidenceTier.TIER_0_THESIS
|
|
|
|
proposal.evidence_tier = EvidenceTier.TIER_1_PUBLIC_EVIDENCE if coverage_ratio > 0 else EvidenceTier.TIER_0_THESIS
|
|
|
|
if proposal.thesis:
|
|
|
|
if proposal.thesis:
|
|
|
|
proposal.thesis.evidence_tier = proposal.evidence_tier
|
|
|
|
proposal.thesis.evidence_tier = proposal.evidence_tier
|
|
|
|
@ -208,7 +214,13 @@ class VentureDiscoveryService:
|
|
|
|
return response
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
def score_and_decide(self, diligence: ICDiligence, *, graph_run=None) -> ICDecision:
|
|
|
|
def score_and_decide(self, diligence: ICDiligence, *, graph_run=None) -> ICDecision:
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
assessment = diligence.proposal.autonomous_assessment
|
|
|
|
|
|
|
|
except ObjectDoesNotExist:
|
|
|
|
|
|
|
|
assessment = None
|
|
|
|
scores = self._evidence_scores(diligence.proposal)
|
|
|
|
scores = self._evidence_scores(diligence.proposal)
|
|
|
|
|
|
|
|
if assessment is not None:
|
|
|
|
|
|
|
|
scores["Autonomous Operability"] = int(assessment.autonomous_operability_score)
|
|
|
|
composite = round(sum(scores.values()) / len(scores), 1)
|
|
|
|
composite = round(sum(scores.values()) / len(scores), 1)
|
|
|
|
calibration = self.calibrate_probability(diligence.proposal, raw_probability=float(scores["Probability of Reaching $500"]))
|
|
|
|
calibration = self.calibrate_probability(diligence.proposal, raw_probability=float(scores["Probability of Reaching $500"]))
|
|
|
|
scores["Probability of Reaching $500"] = int(calibration["evidence_adjusted_probability"])
|
|
|
|
scores["Probability of Reaching $500"] = int(calibration["evidence_adjusted_probability"])
|
|
|
|
@ -419,6 +431,7 @@ class VentureDiscoveryService:
|
|
|
|
self.red_team(diligence)
|
|
|
|
self.red_team(diligence)
|
|
|
|
self.final_company_response(diligence)
|
|
|
|
self.final_company_response(diligence)
|
|
|
|
self.score_and_decide(diligence)
|
|
|
|
self.score_and_decide(diligence)
|
|
|
|
|
|
|
|
self.assess_autonomous_operability(proposal)
|
|
|
|
gap = self.capability_analysis(proposal)
|
|
|
|
gap = self.capability_analysis(proposal)
|
|
|
|
self.produce_investment_memo(diligence, gap)
|
|
|
|
self.produce_investment_memo(diligence, gap)
|
|
|
|
member.child_graph_run = GraphRun.objects.create(execution_graph_version=cohort.graph_run.execution_graph_version if cohort.graph_run else None, status=GraphRunStatus.COMPLETE, metadata={"logical_child_company_run": True, "proposal_id": str(proposal.id), "cohort_id": cohort.cohort_id}) if cohort.graph_run else None
|
|
|
|
member.child_graph_run = GraphRun.objects.create(execution_graph_version=cohort.graph_run.execution_graph_version if cohort.graph_run else None, status=GraphRunStatus.COMPLETE, metadata={"logical_child_company_run": True, "proposal_id": str(proposal.id), "cohort_id": cohort.cohort_id}) if cohort.graph_run else None
|
|
|
|
@ -431,7 +444,85 @@ class VentureDiscoveryService:
|
|
|
|
cohort.status = "INDIVIDUAL_DILIGENCE_COMPLETE"
|
|
|
|
cohort.status = "INDIVIDUAL_DILIGENCE_COMPLETE"
|
|
|
|
cohort.save(update_fields=["metrics", "status", "updated_at"])
|
|
|
|
cohort.save(update_fields=["metrics", "status", "updated_at"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def assess_autonomous_operability_for_cohort(self, cohort: VentureCohort) -> list[AutonomousOperabilityAssessment]:
|
|
|
|
|
|
|
|
assessments = [self.assess_autonomous_operability(member.proposal) for member in cohort.members.select_related("proposal")]
|
|
|
|
|
|
|
|
cohort.metrics = {**cohort.metrics, "autonomous_eligible_count": sum(1 for item in assessments if item.gate_result == AutonomousGateResult.AUTONOMOUS_ELIGIBLE), "assisted_only_count": sum(1 for item in assessments if item.gate_result == AutonomousGateResult.ASSISTED_ONLY)}
|
|
|
|
|
|
|
|
cohort.save(update_fields=["metrics", "updated_at"])
|
|
|
|
|
|
|
|
return assessments
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def assess_autonomous_operability(self, proposal: CompanyProposal) -> AutonomousOperabilityAssessment:
|
|
|
|
|
|
|
|
text = " ".join([proposal.title, proposal.description, proposal.problem, proposal.target_customer, proposal.proposed_solution, proposal.business_model, proposal.pricing_hypothesis, proposal.acquisition_strategy, proposal.validation_plan, proposal.differentiation]).lower()
|
|
|
|
|
|
|
|
positive_terms = ["self-service", "automated", "monitor", "subscription", "api", "dashboard", "report", "digital", "crawl", "alert", "standardized", "recurring", "checkout", "plugin", "cli", "no-code"]
|
|
|
|
|
|
|
|
negative_causes = {
|
|
|
|
|
|
|
|
"SALES": ["sales call", "enterprise sales", "procurement", "relationship"],
|
|
|
|
|
|
|
|
"DOMAIN_EXPERTISE": ["expert", "clinical", "medical", "licensed", "engineer review"],
|
|
|
|
|
|
|
|
"CUSTOMER_TRUST": ["trust", "advisor", "consultant"],
|
|
|
|
|
|
|
|
"RELATIONSHIP_MANAGEMENT": ["account management", "relationship"],
|
|
|
|
|
|
|
|
"MANUAL_QA": ["manual qa", "manual review", "human review"],
|
|
|
|
|
|
|
|
"REGULATORY_SIGNOFF": ["regulatory sign-off", "compliance signoff", "approval"],
|
|
|
|
|
|
|
|
"CUSTOM_IMPLEMENTATION": ["custom implementation", "integration", "bespoke"],
|
|
|
|
|
|
|
|
"SUPPORT_ESCALATION": ["high-touch support", "white glove"],
|
|
|
|
|
|
|
|
"OFFLINE_ACTIVITY": ["onsite", "offline", "in person"],
|
|
|
|
|
|
|
|
"LEGAL_JUDGMENT": ["legal judgment", "legal advice", "lawyer"],
|
|
|
|
|
|
|
|
"NEGOTIATION": ["negotiation"],
|
|
|
|
|
|
|
|
"PERSONAL_BRAND": ["personal brand", "founder credibility"],
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
causes = [cause for cause, terms in negative_causes.items() if any(term in text for term in terms)]
|
|
|
|
|
|
|
|
score = 45 + min(30, sum(5 for term in positive_terms if term in text)) - min(45, len(causes) * 8)
|
|
|
|
|
|
|
|
ai = self.ai_leverage_score(proposal)
|
|
|
|
|
|
|
|
platform = self.platformization_potential(proposal)
|
|
|
|
|
|
|
|
score += 10 if ai >= 70 else 0
|
|
|
|
|
|
|
|
score += 10 if platform >= 70 else 0
|
|
|
|
|
|
|
|
score = max(0, min(100, score))
|
|
|
|
|
|
|
|
minutes = 5 if score >= 90 else 15 if score >= 80 else 30 if score >= 70 else 90 if score >= 50 else 240
|
|
|
|
|
|
|
|
dependency = FounderDependencyLevel.NONE if minutes <= 5 and not causes else FounderDependencyLevel.LOW if minutes <= 30 and len(causes) <= 1 else FounderDependencyLevel.MEDIUM if minutes <= 90 else FounderDependencyLevel.HIGH if minutes <= 240 else FounderDependencyLevel.CRITICAL
|
|
|
|
|
|
|
|
if score >= 80 and dependency in {FounderDependencyLevel.NONE, FounderDependencyLevel.LOW} and ai >= 70 and platform >= 70:
|
|
|
|
|
|
|
|
gate = AutonomousGateResult.AUTONOMOUS_ELIGIBLE
|
|
|
|
|
|
|
|
track = VentureTrack.AUTONOMOUS
|
|
|
|
|
|
|
|
elif score >= 70 and dependency in {FounderDependencyLevel.LOW, FounderDependencyLevel.MEDIUM}:
|
|
|
|
|
|
|
|
gate = AutonomousGateResult.AUTONOMOUS_BORDERLINE
|
|
|
|
|
|
|
|
track = VentureTrack.AUTONOMOUS
|
|
|
|
|
|
|
|
elif score >= 45:
|
|
|
|
|
|
|
|
gate = AutonomousGateResult.ASSISTED_ONLY
|
|
|
|
|
|
|
|
track = VentureTrack.ASSISTED
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
gate = AutonomousGateResult.REJECT_OPERABILITY
|
|
|
|
|
|
|
|
track = VentureTrack.ASSISTED
|
|
|
|
|
|
|
|
loop = self.autonomous_operating_loop(proposal, gate)
|
|
|
|
|
|
|
|
contract = self.autonomous_fulfillment_contract(proposal)
|
|
|
|
|
|
|
|
assessment, _ = AutonomousOperabilityAssessment.objects.update_or_create(
|
|
|
|
|
|
|
|
proposal=proposal,
|
|
|
|
|
|
|
|
defaults={"venture_track": track, "gate_result": gate, "autonomous_operability_score": score, "commercial_score": self.commercial_score(proposal), "founder_dependency": dependency, "dependency_causes": causes, "minutes_per_week_human": minutes, "human_actions_required": contract["escalation"], "human_action_categories": ["LEGAL_SETUP", "SPEND_APPROVAL", "EXCEPTION_ESCALATION"] if minutes <= 30 else causes, "operating_loop": loop, "fulfillment_contract": contract, "validation_offer": self.validation_offer(proposal), "end_state_business_model": self.end_state_business_model(proposal), "structural_blockers": causes, "platform_blockers": self.platform_blockers(proposal), "component_scores": {"customer_acquisition_autonomy": loop["discover"]["score"], "onboarding_autonomy": loop["onboard"]["score"], "fulfillment_autonomy": loop["fulfill"]["score"], "support_autonomy": loop["support"]["score"], "billing_payment_autonomy": loop["checkout"]["score"], "quality_verification": loop["verify"]["score"], "repeatability": 90 if "recurring" in text or "subscription" in text else 65}, "rationale": f"{gate}: score {score}, dependency {dependency}, causes {', '.join(causes) or 'none'}."},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
proposal.metadata = {**proposal.metadata, "venture_track": track, "autonomous_operability_score": score, "founder_dependency": dependency, "autonomous_gate_result": gate, "minutes_per_week_human": minutes, "validation_offer": assessment.validation_offer, "end_state_business_model": assessment.end_state_business_model}
|
|
|
|
|
|
|
|
proposal.save(update_fields=["metadata", "updated_at"])
|
|
|
|
|
|
|
|
self._artifact(proposal, proposal.mandate, "AUTONOMOUS_OPERABILITY_REPORT", "Autonomous Operability Report", {"score": score, "gate_result": gate, "founder_dependency": dependency, "dependency_causes": causes, "operating_loop": loop, "fulfillment_contract": contract, "validation_offer": assessment.validation_offer, "end_state_business_model": assessment.end_state_business_model, "platform_blockers": assessment.platform_blockers, "structural_blockers": assessment.structural_blockers}, self._readable_memo({"score": score, "gate_result": gate, "founder_dependency": dependency, "dependency_causes": causes, "operating_loop": loop}), "Autonomous Operability IC")
|
|
|
|
|
|
|
|
return assessment
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def commercial_score(self, proposal: CompanyProposal) -> float:
|
|
|
|
|
|
|
|
diligence = proposal.ic_diligence.order_by("-created_at").first()
|
|
|
|
|
|
|
|
return float(diligence.decision.composite_score) if diligence and hasattr(diligence, "decision") else 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def autonomous_operating_loop(self, proposal: CompanyProposal, gate: str) -> dict[str, dict[str, Any]]:
|
|
|
|
|
|
|
|
automated = gate in {AutonomousGateResult.AUTONOMOUS_ELIGIBLE, AutonomousGateResult.AUTONOMOUS_BORDERLINE}
|
|
|
|
|
|
|
|
state = "AUTONOMOUS" if automated else "HUMAN_ROUTINE_REQUIRED"
|
|
|
|
|
|
|
|
score = 90 if automated else 35
|
|
|
|
|
|
|
|
return {stage: {"capability": "AVAILABLE" if stage in {"discover", "checkout", "deliver", "measure"} else "PARTIAL", "operation": state, "score": score} for stage in ["discover", "qualify", "acquire", "checkout", "onboard", "fulfill", "verify", "deliver", "support", "measure", "retain_upsell"]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def autonomous_fulfillment_contract(self, proposal: CompanyProposal) -> dict[str, Any]:
|
|
|
|
|
|
|
|
return {"customer_input": "Customer supplies a URL, file, repository, document, dataset, or narrow workflow input through a self-service intake.", "artifex_process": "Artifex agents run retrieval, analysis/transformation, structured QA, report generation, and delivery workflows.", "customer_output": proposal.proposed_solution, "quality_verification": "Automated checks validate schema completeness, source links, threshold scores, and known failure conditions before delivery.", "failure_recovery": "If verification fails, rerun once with stricter constraints; unresolved failures create exceptional human escalation.", "billing_event": "Payment is collected at checkout before one-off validation delivery or at subscription activation.", "support_model": "Agent-authored help, status updates, and retry guidance; human handles only exceptional safety/legal/account issues.", "escalation": ["material legal commitment", "irreversible spend", "security/safety exception", "verification repeatedly fails"]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def validation_offer(self, proposal: CompanyProposal) -> dict[str, Any]:
|
|
|
|
|
|
|
|
return {"offer": "$49-$99 fixed-scope automated validation output", "price": "$49-$99", "proof": "willingness-to-pay for a standardized digital result before full product build", "aligned_with_end_state": True}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def end_state_business_model(self, proposal: CompanyProposal) -> dict[str, Any]:
|
|
|
|
|
|
|
|
return {"model": proposal.business_model, "likely_pricing": proposal.pricing_hypothesis, "recurring_path": "subscription, usage, or repeat purchase if validation demand repeats"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def platform_blockers(self, proposal: CompanyProposal) -> list[str]:
|
|
|
|
|
|
|
|
return ["STRIPE_IMPLEMENTATION_MISSING", "SUBDOMAIN_DEPLOYMENT", "EMAIL_DELIVERY", "SUPPORT_INBOX"]
|
|
|
|
|
|
|
|
|
|
|
|
def portfolio_ic(self, cohort: VentureCohort) -> PortfolioICReview:
|
|
|
|
def portfolio_ic(self, cohort: VentureCohort) -> PortfolioICReview:
|
|
|
|
|
|
|
|
self.assess_autonomous_operability_for_cohort(cohort)
|
|
|
|
preliminary_rows = self.portfolio_ranking_rows(cohort)
|
|
|
|
preliminary_rows = self.portfolio_ranking_rows(cohort)
|
|
|
|
preliminary_top_5 = preliminary_rows[:5]
|
|
|
|
preliminary_top_5 = preliminary_rows[:5]
|
|
|
|
before_coverage = {}
|
|
|
|
before_coverage = {}
|
|
|
|
@ -452,15 +543,17 @@ class VentureDiscoveryService:
|
|
|
|
rows = self.portfolio_ranking_rows(cohort)
|
|
|
|
rows = self.portfolio_ranking_rows(cohort)
|
|
|
|
preliminary_rank_by_id = {row["proposal_id"]: index + 1 for index, row in enumerate(preliminary_rows)}
|
|
|
|
preliminary_rank_by_id = {row["proposal_id"]: index + 1 for index, row in enumerate(preliminary_rows)}
|
|
|
|
ranking_changes = [{"proposal_id": row["proposal_id"], "company": row["company"], "preliminary_rank": preliminary_rank_by_id.get(row["proposal_id"]), "final_rank": index + 1} for index, row in enumerate(rows) if preliminary_rank_by_id.get(row["proposal_id"]) != index + 1]
|
|
|
|
ranking_changes = [{"proposal_id": row["proposal_id"], "company": row["company"], "preliminary_rank": preliminary_rank_by_id.get(row["proposal_id"]), "final_rank": index + 1} for index, row in enumerate(rows) if preliminary_rank_by_id.get(row["proposal_id"]) != index + 1]
|
|
|
|
|
|
|
|
top_3 = [row for row in rows if row.get("autonomous_gate_result") == AutonomousGateResult.AUTONOMOUS_ELIGIBLE][:3]
|
|
|
|
|
|
|
|
top_3_ids = {row["proposal_id"] for row in top_3}
|
|
|
|
for rank, row in enumerate(rows, start=1):
|
|
|
|
for rank, row in enumerate(rows, start=1):
|
|
|
|
member = cohort.members.get(proposal_id=row["proposal_id"])
|
|
|
|
member = cohort.members.get(proposal_id=row["proposal_id"])
|
|
|
|
member.rank = rank
|
|
|
|
member.rank = rank
|
|
|
|
member.is_top_3 = rank <= 3
|
|
|
|
member.is_top_3 = row["proposal_id"] in top_3_ids
|
|
|
|
member.portfolio_score = row["portfolio_score"]
|
|
|
|
member.portfolio_score = row["portfolio_score"]
|
|
|
|
member.save(update_fields=["rank", "is_top_3", "portfolio_score", "updated_at"])
|
|
|
|
member.save(update_fields=["rank", "is_top_3", "portfolio_score", "updated_at"])
|
|
|
|
row["rank"] = rank
|
|
|
|
row["rank"] = rank
|
|
|
|
concentration = self._portfolio_concentration(cohort)
|
|
|
|
concentration = self._portfolio_concentration(cohort)
|
|
|
|
review, _ = PortfolioICReview.objects.update_or_create(cohort=cohort, defaults={"rankings": rows, "top_3": rows[:3], "concentration": concentration, "metadata": {"no_funding": True, "preliminary_rankings": preliminary_rows, "preliminary_top_5": preliminary_top_5, "finalist_research_coverage_before": before_coverage, "finalist_research_coverage_after": after_coverage, "ranking_changes_after_deep_research": ranking_changes}})
|
|
|
|
review, _ = PortfolioICReview.objects.update_or_create(cohort=cohort, defaults={"rankings": rows, "top_3": top_3, "concentration": concentration, "metadata": {"no_funding": True, "preliminary_rankings": preliminary_rows, "preliminary_top_5": preliminary_top_5, "finalist_research_coverage_before": before_coverage, "finalist_research_coverage_after": after_coverage, "ranking_changes_after_deep_research": ranking_changes, "autonomous_top_3_shortfall": max(0, 3 - len(top_3))}})
|
|
|
|
cohort.metrics = {**cohort.metrics, "finalist_deep_research_count": len(preliminary_top_5), "finalist_deep_research_runtime_seconds": round(time.monotonic() - deep_started, 2), "finalist_research_coverage_before": before_coverage, "finalist_research_coverage_after": after_coverage, "ranking_changes_after_deep_research": ranking_changes}
|
|
|
|
cohort.metrics = {**cohort.metrics, "finalist_deep_research_count": len(preliminary_top_5), "finalist_deep_research_runtime_seconds": round(time.monotonic() - deep_started, 2), "finalist_research_coverage_before": before_coverage, "finalist_research_coverage_after": after_coverage, "ranking_changes_after_deep_research": ranking_changes}
|
|
|
|
cohort.status = "PORTFOLIO_IC_COMPLETE"
|
|
|
|
cohort.status = "PORTFOLIO_IC_COMPLETE"
|
|
|
|
cohort.save(update_fields=["metrics", "status", "updated_at"])
|
|
|
|
cohort.save(update_fields=["metrics", "status", "updated_at"])
|
|
|
|
@ -475,11 +568,13 @@ class VentureDiscoveryService:
|
|
|
|
for member in cohort.members.select_related("proposal"):
|
|
|
|
for member in cohort.members.select_related("proposal"):
|
|
|
|
proposal = member.proposal
|
|
|
|
proposal = member.proposal
|
|
|
|
decision = proposal.ic_diligence.order_by("-created_at").first().decision
|
|
|
|
decision = proposal.ic_diligence.order_by("-created_at").first().decision
|
|
|
|
|
|
|
|
assessment = self.assess_autonomous_operability(proposal)
|
|
|
|
capability_burden = proposal.capability_requirements.filter(status=CapabilityStatus.MISSING).count()
|
|
|
|
capability_burden = proposal.capability_requirements.filter(status=CapabilityStatus.MISSING).count()
|
|
|
|
concentration_penalty = self._generic_concentration_penalty(proposal)
|
|
|
|
concentration_penalty = self._generic_concentration_penalty(proposal)
|
|
|
|
ai_bonus = (decision.component_scores.get("AI Leverage", 0) * 0.08) + (decision.component_scores.get("Platformization Potential", 0) * 0.06)
|
|
|
|
ai_bonus = (decision.component_scores.get("AI Leverage", 0) * 0.08) + (decision.component_scores.get("Platformization Potential", 0) * 0.06)
|
|
|
|
score = round(decision.composite_score + decision.probability_500_within_30_days * 0.2 + ai_bonus - capability_burden * 1.5 - collision_risk[str(proposal.id)] * 2 - concentration_penalty, 1)
|
|
|
|
autonomy_penalty = 35 if assessment.gate_result == AutonomousGateResult.ASSISTED_ONLY else 70 if assessment.gate_result == AutonomousGateResult.REJECT_OPERABILITY else 12 if assessment.gate_result == AutonomousGateResult.AUTONOMOUS_BORDERLINE else 0
|
|
|
|
rows.append({"proposal_id": str(proposal.id), "company": proposal.title, "thesis": proposal.pitch.get("One-line thesis", proposal.description), "ic_score": decision.composite_score, "probability": decision.probability_500_within_30_days, "decision": decision.decision, "evidence_tier": decision.evidence_tier, "initial_tranche": str(decision.initial_tranche or "0"), "ai_leverage": decision.component_scores.get("AI Leverage", 0), "platformization_potential": decision.component_scores.get("Platformization Potential", 0), "portfolio_score": score, "capability_burden": capability_burden, "collision_risk": collision_risk[str(proposal.id)], "concentration_penalty": concentration_penalty})
|
|
|
|
score = round(decision.composite_score * 0.45 + assessment.autonomous_operability_score * 0.45 + decision.probability_500_within_30_days * 0.12 + ai_bonus - capability_burden * 1.5 - collision_risk[str(proposal.id)] * 2 - concentration_penalty - autonomy_penalty, 1)
|
|
|
|
|
|
|
|
rows.append({"proposal_id": str(proposal.id), "company": proposal.title, "thesis": proposal.pitch.get("One-line thesis", proposal.description), "ic_score": decision.composite_score, "commercial_score": assessment.commercial_score, "autonomy_score": assessment.autonomous_operability_score, "autonomous_gate_result": assessment.gate_result, "founder_dependency": assessment.founder_dependency, "minutes_per_week_human": assessment.minutes_per_week_human, "probability": decision.probability_500_within_30_days, "decision": decision.decision, "evidence_tier": decision.evidence_tier, "initial_tranche": str(decision.initial_tranche or "0"), "ai_leverage": decision.component_scores.get("AI Leverage", 0), "platformization_potential": decision.component_scores.get("Platformization Potential", 0), "portfolio_score": score, "capability_burden": capability_burden, "collision_risk": collision_risk[str(proposal.id)], "concentration_penalty": concentration_penalty})
|
|
|
|
rows.sort(key=lambda item: item["portfolio_score"], reverse=True)
|
|
|
|
rows.sort(key=lambda item: item["portfolio_score"], reverse=True)
|
|
|
|
return rows
|
|
|
|
return rows
|
|
|
|
|
|
|
|
|
|
|
|
@ -547,9 +642,15 @@ class VentureDiscoveryService:
|
|
|
|
thesis.first_seen_cohort = thesis.first_seen_cohort or cohort
|
|
|
|
thesis.first_seen_cohort = thesis.first_seen_cohort or cohort
|
|
|
|
thesis.last_seen_cohort = cohort
|
|
|
|
thesis.last_seen_cohort = cohort
|
|
|
|
thesis.status = analysis.status_recommendation
|
|
|
|
thesis.status = analysis.status_recommendation
|
|
|
|
|
|
|
|
best_assessment = self.assess_autonomous_operability(best[0]) if best else None
|
|
|
|
|
|
|
|
if best_assessment:
|
|
|
|
|
|
|
|
thesis.venture_track = best_assessment.venture_track
|
|
|
|
|
|
|
|
thesis.best_autonomous_operability_score = max(float(thesis.best_autonomous_operability_score or 0.0), best_assessment.autonomous_operability_score)
|
|
|
|
|
|
|
|
thesis.best_founder_dependency = best_assessment.founder_dependency
|
|
|
|
|
|
|
|
thesis.autonomous_candidate_status = AutonomousCandidateStatus.AUTONOMOUS_CANDIDATE if best_assessment.gate_result == AutonomousGateResult.AUTONOMOUS_ELIGIBLE else AutonomousCandidateStatus.ASSISTED_CANDIDATE if best_assessment.gate_result == AutonomousGateResult.ASSISTED_ONLY else AutonomousCandidateStatus.AUTONOMOUS_REJECTED if best_assessment.gate_result == AutonomousGateResult.REJECT_OPERABILITY else AutonomousCandidateStatus.NOT_ASSESSED
|
|
|
|
thesis.metadata = {**thesis.metadata, "last_saturation_rationale": analysis.rationale, "last_reopen_reason": thesis.metadata.get("last_reopen_reason", "")}
|
|
|
|
thesis.metadata = {**thesis.metadata, "last_saturation_rationale": analysis.rationale, "last_reopen_reason": thesis.metadata.get("last_reopen_reason", "")}
|
|
|
|
thesis.save(update_fields=["proposal_count", "best_ic_score", "best_company", "first_seen_cohort", "last_seen_cohort", "status", "metadata", "updated_at"])
|
|
|
|
thesis.save(update_fields=["proposal_count", "best_ic_score", "best_company", "first_seen_cohort", "last_seen_cohort", "status", "venture_track", "best_autonomous_operability_score", "best_founder_dependency", "autonomous_candidate_status", "metadata", "updated_at"])
|
|
|
|
updates.append({"thesis": thesis.canonical_name, "status": thesis.status, "proposal_count": thesis.proposal_count, "best_ic_score": thesis.best_ic_score})
|
|
|
|
updates.append({"thesis": thesis.canonical_name, "status": thesis.status, "proposal_count": thesis.proposal_count, "best_ic_score": thesis.best_ic_score, "venture_track": thesis.venture_track, "best_autonomous_operability_score": thesis.best_autonomous_operability_score, "best_founder_dependency": thesis.best_founder_dependency, "autonomous_candidate_status": thesis.autonomous_candidate_status})
|
|
|
|
cohort.metadata = {**cohort.metadata, "thesis_registry_after": self.registry_snapshot()}
|
|
|
|
cohort.metadata = {**cohort.metadata, "thesis_registry_after": self.registry_snapshot()}
|
|
|
|
cohort.save(update_fields=["metadata", "updated_at"])
|
|
|
|
cohort.save(update_fields=["metadata", "updated_at"])
|
|
|
|
return updates
|
|
|
|
return updates
|
|
|
|
@ -587,15 +688,20 @@ class VentureDiscoveryService:
|
|
|
|
review = cohort.portfolio_review
|
|
|
|
review = cohort.portfolio_review
|
|
|
|
ideation_mandate = getattr(cohort, "ideation_mandate", None)
|
|
|
|
ideation_mandate = getattr(cohort, "ideation_mandate", None)
|
|
|
|
clusters = [{"company": cluster.proposal.title, "portfolio_thesis": cluster.portfolio_thesis.canonical_name if cluster.portfolio_thesis else "", "classification": cluster.classification, "similarity_score": cluster.similarity_score, "explanation": cluster.explanation} for cluster in cohort.thesis_clusters.select_related("proposal", "portfolio_thesis").order_by("created_at")]
|
|
|
|
clusters = [{"company": cluster.proposal.title, "portfolio_thesis": cluster.portfolio_thesis.canonical_name if cluster.portfolio_thesis else "", "classification": cluster.classification, "similarity_score": cluster.similarity_score, "explanation": cluster.explanation} for cluster in cohort.thesis_clusters.select_related("proposal", "portfolio_thesis").order_by("created_at")]
|
|
|
|
|
|
|
|
autonomy = [self.autonomy_report_row(member.proposal) for member in cohort.members.select_related("proposal").order_by("rank", "created_at")]
|
|
|
|
generation_rejections = [{"slot_index": rejection.slot_index, "attempt": rejection.attempt, "decision": rejection.decision, "reason": rejection.reason, "candidate_title": rejection.candidate.get("title", "") if isinstance(rejection.candidate, dict) else "", "similarity_score": rejection.similarity_score} for rejection in cohort.generation_rejections.order_by("slot_index", "attempt")]
|
|
|
|
generation_rejections = [{"slot_index": rejection.slot_index, "attempt": rejection.attempt, "decision": rejection.decision, "reason": rejection.reason, "candidate_title": rejection.candidate.get("title", "") if isinstance(rejection.candidate, dict) else "", "similarity_score": rejection.similarity_score} for rejection in cohort.generation_rejections.order_by("slot_index", "attempt")]
|
|
|
|
saturation = [{"thesis": analysis.portfolio_thesis.canonical_name, "proposal_count": analysis.proposal_count, "best_ic_score": analysis.best_ic_score, "score_spread": analysis.score_spread, "status_recommendation": analysis.status_recommendation, "rationale": analysis.rationale} for analysis in cohort.saturation_analyses.select_related("portfolio_thesis").order_by("-proposal_count")]
|
|
|
|
saturation = [{"thesis": analysis.portfolio_thesis.canonical_name, "proposal_count": analysis.proposal_count, "best_ic_score": analysis.best_ic_score, "score_spread": analysis.score_spread, "status_recommendation": analysis.status_recommendation, "rationale": analysis.rationale} for analysis in cohort.saturation_analyses.select_related("portfolio_thesis").order_by("-proposal_count")]
|
|
|
|
accepted_count = cohort.members.count()
|
|
|
|
accepted_count = cohort.members.count()
|
|
|
|
content = {"cohort_id": cohort.cohort_id, "mandate": cohort.mandate.objective, "accepted_count": accepted_count, "requested_count": cohort.cohort_size, "ideation_mandate": self.ideation_mandate_payload(ideation_mandate) if ideation_mandate else {}, "hard_exclusions": ideation_mandate.hard_exclusions if ideation_mandate else [], "soft_exclusions": ideation_mandate.soft_exclusions if ideation_mandate else [], "search_territories": ideation_mandate.opportunity_territories if ideation_mandate else [], "generation_rejections": generation_rejections, "thesis_registry_before": (ideation_mandate.registry_snapshot.get("theses", []) if ideation_mandate else []), "thesis_registry_after": cohort.metadata.get("thesis_registry_after", []), "thesis_clusters": clusters, "saturation_analysis": saturation, "idea_diversity_metrics": self.diversity_metrics(cohort), "runtime": cohort.metrics, "total_spend": 0, "customer_outreach": "none", "rankings": review.rankings, "top_3": review.top_3, "collisions": self._collision_summary(cohort), "portfolio_concentration": review.concentration, "capability_demand": review.capability_demand, "top_3_capability_gaps": review.top_3_capability_gaps, "recommended_build_priorities": review.recommended_build_priorities}
|
|
|
|
content = {"cohort_id": cohort.cohort_id, "mandate": cohort.mandate.objective, "accepted_count": accepted_count, "requested_count": cohort.cohort_size, "venture_track": VentureTrack.AUTONOMOUS, "autonomous_operability_report": autonomy, "assisted_only_companies": [row for row in autonomy if row.get("gate_result") == AutonomousGateResult.ASSISTED_ONLY], "ideation_mandate": self.ideation_mandate_payload(ideation_mandate) if ideation_mandate else {}, "hard_exclusions": ideation_mandate.hard_exclusions if ideation_mandate else [], "soft_exclusions": ideation_mandate.soft_exclusions if ideation_mandate else [], "search_territories": ideation_mandate.opportunity_territories if ideation_mandate else [], "generation_rejections": generation_rejections, "thesis_registry_before": (ideation_mandate.registry_snapshot.get("theses", []) if ideation_mandate else []), "thesis_registry_after": cohort.metadata.get("thesis_registry_after", []), "thesis_clusters": clusters, "saturation_analysis": saturation, "idea_diversity_metrics": self.diversity_metrics(cohort), "runtime": cohort.metrics, "total_spend": 0, "customer_outreach": "none", "rankings": review.rankings, "top_3": review.top_3, "collisions": self._collision_summary(cohort), "portfolio_concentration": review.concentration, "capability_demand": review.capability_demand, "top_3_capability_gaps": review.top_3_capability_gaps, "recommended_build_priorities": review.recommended_build_priorities}
|
|
|
|
readable = self._readable_cohort_report(content)
|
|
|
|
readable = self._readable_cohort_report(content)
|
|
|
|
cohort.status = "COMPLETE" if accepted_count == cohort.cohort_size else "PARTIAL_COMPLETE"
|
|
|
|
cohort.status = "COMPLETE" if accepted_count == cohort.cohort_size else "PARTIAL_COMPLETE"
|
|
|
|
cohort.save(update_fields=["status", "updated_at"])
|
|
|
|
cohort.save(update_fields=["status", "updated_at"])
|
|
|
|
return VentureArtifact.objects.create(mandate=cohort.mandate, graph_run=cohort.graph_run, artifact_type="VENTURE_DISCOVERY_COHORT_REPORT", name="Venture Discovery Cohort Report", content=content, readable=readable, generated_by="Portfolio IC")
|
|
|
|
return VentureArtifact.objects.create(mandate=cohort.mandate, graph_run=cohort.graph_run, artifact_type="VENTURE_DISCOVERY_COHORT_REPORT", name="Venture Discovery Cohort Report", content=content, readable=readable, generated_by="Portfolio IC")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def autonomy_report_row(self, proposal: CompanyProposal) -> dict[str, Any]:
|
|
|
|
|
|
|
|
assessment = self.assess_autonomous_operability(proposal)
|
|
|
|
|
|
|
|
return {"company": proposal.title, "venture_track": assessment.venture_track, "gate_result": assessment.gate_result, "commercial_score": assessment.commercial_score, "autonomous_operability_score": assessment.autonomous_operability_score, "founder_dependency": assessment.founder_dependency, "dependency_causes": assessment.dependency_causes, "minutes_per_week_human": assessment.minutes_per_week_human, "human_actions_required": assessment.human_actions_required, "operating_loop": assessment.operating_loop, "fulfillment_contract": assessment.fulfillment_contract, "validation_offer": assessment.validation_offer, "end_state_business_model": assessment.end_state_business_model, "structural_blockers": assessment.structural_blockers, "platform_blockers": assessment.platform_blockers, "component_scores": assessment.component_scores, "rationale": assessment.rationale}
|
|
|
|
|
|
|
|
|
|
|
|
def calibrate_probability(self, proposal: CompanyProposal, *, raw_probability: float) -> dict[str, Any]:
|
|
|
|
def calibrate_probability(self, proposal: CompanyProposal, *, raw_probability: float) -> dict[str, Any]:
|
|
|
|
tier = proposal.evidence_tier or EvidenceTier.TIER_0_THESIS
|
|
|
|
tier = proposal.evidence_tier or EvidenceTier.TIER_0_THESIS
|
|
|
|
ceiling = float(EVIDENCE_CEILINGS.get(tier, 35))
|
|
|
|
ceiling = float(EVIDENCE_CEILINGS.get(tier, 35))
|
|
|
|
@ -912,7 +1018,7 @@ class VentureDiscoveryService:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
slot = f" Independent cohort slot: {ideation_index}. Do not use or imitate other cohort ideas; no other ideas are visible." if ideation_index else ""
|
|
|
|
slot = f" Independent cohort slot: {ideation_index}. Do not use or imitate other cohort ideas; no other ideas are visible." if ideation_index else ""
|
|
|
|
context = ideation_context or {}
|
|
|
|
context = ideation_context or {}
|
|
|
|
response = self.router.complete(ModelRequestContract(purpose=ModelCapability.PLANNING, model_hint=self.ideation_model_hint, prompt="Generate exactly ONE startup idea for Venture Discovery V0.3. Return a single JSON object, not a list. Respect no spend and no outreach in V0. Keep the $50 to $500 in 30 days mandate. Prefer AI-native businesses where Artifex can deliver most value using agents/local inference, but do not allow 'uses AI' to substitute for real customer pain. Prefer AI wrapper platforms, agentic workflow products, vertical copilots, AI-enabled services with low human labor, developer/AI infrastructure, intelligence products, automation products, service-to-platform paths, recurring revenue, and owned-compute leverage. DO NOT propose companies substantially equivalent to these hard-excluded thesis areas, including semantic variants: " + json.dumps(context.get("hard_exclusions", [])) + ". Soft exclusions require material differentiation and an explicit exception rationale: " + json.dumps(context.get("soft_exclusions", [])) + ". Search territory for this slot: " + str(context.get("territory", "OPEN_CATEGORY")) + ". This is a creative search constraint, not a fixed solution. 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, and optional exception_rationale." + slot + " Mandate: " + json.dumps({"objective": mandate.objective, "constraints": mandate.constraints, "optimization_targets": mandate.optimization_targets, "ai_native_policy": AI_NATIVE_POLICY, "ideation_brief": context.get("brief", {})})))
|
|
|
|
response = self.router.complete(ModelRequestContract(purpose=ModelCapability.PLANNING, model_hint=self.ideation_model_hint, prompt="Generate exactly ONE startup idea for an AUTONOMOUS Venture Discovery cohort. Return a single JSON object, not a list. The business must be operable by Artifex with <=30 minutes/week routine human involvement. Prefer archetypes: " + json.dumps(AUTONOMOUS_ARCHETYPES) + ". Require concrete digital operating loop: discover lead/user -> qualify -> acquire -> checkout -> onboard -> fulfill -> verify -> deliver -> support -> measure -> retain/upsell. Include validation_offer and end_state_business_model as separate fields. Include fulfillment_contract with customer_input, artifex_process, customer_output, quality_verification, failure_recovery, billing_event, support_model, escalation. Avoid enterprise procurement, founder-led calls, bespoke consulting, legal/medical judgment, offline fulfillment, and custom implementation. Respect no spend and no outreach in V0. Keep the $50 to $500 in 30 days mandate. Prefer AI-native businesses where Artifex can deliver most value using agents/local inference, but do not allow 'uses AI' to substitute for real customer pain. DO NOT propose companies substantially equivalent to these hard-excluded thesis areas, including semantic variants: " + json.dumps(context.get("hard_exclusions", [])) + ". Soft exclusions require material differentiation and an explicit exception rationale: " + json.dumps(context.get("soft_exclusions", [])) + ". Search territory for this slot: " + str(context.get("territory", "OPEN_CATEGORY")) + ". 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, minutes_per_week_human, human_actions_required, and optional exception_rationale." + slot + " Mandate: " + json.dumps({"objective": mandate.objective, "constraints": mandate.constraints, "optimization_targets": mandate.optimization_targets, "ai_native_policy": AI_NATIVE_POLICY, "ideation_brief": context.get("brief", {})})))
|
|
|
|
parsed = extract_json_object(response.content)
|
|
|
|
parsed = extract_json_object(response.content)
|
|
|
|
if isinstance(parsed, dict) and parsed.get("title"):
|
|
|
|
if isinstance(parsed, dict) and parsed.get("title"):
|
|
|
|
return self._normalize_payload(parsed), self.ideation_model_hint
|
|
|
|
return self._normalize_payload(parsed), self.ideation_model_hint
|
|
|
|
@ -933,6 +1039,9 @@ class VentureDiscoveryService:
|
|
|
|
normalized["major_risks"] = self._as_list(normalized.get("major_risks"))
|
|
|
|
normalized["major_risks"] = self._as_list(normalized.get("major_risks"))
|
|
|
|
if payload.get("exception_rationale"):
|
|
|
|
if payload.get("exception_rationale"):
|
|
|
|
normalized["exception_rationale"] = payload["exception_rationale"]
|
|
|
|
normalized["exception_rationale"] = payload["exception_rationale"]
|
|
|
|
|
|
|
|
for key in ["validation_offer", "end_state_business_model", "fulfillment_contract", "minutes_per_week_human", "human_actions_required"]:
|
|
|
|
|
|
|
|
if key in payload:
|
|
|
|
|
|
|
|
normalized[key] = payload[key]
|
|
|
|
return normalized
|
|
|
|
return normalized
|
|
|
|
|
|
|
|
|
|
|
|
def _normalize_research(self, payload: dict[str, Any], required: list[str]) -> dict[str, Any]:
|
|
|
|
def _normalize_research(self, payload: dict[str, Any], required: list[str]) -> dict[str, Any]:
|
|
|
|
@ -945,7 +1054,9 @@ class VentureDiscoveryService:
|
|
|
|
|
|
|
|
|
|
|
|
def _searxng_sources(self, proposal: CompanyProposal, required: list[str], *, depth: str = "light") -> list[dict[str, Any]]:
|
|
|
|
def _searxng_sources(self, proposal: CompanyProposal, required: list[str], *, depth: str = "light") -> list[dict[str, Any]]:
|
|
|
|
client = self.search_client or SearxngSearchClient.from_resources()
|
|
|
|
client = self.search_client or SearxngSearchClient.from_resources()
|
|
|
|
|
|
|
|
self._last_search_diagnostics = {"queries": [], "errors": []}
|
|
|
|
if client is None:
|
|
|
|
if client is None:
|
|
|
|
|
|
|
|
self._last_search_diagnostics["errors"].append("no active searxng resource")
|
|
|
|
return []
|
|
|
|
return []
|
|
|
|
sources = []
|
|
|
|
sources = []
|
|
|
|
limit = 5 if depth == "deep" else 3
|
|
|
|
limit = 5 if depth == "deep" else 3
|
|
|
|
@ -959,11 +1070,34 @@ class VentureDiscoveryService:
|
|
|
|
for category in required:
|
|
|
|
for category in required:
|
|
|
|
query = f"{proposal.title} {proposal.problem[:120]} {proposal.target_customer} {query_terms.get(category, category)}"
|
|
|
|
query = f"{proposal.title} {proposal.problem[:120]} {proposal.target_customer} {query_terms.get(category, category)}"
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
sources.extend(client.search(query, category=category, limit=limit))
|
|
|
|
results = self._search_client_results(client, query, category=category, limit=limit)
|
|
|
|
except Exception:
|
|
|
|
if not results:
|
|
|
|
|
|
|
|
fallback_query = f"{proposal.title} {query_terms.get(category, category)}"
|
|
|
|
|
|
|
|
results = self._search_client_results(client, fallback_query, category=category, limit=limit)
|
|
|
|
|
|
|
|
if not results:
|
|
|
|
|
|
|
|
broad_query = f"{proposal.target_customer} {query_terms.get(category, category)}"
|
|
|
|
|
|
|
|
results = self._search_client_results(client, broad_query, category=category, limit=limit)
|
|
|
|
|
|
|
|
sources.extend(results)
|
|
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
|
|
self._last_search_diagnostics["errors"].append(f"{category}: {exc}")
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
return sources[:25 if depth == "deep" else 15]
|
|
|
|
return sources[:25 if depth == "deep" else 15]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _search_client_results(self, client: Any, query: str, *, category: str, limit: int) -> list[dict[str, Any]]:
|
|
|
|
|
|
|
|
if hasattr(client, "search_payload"):
|
|
|
|
|
|
|
|
payload = client.search_payload(query)
|
|
|
|
|
|
|
|
raw_results = payload.get("results", []) if isinstance(payload, dict) else []
|
|
|
|
|
|
|
|
unresponsive = payload.get("unresponsive_engines", []) if isinstance(payload, dict) else []
|
|
|
|
|
|
|
|
self._last_search_diagnostics["queries"].append({"category": category, "query": query, "result_count": len(raw_results), "unresponsive_engines": unresponsive})
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
|
|
{"type": "public_web", "source": "searxng", "url": str(item["url"]), "title": str(item.get("title", "")), "category": category, "summary": str(item.get("content", item.get("snippet", ""))), "fallback_evidence": False}
|
|
|
|
|
|
|
|
for item in raw_results[: limit * 4]
|
|
|
|
|
|
|
|
if isinstance(item, dict) and item.get("url")
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
results = client.search(query, category=category, limit=limit)
|
|
|
|
|
|
|
|
self._last_search_diagnostics["queries"].append({"category": category, "query": query, "result_count": len(results)})
|
|
|
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
|
def _page_corpus(self, search_sources: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
|
|
def _page_corpus(self, search_sources: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
|
|
if self.page_fetcher is None:
|
|
|
|
if self.page_fetcher is None:
|
|
|
|
return []
|
|
|
|
return []
|
|
|
|
@ -1024,9 +1158,13 @@ class VentureDiscoveryService:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
company_score = self._jaccard(company_text, source_text)
|
|
|
|
company_score = self._jaccard(company_text, source_text)
|
|
|
|
category_score = self._jaccard(category_terms.get(category, category), source_text)
|
|
|
|
category_score = self._jaccard(category_terms.get(category, category), source_text)
|
|
|
|
|
|
|
|
company_overlap = self._token_overlap(company_text, source_text)
|
|
|
|
|
|
|
|
category_overlap = self._token_overlap(category_terms.get(category, category), source_text)
|
|
|
|
if category.replace("_", " ") in source_text:
|
|
|
|
if category.replace("_", " ") in source_text:
|
|
|
|
category_score = max(category_score, 0.5)
|
|
|
|
category_score = max(category_score, 0.5)
|
|
|
|
score = round(company_score * 0.65 + category_score * 0.35, 2)
|
|
|
|
score = round(company_score * 0.55 + category_score * 0.25 + min(company_overlap, 5) * 0.03 + min(category_overlap, 4) * 0.025, 2)
|
|
|
|
|
|
|
|
if company_overlap >= 2 and category_overlap >= 1:
|
|
|
|
|
|
|
|
return {"accepted": True, "quality": "strong" if score >= 0.13 or category_overlap >= 2 else "weak", "score": score, "reason": "relevant proposal and category overlap"}
|
|
|
|
if score < 0.08:
|
|
|
|
if score < 0.08:
|
|
|
|
return {"accepted": False, "quality": "weak", "score": score, "reason": "insufficient semantic relevance"}
|
|
|
|
return {"accepted": False, "quality": "weak", "score": score, "reason": "insufficient semantic relevance"}
|
|
|
|
if score < 0.16:
|
|
|
|
if score < 0.16:
|
|
|
|
@ -1106,7 +1244,12 @@ class VentureDiscoveryService:
|
|
|
|
ai_leverage = self.ai_leverage_score(proposal)
|
|
|
|
ai_leverage = self.ai_leverage_score(proposal)
|
|
|
|
platformization = self.platformization_potential(proposal)
|
|
|
|
platformization = self.platformization_potential(proposal)
|
|
|
|
probability = round((demand * 0.22 + pricing * 0.12 + pain * 0.16 + distribution * 0.14 + build * 0.1 + capital * 0.1 + risk * 0.16), 0)
|
|
|
|
probability = round((demand * 0.22 + pricing * 0.12 + pain * 0.16 + distribution * 0.14 + build * 0.1 + capital * 0.1 + risk * 0.16), 0)
|
|
|
|
return {"Demand Evidence": demand, "Time-to-First-Dollar Attractiveness": 76 if service_model else 50, "Capital Efficiency": capital, "Validation Affordability": validation, "Gross Margin Potential": gross_margin, "Distribution Feasibility": distribution, "Build Simplicity": build, "Defensibility": min(75, defensibility), "Market Opportunity": market_size, "Competitive Position": competition, "Risk Manageability": risk, "AI Leverage": ai_leverage, "Platformization Potential": platformization, "Probability of Reaching $500": int(max(20, min(80, probability)))}
|
|
|
|
autonomy = 55
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
autonomy = int(proposal.autonomous_assessment.autonomous_operability_score)
|
|
|
|
|
|
|
|
except ObjectDoesNotExist:
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
return {"Demand Evidence": demand, "Time-to-First-Dollar Attractiveness": 76 if service_model else 50, "Capital Efficiency": capital, "Validation Affordability": validation, "Gross Margin Potential": gross_margin, "Distribution Feasibility": distribution, "Build Simplicity": build, "Defensibility": min(75, defensibility), "Market Opportunity": market_size, "Competitive Position": competition, "Risk Manageability": risk, "AI Leverage": ai_leverage, "Platformization Potential": platformization, "Autonomous Operability": autonomy, "Probability of Reaching $500": int(max(20, min(80, probability)))}
|
|
|
|
|
|
|
|
|
|
|
|
def ai_leverage_score(self, proposal: CompanyProposal) -> int:
|
|
|
|
def ai_leverage_score(self, proposal: CompanyProposal) -> int:
|
|
|
|
text = " ".join([proposal.title, proposal.description, proposal.problem, proposal.proposed_solution, proposal.business_model, proposal.differentiation]).lower()
|
|
|
|
text = " ".join([proposal.title, proposal.description, proposal.problem, proposal.proposed_solution, proposal.business_model, proposal.differentiation]).lower()
|
|
|
|
@ -1153,6 +1296,10 @@ class VentureDiscoveryService:
|
|
|
|
def _capability_requirements(self, proposal: CompanyProposal) -> list[dict[str, Any]]:
|
|
|
|
def _capability_requirements(self, proposal: CompanyProposal) -> list[dict[str, Any]]:
|
|
|
|
research = proposal.metadata.get("research", {}) if isinstance(proposal.metadata, dict) else {}
|
|
|
|
research = proposal.metadata.get("research", {}) if isinstance(proposal.metadata, dict) else {}
|
|
|
|
web_status = CapabilityStatus.MISSING if not self.web_research_available else CapabilityStatus.AVAILABLE if not research.get("unverified_categories") else CapabilityStatus.PARTIAL
|
|
|
|
web_status = CapabilityStatus.MISSING if not self.web_research_available else CapabilityStatus.AVAILABLE if not research.get("unverified_categories") else CapabilityStatus.PARTIAL
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
structural = proposal.autonomous_assessment.structural_blockers
|
|
|
|
|
|
|
|
except ObjectDoesNotExist:
|
|
|
|
|
|
|
|
structural = []
|
|
|
|
return [
|
|
|
|
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": "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": "Board", "status": CapabilityStatus.AVAILABLE, "rationale": "Structured CEO/CTO/CFO/CRO/Independent Director review exists for V0.", "priority": CapabilityPriority.BEFORE_VALIDATION, "evidence": {}},
|
|
|
|
@ -1160,14 +1307,15 @@ class VentureDiscoveryService:
|
|
|
|
{"category": "WEB_MARKET_RESEARCH", "status": web_status, "rationale": "Bounded source-linked web research exists only when configured and category coverage is complete.", "priority": CapabilityPriority.BEFORE_VALIDATION, "evidence": {"web_research_available": self.web_research_available, **research}},
|
|
|
|
{"category": "WEB_MARKET_RESEARCH", "status": web_status, "rationale": "Bounded source-linked web research exists only when configured and category coverage is complete.", "priority": CapabilityPriority.BEFORE_VALIDATION, "evidence": {"web_research_available": self.web_research_available, **research}},
|
|
|
|
{"category": "software build", "status": CapabilityStatus.AVAILABLE, "rationale": "Task execution, coding, review, tests, and graph runtime exist.", "priority": CapabilityPriority.BEFORE_FIRST_CUSTOMER, "evidence": {}},
|
|
|
|
{"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": "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": "deployment", "status": CapabilityStatus.PARTIAL, "rationale": "Validation can use SUBDOMAIN_DEPLOYMENT under a shared parent domain; dedicated domains are a traction-stage upgrade.", "priority": CapabilityPriority.BEFORE_FIRST_CUSTOMER, "evidence": {"deployment_model": "SUBDOMAIN_DEPLOYMENT", "domain_purchase_required_for_validation": False}},
|
|
|
|
{"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": "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": "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": "payments", "status": CapabilityStatus.PARTIAL, "rationale": "Stripe merchant account is available as CONFIGURED_EXTERNAL_PROVIDER, but no real charges or product checkout integration are implemented in this milestone.", "priority": CapabilityPriority.BEFORE_FIRST_CUSTOMER, "evidence": {"payment_provider_state": "CONFIGURED_EXTERNAL_PROVIDER", "implementation_state": "IMPLEMENTATION_MISSING", "real_charges_allowed": False}},
|
|
|
|
{"category": "invoicing", "status": CapabilityStatus.MISSING, "rationale": "No invoicing workflow 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": "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": "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": {}},
|
|
|
|
{"category": "legal/compliance", "status": CapabilityStatus.MISSING, "rationale": "No contracts, terms, privacy, or compliance review workflow exists.", "priority": CapabilityPriority.BEFORE_FIRST_CUSTOMER, "evidence": {}},
|
|
|
|
|
|
|
|
{"category": "COMPANY_STRUCTURAL_HUMAN_DEPENDENCY", "status": CapabilityStatus.MISSING if structural else CapabilityStatus.AVAILABLE, "rationale": "Structural human dependency is inherent to the company model and should route assisted-only if severe; it is not solved by platform infrastructure.", "priority": CapabilityPriority.BEFORE_VALIDATION, "evidence": {"dependency_causes": structural}},
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def _portfolio_concentration(self, cohort: VentureCohort) -> dict[str, Any]:
|
|
|
|
def _portfolio_concentration(self, cohort: VentureCohort) -> dict[str, Any]:
|
|
|
|
|