Artifex/control_plane/ventures/models.py
2026-08-16 17:48:04 +07:00

431 lines
21 KiB
Python

from __future__ import annotations
from django.db import models
from control_plane.common import TimestampedModel
class CompanyProposalStatus(models.TextChoices):
DRAFT = "DRAFT"
SUBMITTED = "SUBMITTED"
UNDER_DILIGENCE = "UNDER_DILIGENCE"
REVISE = "REVISE"
FUNDED_RECOMMENDED = "FUNDED_RECOMMENDED"
WATCHLIST = "WATCHLIST"
REJECTED = "REJECTED"
class ICDecisionType(models.TextChoices):
FUND_RECOMMENDED = "FUND_RECOMMENDED"
CONDITIONAL_FUND = "CONDITIONAL_FUND"
REVISE_AND_RESUBMIT = "REVISE_AND_RESUBMIT"
WATCHLIST = "WATCHLIST"
PIVOT = "PIVOT"
REJECT = "REJECT"
class CapabilityStatus(models.TextChoices):
AVAILABLE = "AVAILABLE"
PARTIAL = "PARTIAL"
MISSING = "MISSING"
class CapabilityPriority(models.TextChoices):
BEFORE_VALIDATION = "BEFORE_VALIDATION"
BEFORE_FIRST_CUSTOMER = "BEFORE_FIRST_CUSTOMER"
BEFORE_SCALING = "BEFORE_SCALING"
class EvidenceTier(models.TextChoices):
TIER_0_THESIS = "TIER_0_THESIS"
TIER_1_PUBLIC_EVIDENCE = "TIER_1_PUBLIC_EVIDENCE"
TIER_2_CUSTOMER_SIGNAL = "TIER_2_CUSTOMER_SIGNAL"
TIER_3_WILLINGNESS_TO_PAY = "TIER_3_WILLINGNESS_TO_PAY"
TIER_4_PAID_CUSTOMER = "TIER_4_PAID_CUSTOMER"
TIER_5_REPEATABLE_TRACTION = "TIER_5_REPEATABLE_TRACTION"
class OverlapClassification(models.TextChoices):
NONE = "NONE"
ADJACENT = "ADJACENT"
COMPETITIVE = "COMPETITIVE"
NEAR_DUPLICATE = "NEAR_DUPLICATE"
DUPLICATE = "DUPLICATE"
class PortfolioThesisStatus(models.TextChoices):
NEW = "NEW"
ACTIVE_CANDIDATE = "ACTIVE_CANDIDATE"
EXPLORED = "EXPLORED"
SATURATED = "SATURATED"
PAUSED = "PAUSED"
REJECTED = "REJECTED"
OPERATING = "OPERATING"
WINNER = "WINNER"
class ThesisMatchClassification(models.TextChoices):
NEW_THESIS = "NEW_THESIS"
ADJACENT = "ADJACENT"
COMPETITIVE = "COMPETITIVE"
NEAR_DUPLICATE = "NEAR_DUPLICATE"
DUPLICATE = "DUPLICATE"
class NoveltyGateDecision(models.TextChoices):
ACCEPT = "ACCEPT"
REGENERATE_HARD_EXCLUSION = "REGENERATE_HARD_EXCLUSION"
REGENERATE_DUPLICATE = "REGENERATE_DUPLICATE"
REVIEW_SOFT_EXCLUSION = "REVIEW_SOFT_EXCLUSION"
FAILED_IDEATION = "FAILED_IDEATION"
class OpportunityTerritory(models.TextChoices):
VERTICAL_AI_WRAPPERS = "VERTICAL_AI_WRAPPERS"
ENTERPRISE_WORKFLOW_AUTOMATION = "ENTERPRISE_WORKFLOW_AUTOMATION"
SMB_AUTOMATION = "SMB_AUTOMATION"
DEVELOPER_AI_INFRASTRUCTURE = "DEVELOPER_AI_INFRASTRUCTURE"
INTELLIGENCE_MONITORING = "INTELLIGENCE_MONITORING"
DATA_DOCUMENT_AUTOMATION = "DATA_DOCUMENT_AUTOMATION"
AI_ENABLED_SERVICE_TO_PLATFORM = "AI_ENABLED_SERVICE_TO_PLATFORM"
OPEN_CATEGORY = "OPEN_CATEGORY"
class VentureTrack(models.TextChoices):
AUTONOMOUS = "AUTONOMOUS"
ASSISTED = "ASSISTED"
class FounderDependencyLevel(models.TextChoices):
NONE = "NONE"
LOW = "LOW"
MEDIUM = "MEDIUM"
HIGH = "HIGH"
CRITICAL = "CRITICAL"
class AutonomousGateResult(models.TextChoices):
AUTONOMOUS_ELIGIBLE = "AUTONOMOUS_ELIGIBLE"
AUTONOMOUS_BORDERLINE = "AUTONOMOUS_BORDERLINE"
ASSISTED_ONLY = "ASSISTED_ONLY"
REJECT_OPERABILITY = "REJECT_OPERABILITY"
class AutonomousCandidateStatus(models.TextChoices):
NOT_ASSESSED = "NOT_ASSESSED"
AUTONOMOUS_CANDIDATE = "AUTONOMOUS_CANDIDATE"
ASSISTED_CANDIDATE = "ASSISTED_CANDIDATE"
AUTONOMOUS_REJECTED = "AUTONOMOUS_REJECTED"
class CompanyMandate(TimestampedModel):
objective = models.TextField()
constraints = models.JSONField(default=dict, blank=True)
optimization_targets = models.JSONField(default=list, blank=True)
max_validation_capital = models.DecimalField(max_digits=10, decimal_places=2, default=50)
target_net_new_cash = models.DecimalField(max_digits=10, decimal_places=2, default=500)
target_window_days = models.PositiveIntegerField(default=30)
metadata = models.JSONField(default=dict, blank=True)
class VentureThesis(TimestampedModel):
mandate = models.ForeignKey(CompanyMandate, on_delete=models.CASCADE, related_name="theses")
title = models.CharField(max_length=255)
thesis = models.TextField()
similarity_fingerprint = models.CharField(max_length=128, blank=True)
related_theses = models.JSONField(default=list, blank=True)
competitive_theses = models.JSONField(default=list, blank=True)
portfolio_visibility = models.JSONField(default=dict, blank=True)
metadata = models.JSONField(default=dict, blank=True)
evidence_tier = models.CharField(max_length=40, choices=EvidenceTier.choices, default=EvidenceTier.TIER_0_THESIS)
class CompanyProposal(TimestampedModel):
mandate = models.ForeignKey(CompanyMandate, on_delete=models.PROTECT, related_name="company_proposals")
thesis = models.ForeignKey(VentureThesis, on_delete=models.SET_NULL, null=True, blank=True, related_name="company_proposals")
title = models.CharField(max_length=255)
description = models.TextField()
problem = models.TextField()
target_customer = models.TextField()
proposed_solution = models.TextField()
business_model = models.TextField()
pricing_hypothesis = models.TextField()
acquisition_strategy = models.TextField()
validation_plan = models.TextField()
capital_requested = models.DecimalField(max_digits=10, decimal_places=2, default=0)
time_to_first_dollar_estimate = models.CharField(max_length=120)
expected_margin = models.CharField(max_length=120)
build_complexity = models.CharField(max_length=80)
market_evidence = models.JSONField(default=list, blank=True)
differentiation = models.TextField()
major_risks = models.JSONField(default=list, blank=True)
confidence = models.FloatField(default=0.0)
status = models.CharField(max_length=32, choices=CompanyProposalStatus.choices, default=CompanyProposalStatus.DRAFT)
pitch = models.JSONField(default=dict, blank=True)
metadata = models.JSONField(default=dict, blank=True)
evidence_tier = models.CharField(max_length=40, choices=EvidenceTier.choices, default=EvidenceTier.TIER_0_THESIS)
class PortfolioThesis(TimestampedModel):
canonical_name = models.CharField(max_length=255, unique=True)
concise_thesis = models.TextField()
category = models.CharField(max_length=120, blank=True)
industry = models.CharField(max_length=160, blank=True)
icp = models.TextField(blank=True)
problem = models.TextField(blank=True)
offer = models.TextField(blank=True)
business_model = models.CharField(max_length=160, blank=True)
primary_channel = models.CharField(max_length=160, blank=True)
ai_leverage = models.FloatField(default=0.0)
platformization_potential = models.FloatField(default=0.0)
fingerprint = models.CharField(max_length=128, blank=True)
status = models.CharField(max_length=32, choices=PortfolioThesisStatus.choices, default=PortfolioThesisStatus.NEW)
proposal_count = models.PositiveIntegerField(default=0)
best_ic_score = models.FloatField(default=0.0)
best_company = models.ForeignKey(CompanyProposal, on_delete=models.SET_NULL, null=True, blank=True, related_name="best_for_portfolio_theses")
first_seen_cohort = models.ForeignKey("VentureCohort", on_delete=models.SET_NULL, null=True, blank=True, related_name="first_seen_portfolio_theses")
last_seen_cohort = models.ForeignKey("VentureCohort", on_delete=models.SET_NULL, null=True, blank=True, related_name="last_seen_portfolio_theses")
metadata = models.JSONField(default=dict, blank=True)
venture_track = models.CharField(max_length=32, choices=VentureTrack.choices, default=VentureTrack.AUTONOMOUS)
best_autonomous_operability_score = models.FloatField(default=0.0)
best_founder_dependency = models.CharField(max_length=32, choices=FounderDependencyLevel.choices, default=FounderDependencyLevel.MEDIUM)
autonomous_candidate_status = models.CharField(max_length=32, choices=AutonomousCandidateStatus.choices, default=AutonomousCandidateStatus.NOT_ASSESSED)
class CohortIdeationMandate(TimestampedModel):
cohort = models.OneToOneField("VentureCohort", on_delete=models.CASCADE, related_name="ideation_mandate")
objective = models.TextField()
desired_opportunity_classes = models.JSONField(default=list, blank=True)
hard_exclusions = models.JSONField(default=list, blank=True)
soft_exclusions = models.JSONField(default=list, blank=True)
opportunity_territories = models.JSONField(default=list, blank=True)
portfolio_gaps = models.JSONField(default=list, blank=True)
diversity_preferences = models.JSONField(default=dict, blank=True)
registry_snapshot = models.JSONField(default=dict, blank=True)
created_by = models.CharField(max_length=120, default="Portfolio IC")
metadata = models.JSONField(default=dict, blank=True)
class PortfolioThesisCluster(TimestampedModel):
cohort = models.ForeignKey("VentureCohort", on_delete=models.CASCADE, related_name="thesis_clusters")
proposal = models.ForeignKey(CompanyProposal, on_delete=models.CASCADE, related_name="portfolio_thesis_clusters")
portfolio_thesis = models.ForeignKey(PortfolioThesis, on_delete=models.SET_NULL, null=True, blank=True, related_name="proposal_clusters")
similarity_score = models.FloatField(default=0.0)
classification = models.CharField(max_length=32, choices=ThesisMatchClassification.choices)
explanation = models.TextField(blank=True)
metadata = models.JSONField(default=dict, blank=True)
class VentureGenerationRejection(TimestampedModel):
cohort = models.ForeignKey("VentureCohort", on_delete=models.CASCADE, related_name="generation_rejections")
slot_index = models.PositiveIntegerField()
attempt = models.PositiveIntegerField()
decision = models.CharField(max_length=40, choices=NoveltyGateDecision.choices)
reason = models.TextField(blank=True)
candidate = models.JSONField(default=dict, blank=True)
matched_thesis = models.ForeignKey(PortfolioThesis, on_delete=models.SET_NULL, null=True, blank=True, related_name="generation_rejections")
similarity_score = models.FloatField(default=0.0)
metadata = models.JSONField(default=dict, blank=True)
class PortfolioSaturationAnalysis(TimestampedModel):
cohort = models.ForeignKey("VentureCohort", on_delete=models.CASCADE, related_name="saturation_analyses")
portfolio_thesis = models.ForeignKey(PortfolioThesis, on_delete=models.CASCADE, related_name="saturation_analyses")
proposal_count = models.PositiveIntegerField(default=0)
best_ic_score = models.FloatField(default=0.0)
score_spread = models.FloatField(default=0.0)
status_recommendation = models.CharField(max_length=32, choices=PortfolioThesisStatus.choices)
rationale = models.TextField(blank=True)
metadata = models.JSONField(default=dict, blank=True)
class AutonomousOperabilityAssessment(TimestampedModel):
proposal = models.OneToOneField(CompanyProposal, on_delete=models.CASCADE, related_name="autonomous_assessment")
venture_track = models.CharField(max_length=32, choices=VentureTrack.choices, default=VentureTrack.AUTONOMOUS)
gate_result = models.CharField(max_length=40, choices=AutonomousGateResult.choices)
autonomous_operability_score = models.FloatField(default=0.0)
commercial_score = models.FloatField(default=0.0)
founder_dependency = models.CharField(max_length=32, choices=FounderDependencyLevel.choices)
dependency_causes = models.JSONField(default=list, blank=True)
minutes_per_week_human = models.PositiveIntegerField(default=30)
human_actions_required = models.JSONField(default=list, blank=True)
human_action_categories = models.JSONField(default=list, blank=True)
operating_loop = models.JSONField(default=dict, blank=True)
fulfillment_contract = models.JSONField(default=dict, blank=True)
validation_offer = models.JSONField(default=dict, blank=True)
end_state_business_model = models.JSONField(default=dict, blank=True)
structural_blockers = models.JSONField(default=list, blank=True)
platform_blockers = models.JSONField(default=list, blank=True)
component_scores = models.JSONField(default=dict, blank=True)
rationale = models.TextField(blank=True)
metadata = models.JSONField(default=dict, blank=True)
class CompanyBoardReview(TimestampedModel):
proposal = models.ForeignKey(CompanyProposal, on_delete=models.CASCADE, related_name="board_reviews")
observations = models.JSONField(default=dict, blank=True)
strengths = models.JSONField(default=list, blank=True)
weaknesses = models.JSONField(default=list, blank=True)
key_assumptions = models.JSONField(default=list, blank=True)
required_revisions = models.JSONField(default=list, blank=True)
recommendation = models.CharField(max_length=80)
revised_pitch = models.JSONField(default=dict, blank=True)
metadata = models.JSONField(default=dict, blank=True)
class ICDiligence(TimestampedModel):
proposal = models.ForeignKey(CompanyProposal, on_delete=models.CASCADE, related_name="ic_diligence")
status = models.CharField(max_length=32, default="INITIAL_PITCH")
rounds = models.JSONField(default=list, blank=True)
red_team_challenge = models.JSONField(default=dict, blank=True)
final_response = models.JSONField(default=dict, blank=True)
metadata = models.JSONField(default=dict, blank=True)
class ICQuestion(TimestampedModel):
diligence = models.ForeignKey(ICDiligence, on_delete=models.CASCADE, related_name="questions")
question = models.TextField()
category = models.CharField(max_length=80)
evidence_required = models.BooleanField(default=True)
round_name = models.CharField(max_length=80, default="Diligence Round 1")
metadata = models.JSONField(default=dict, blank=True)
class ICResponse(TimestampedModel):
question = models.ForeignKey(ICQuestion, on_delete=models.CASCADE, related_name="responses")
answer = models.TextField()
evidence = models.JSONField(default=list, blank=True)
uncertainty = models.TextField(blank=True)
pitch_changes = models.JSONField(default=dict, blank=True)
metadata = models.JSONField(default=dict, blank=True)
class ICDecision(TimestampedModel):
diligence = models.OneToOneField(ICDiligence, on_delete=models.CASCADE, related_name="decision")
decision = models.CharField(max_length=32, choices=ICDecisionType.choices)
component_scores = models.JSONField(default=dict, blank=True)
composite_score = models.FloatField(default=0.0)
probability_500_within_30_days = models.FloatField(default=0.0)
initial_tranche = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
validation_condition = models.TextField(blank=True)
evidence_required = models.JSONField(default=list, blank=True)
recommended_allocation = models.JSONField(default=dict, blank=True)
kill_criteria = models.JSONField(default=list, blank=True)
next_decision_point = models.TextField(blank=True)
metadata = models.JSONField(default=dict, blank=True)
evidence_tier = models.CharField(max_length=40, choices=EvidenceTier.choices, default=EvidenceTier.TIER_0_THESIS)
raw_probability_500_within_30_days = models.FloatField(default=0.0)
evidence_ceiling = models.FloatField(default=35.0)
probability_explanation = models.TextField(blank=True)
score_definitions = models.JSONField(default=dict, blank=True)
class VentureThesisFingerprint(TimestampedModel):
proposal = models.OneToOneField(CompanyProposal, on_delete=models.CASCADE, related_name="fingerprint")
industry = models.CharField(max_length=160)
icp = models.TextField()
problem = models.TextField()
offer = models.TextField()
business_model = models.CharField(max_length=160)
primary_distribution_channel = models.CharField(max_length=160)
price_band = models.CharField(max_length=80)
time_to_first_cash_band = models.CharField(max_length=80)
required_capability_set = models.JSONField(default=list, blank=True)
geography_dependency = models.CharField(max_length=120, blank=True)
regulatory_dependency = models.CharField(max_length=120, blank=True)
online_offline = models.CharField(max_length=80, blank=True)
service_software_hybrid = models.CharField(max_length=80, blank=True)
fingerprint_hash = models.CharField(max_length=128)
metadata = models.JSONField(default=dict, blank=True)
class VentureCohort(TimestampedModel):
cohort_id = models.CharField(max_length=80, unique=True)
mandate = models.ForeignKey(CompanyMandate, on_delete=models.PROTECT, related_name="venture_cohorts")
cohort_size = models.PositiveIntegerField(default=10)
status = models.CharField(max_length=40, default="PREPARING")
graph_run = models.ForeignKey("graph.GraphRun", on_delete=models.SET_NULL, null=True, blank=True, related_name="venture_cohorts")
concurrency = models.PositiveIntegerField(default=1)
model_versions = models.JSONField(default=dict, blank=True)
graph_versions = models.JSONField(default=dict, blank=True)
research_policy = models.JSONField(default=dict, blank=True)
scoring_policy = models.JSONField(default=dict, blank=True)
evidence_calibration_policy = models.JSONField(default=dict, blank=True)
metrics = models.JSONField(default=dict, blank=True)
metadata = models.JSONField(default=dict, blank=True)
class VentureCohortMember(TimestampedModel):
cohort = models.ForeignKey(VentureCohort, on_delete=models.CASCADE, related_name="members")
proposal = models.ForeignKey(CompanyProposal, on_delete=models.CASCADE, related_name="cohort_memberships")
child_graph_run = models.ForeignKey("graph.GraphRun", on_delete=models.SET_NULL, null=True, blank=True, related_name="venture_cohort_members")
rank = models.PositiveIntegerField(null=True, blank=True)
is_top_3 = models.BooleanField(default=False)
portfolio_score = models.FloatField(default=0.0)
metadata = models.JSONField(default=dict, blank=True)
class Meta:
constraints = [models.UniqueConstraint(fields=["cohort", "proposal"], name="unique_venture_cohort_member")]
class VentureCollision(TimestampedModel):
cohort = models.ForeignKey(VentureCohort, on_delete=models.CASCADE, related_name="collisions")
company_a = models.ForeignKey(CompanyProposal, on_delete=models.CASCADE, related_name="collisions_as_a")
company_b = models.ForeignKey(CompanyProposal, on_delete=models.CASCADE, related_name="collisions_as_b")
classification = models.CharField(max_length=40, choices=OverlapClassification.choices)
similarity_score = models.FloatField(default=0.0)
explanation = models.TextField(blank=True)
overlapping_dimensions = models.JSONField(default=list, blank=True)
class PortfolioICReview(TimestampedModel):
cohort = models.OneToOneField(VentureCohort, on_delete=models.CASCADE, related_name="portfolio_review")
rankings = models.JSONField(default=list, blank=True)
top_3 = models.JSONField(default=list, blank=True)
concentration = models.JSONField(default=dict, blank=True)
capability_demand = models.JSONField(default=list, blank=True)
top_3_capability_gaps = models.JSONField(default=list, blank=True)
recommended_build_priorities = models.JSONField(default=list, blank=True)
metadata = models.JSONField(default=dict, blank=True)
class VentureCapabilityDemand(TimestampedModel):
cohort = models.ForeignKey(VentureCohort, on_delete=models.CASCADE, related_name="capability_demands")
capability = models.CharField(max_length=120)
count = models.PositiveIntegerField(default=0)
percentage = models.FloatField(default=0.0)
earliest_stage = models.CharField(max_length=32, choices=CapabilityPriority.choices)
companies = models.JSONField(default=list, blank=True)
status_distribution = models.JSONField(default=dict, blank=True)
top_3_count = models.PositiveIntegerField(default=0)
priority_score = models.FloatField(default=0.0)
class CompanyCapabilityRequirement(TimestampedModel):
proposal = models.ForeignKey(CompanyProposal, on_delete=models.CASCADE, related_name="capability_requirements")
category = models.CharField(max_length=120)
status = models.CharField(max_length=32, choices=CapabilityStatus.choices)
rationale = models.TextField(blank=True)
priority = models.CharField(max_length=32, choices=CapabilityPriority.choices, default=CapabilityPriority.BEFORE_SCALING)
evidence = models.JSONField(default=dict, blank=True)
class PortfolioCapabilityGap(TimestampedModel):
proposal = models.ForeignKey(CompanyProposal, on_delete=models.CASCADE, related_name="capability_gap_reports")
available = models.JSONField(default=list, blank=True)
partial = models.JSONField(default=list, blank=True)
missing = models.JSONField(default=list, blank=True)
ranked_missing = models.JSONField(default=list, blank=True)
report = models.TextField(blank=True)
metadata = models.JSONField(default=dict, blank=True)
class VentureArtifact(TimestampedModel):
proposal = models.ForeignKey(CompanyProposal, on_delete=models.CASCADE, null=True, blank=True, related_name="artifacts")
mandate = models.ForeignKey(CompanyMandate, on_delete=models.CASCADE, null=True, blank=True, related_name="artifacts")
graph_run = models.ForeignKey("graph.GraphRun", on_delete=models.SET_NULL, null=True, blank=True, related_name="venture_artifacts")
artifact_type = models.CharField(max_length=80)
name = models.CharField(max_length=255)
content = models.JSONField(default=dict, blank=True)
readable = models.TextField(blank=True)
generated_by = models.CharField(max_length=120, blank=True)