Add crypto protocol cohort V0.2
This commit is contained in:
parent
861decb0f8
commit
3cdd7b0cae
7 changed files with 601 additions and 71 deletions
|
|
@ -21,15 +21,25 @@ from control_plane.ventures.models import (
|
||||||
CryptoJurisdictionPolicy,
|
CryptoJurisdictionPolicy,
|
||||||
CryptoRedTeamAssessment,
|
CryptoRedTeamAssessment,
|
||||||
CryptoScenarioLab,
|
CryptoScenarioLab,
|
||||||
|
CryptoAutonomyClass,
|
||||||
FounderDependencyLevel,
|
FounderDependencyLevel,
|
||||||
OnchainNecessityAssessment,
|
OnchainNecessityAssessment,
|
||||||
PortfolioICReview,
|
PortfolioICReview,
|
||||||
PortfolioThesis,
|
PortfolioThesis,
|
||||||
PortfolioThesisStatus,
|
PortfolioThesisStatus,
|
||||||
ProtocolThesis,
|
ProtocolThesis,
|
||||||
|
ProtocolSecurityAssessment,
|
||||||
|
ProtocolSecurityDecision,
|
||||||
ProtocolValueCapture,
|
ProtocolValueCapture,
|
||||||
|
PreTokenMonetizationAssessment,
|
||||||
|
PreTokenMonetizationMode,
|
||||||
TokenDemandLoop,
|
TokenDemandLoop,
|
||||||
|
TokenEconomicModel,
|
||||||
|
TokenLaunchReadinessAssessment,
|
||||||
|
TokenLaunchReadinessStatus,
|
||||||
TokenNecessityClassification,
|
TokenNecessityClassification,
|
||||||
|
TokenRoleDecomposition,
|
||||||
|
TokenRoleRequirement,
|
||||||
TokenomicsSimulation,
|
TokenomicsSimulation,
|
||||||
TokenRedTeamFlag,
|
TokenRedTeamFlag,
|
||||||
TokenUtilityAssessment,
|
TokenUtilityAssessment,
|
||||||
|
|
@ -44,6 +54,7 @@ from control_plane.ventures.models import (
|
||||||
from model_router.policy import model_for_role
|
from model_router.policy import model_for_role
|
||||||
from model_router.providers import extract_json_object
|
from model_router.providers import extract_json_object
|
||||||
from model_router.router import ModelCapability, ModelRequestContract, ModelRouter
|
from model_router.router import ModelCapability, ModelRequestContract, ModelRouter
|
||||||
|
from research.searxng import SearxngSearchClient, WebPageFetcher
|
||||||
|
|
||||||
|
|
||||||
CRYPTO_TERRITORIES = [
|
CRYPTO_TERRITORIES = [
|
||||||
|
|
@ -94,8 +105,11 @@ CRYPTO_SCORE_DIMENSIONS = [
|
||||||
"AUTONOMOUS_OPERABILITY",
|
"AUTONOMOUS_OPERABILITY",
|
||||||
"SECURITY_MODEL_QUALITY",
|
"SECURITY_MODEL_QUALITY",
|
||||||
"REGULATORY_MANAGEABILITY",
|
"REGULATORY_MANAGEABILITY",
|
||||||
|
"PRE_TOKEN_MONETIZATION_POTENTIAL",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
CRYPTO_RESEARCH_CATEGORIES = ["USER_PAIN", "EXISTING_PROTOCOLS", "FAILED_PRECEDENTS", "PAYMENT_MODELS", "TOKEN_MODELS", "STAKING_MODELS", "SLASHING_PRECEDENTS", "PROVIDER_ECONOMICS", "NETWORK_BOOTSTRAP", "ONCHAIN_ALTERNATIVES", "OFFCHAIN_ALTERNATIVES", "SECURITY_INCIDENTS", "LEGAL_REGULATORY", "TOKEN_LAUNCH_PRECEDENTS", "PRE_TOKEN_MONETIZATION_PRECEDENTS"]
|
||||||
|
|
||||||
CRYPTO_SCENARIOS = [
|
CRYPTO_SCENARIOS = [
|
||||||
"oracle failure",
|
"oracle failure",
|
||||||
"validator/provider collusion",
|
"validator/provider collusion",
|
||||||
|
|
@ -128,15 +142,15 @@ class CryptoVentureService:
|
||||||
objective="Generate crypto-native protocol ventures where onchain execution and a native token are genuinely necessary for real usage, not speculation.",
|
objective="Generate crypto-native protocol ventures where onchain execution and a native token are genuinely necessary for real usage, not speculation.",
|
||||||
constraints={"venture_track": VentureTrack.CRYPTO_PROTOCOL, "no_token_sale": True, "no_fundraising": True, "no_mainnet_issuance": True, "no_us_targeted_activity": True, "testnet_or_local_only": True, "no_real_spend": True, "no_user_contact": True},
|
constraints={"venture_track": VentureTrack.CRYPTO_PROTOCOL, "no_token_sale": True, "no_fundraising": True, "no_mainnet_issuance": True, "no_us_targeted_activity": True, "testnet_or_local_only": True, "no_real_spend": True, "no_user_contact": True},
|
||||||
optimization_targets=["token necessity", "onchain necessity", "real usage demand", "value accrual", "sustainable tokenomics", "autonomous operability", "regulatory manageability", "security model quality"],
|
optimization_targets=["token necessity", "onchain necessity", "real usage demand", "value accrual", "sustainable tokenomics", "autonomous operability", "regulatory manageability", "security model quality"],
|
||||||
metadata={"milestone": "CRYPTO_PROTOCOL_VENTURE_COHORT_V0.1", "venture_track": VentureTrack.CRYPTO_PROTOCOL},
|
metadata={"milestone": "CRYPTO_PROTOCOL_VENTURE_COHORT_V0.2", "venture_track": VentureTrack.CRYPTO_PROTOCOL},
|
||||||
)
|
)
|
||||||
self._artifact(None, mandate, "CRYPTO_VENTURE_MANDATE", "Crypto Protocol Venture V0.1 Mandate", mandate.constraints, "Crypto protocol cohort mandate. No token sale, fundraising, or mainnet issuance.", "crypto_venture")
|
self._artifact(None, mandate, "CRYPTO_VENTURE_MANDATE", "Crypto Protocol Venture V0.1 Mandate", mandate.constraints, "Crypto protocol cohort mandate. No token sale, fundraising, or mainnet issuance.", "crypto_venture")
|
||||||
return mandate
|
return mandate
|
||||||
|
|
||||||
def prepare_cohort(self, *, size: int, graph_run=None, concurrency: int = 2) -> VentureCohort:
|
def prepare_cohort(self, *, size: int, graph_run=None, concurrency: int = 2) -> VentureCohort:
|
||||||
mandate = self.create_crypto_mandate()
|
mandate = self.create_crypto_mandate()
|
||||||
cohort_id = f"CPV01-{time.strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:8]}"
|
cohort_id = f"CPV02-{time.strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:8]}"
|
||||||
return VentureCohort.objects.create(cohort_id=cohort_id, mandate=mandate, cohort_size=size, graph_run=graph_run, concurrency=concurrency, status="PREPARING_CRYPTO", graph_versions={"cohort": "crypto_venture_cohort v1"}, research_policy={"source_linked_evidence_required": True, "research_insufficient_if_no_sources": True}, scoring_policy={"dimensions": CRYPTO_SCORE_DIMENSIONS}, metadata={"milestone": "CRYPTO_PROTOCOL_VENTURE_COHORT_V0.1", "venture_track": VentureTrack.CRYPTO_PROTOCOL, "real_spend": 0, "real_customer_outreach": False, "token_sale": False, "fundraising": False, "mainnet_issuance": False})
|
return VentureCohort.objects.create(cohort_id=cohort_id, mandate=mandate, cohort_size=size, graph_run=graph_run, concurrency=concurrency, status="PREPARING_CRYPTO", graph_versions={"cohort": "crypto_venture_cohort v2"}, research_policy={"source_linked_evidence_required": True, "research_insufficient_if_no_sources": True, "crypto_categories": CRYPTO_RESEARCH_CATEGORIES}, scoring_policy={"dimensions": CRYPTO_SCORE_DIMENSIONS, "strict_token_filter": True}, metadata={"milestone": "CRYPTO_PROTOCOL_VENTURE_COHORT_V0.2", "venture_track": VentureTrack.CRYPTO_PROTOCOL, "real_spend": 0, "real_customer_outreach": False, "token_sale": False, "fundraising": False, "mainnet_issuance": False})
|
||||||
|
|
||||||
def generate_protocols(self, cohort: VentureCohort) -> list[CompanyProposal]:
|
def generate_protocols(self, cohort: VentureCohort) -> list[CompanyProposal]:
|
||||||
accepted = []
|
accepted = []
|
||||||
|
|
@ -148,7 +162,7 @@ class CryptoVentureService:
|
||||||
proposal = self._create_proposal(cohort.mandate, payload, source, territory)
|
proposal = self._create_proposal(cohort.mandate, payload, source, territory)
|
||||||
VentureCohortMember.objects.create(cohort=cohort, proposal=proposal, metadata={"territory": territory})
|
VentureCohortMember.objects.create(cohort=cohort, proposal=proposal, metadata={"territory": territory})
|
||||||
accepted.append(proposal)
|
accepted.append(proposal)
|
||||||
cohort.metrics = {**cohort.metrics, "requested_protocol_count": cohort.cohort_size, "generation_attempts": attempts, "accepted_protocols": len(accepted), "duplicate_rejections": 0, "token_necessity_rejections": 0, "generation_sources": sorted({p.metadata.get("generation_source", "unknown") for p in accepted})}
|
cohort.metrics = {**cohort.metrics, "raw_target": cohort.cohort_size, "raw_generated": len(accepted), "requested_protocol_count": cohort.cohort_size, "generation_attempts": attempts, "accepted_protocols": len(accepted), "duplicate_rejections": 0, "token_necessity_rejections": 0, "generation_sources": sorted({p.metadata.get("generation_source", "unknown") for p in accepted})}
|
||||||
cohort.status = "PROTOCOLS_GENERATED"
|
cohort.status = "PROTOCOLS_GENERATED"
|
||||||
cohort.save(update_fields=["metrics", "status", "updated_at"])
|
cohort.save(update_fields=["metrics", "status", "updated_at"])
|
||||||
return accepted
|
return accepted
|
||||||
|
|
@ -186,24 +200,8 @@ class CryptoVentureService:
|
||||||
return {"removed": removed, "hard_exclusion_rejections": hard_excluded, "duplicate_rejections": duplicate}
|
return {"removed": removed, "hard_exclusion_rejections": hard_excluded, "duplicate_rejections": duplicate}
|
||||||
|
|
||||||
def regenerate_rejected_slots(self, cohort: VentureCohort, *, reason: str = "novelty_or_token_gate") -> list[CompanyProposal]:
|
def regenerate_rejected_slots(self, cohort: VentureCohort, *, reason: str = "novelty_or_token_gate") -> list[CompanyProposal]:
|
||||||
created = []
|
created: list[CompanyProposal] = []
|
||||||
attempts = 0
|
cohort.metrics = {**cohort.metrics, "regeneration_attempts": cohort.metrics.get("regeneration_attempts", 0), "regenerated_protocols": cohort.metrics.get("regenerated_protocols", 0), "crypto_survivors": cohort.members.count(), "unfilled_slots_after_regeneration": max(0, cohort.cohort_size - cohort.members.count()), "regeneration_policy": "V0.2 uses total raw generation budget; weak token ideas are not force-filled."}
|
||||||
max_attempts = min(6, max(2, (cohort.cohort_size - cohort.members.count()) * 2))
|
|
||||||
while cohort.members.count() < cohort.cohort_size and attempts < max_attempts:
|
|
||||||
attempts += 1
|
|
||||||
index = cohort.members.count() + attempts
|
|
||||||
territory = CRYPTO_TERRITORIES[index % len(CRYPTO_TERRITORIES)]
|
|
||||||
if attempts <= 2:
|
|
||||||
payload, source = self._protocol_payload(index, territory)
|
|
||||||
else:
|
|
||||||
payload, source = self._fallback_payload(index, territory), "deterministic_crypto_repair"
|
|
||||||
proposal = self._create_proposal(cohort.mandate, payload, source, territory)
|
|
||||||
VentureCohortMember.objects.create(cohort=cohort, proposal=proposal, metadata={"territory": territory, "regenerated_for": reason})
|
|
||||||
created.append(proposal)
|
|
||||||
self.assess_token_necessity(proposal)
|
|
||||||
self.novelty_gate(cohort)
|
|
||||||
self._remove_weak_token_members(cohort)
|
|
||||||
cohort.metrics = {**cohort.metrics, "regeneration_attempts": cohort.metrics.get("regeneration_attempts", 0) + attempts, "regenerated_protocols": cohort.metrics.get("regenerated_protocols", 0) + len(created), "accepted_protocols": cohort.members.count(), "unfilled_slots_after_regeneration": max(0, cohort.cohort_size - cohort.members.count())}
|
|
||||||
cohort.save(update_fields=["metrics", "updated_at"])
|
cohort.save(update_fields=["metrics", "updated_at"])
|
||||||
return created
|
return created
|
||||||
|
|
||||||
|
|
@ -230,24 +228,135 @@ class CryptoVentureService:
|
||||||
proposal.status = CompanyProposalStatus.REJECTED
|
proposal.status = CompanyProposalStatus.REJECTED
|
||||||
proposal.metadata = {**proposal.metadata, "routed_to_saas": assessment.classification == TokenNecessityClassification.TOKEN_OPTIONAL}
|
proposal.metadata = {**proposal.metadata, "routed_to_saas": assessment.classification == TokenNecessityClassification.TOKEN_OPTIONAL}
|
||||||
proposal.save(update_fields=["status", "metadata", "updated_at"])
|
proposal.save(update_fields=["status", "metadata", "updated_at"])
|
||||||
return {"token_unnecessary_rejections": cohort.metrics.get("token_unnecessary_rejections", 0) + unnecessary, "token_optional_route_to_saas": cohort.metrics.get("token_optional_route_to_saas", 0) + optional}
|
return {"token_unnecessary_rejections": cohort.metrics.get("token_unnecessary_rejections", 0) + unnecessary, "token_optional_route_to_saas": cohort.metrics.get("token_optional_route_to_saas", 0) + optional, "crypto_survivors": cohort.members.count()}
|
||||||
|
|
||||||
def light_market_research(self, cohort: VentureCohort) -> None:
|
def light_market_research(self, cohort: VentureCohort) -> None:
|
||||||
started = time.monotonic()
|
started = time.monotonic()
|
||||||
total_sources = 0
|
total_sources = 0
|
||||||
|
total_rejected = 0
|
||||||
insufficient = 0
|
insufficient = 0
|
||||||
for member in cohort.members.select_related("proposal"):
|
for member in cohort.members.select_related("proposal"):
|
||||||
research = self.venture.conduct_market_research(member.proposal, depth="light")
|
research = self.crypto_research(member.proposal, depth="light")
|
||||||
total_sources += len(research.get("sources", []))
|
total_sources += len(research.get("sources", []))
|
||||||
|
total_rejected += len(research.get("source_rejections", []))
|
||||||
if not research.get("sources"):
|
if not research.get("sources"):
|
||||||
insufficient += 1
|
insufficient += 1
|
||||||
cohort.metrics = {**cohort.metrics, "crypto_light_research_seconds": round(time.monotonic() - started, 2), "crypto_light_research_sources": total_sources, "research_insufficient_count": insufficient}
|
cohort.metrics = {**cohort.metrics, "crypto_light_research_seconds": round(time.monotonic() - started, 2), "crypto_light_research_sources": total_sources, "crypto_light_research_rejected_sources": total_rejected, "research_insufficient_count": insufficient, "research_survivors": cohort.members.count() - insufficient}
|
||||||
cohort.save(update_fields=["metrics", "updated_at"])
|
cohort.save(update_fields=["metrics", "updated_at"])
|
||||||
|
|
||||||
def protocol_research(self, cohort: VentureCohort) -> None:
|
def protocol_research(self, cohort: VentureCohort) -> None:
|
||||||
for member in cohort.members.select_related("proposal"):
|
for member in cohort.members.select_related("proposal"):
|
||||||
self._update_protocol_research_flags(member.proposal)
|
self._update_protocol_research_flags(member.proposal)
|
||||||
|
|
||||||
|
def crypto_research(self, proposal: CompanyProposal, *, depth: str = "light") -> dict[str, Any]:
|
||||||
|
query_plan = self.crypto_query_plan(proposal, depth=depth)
|
||||||
|
client = SearxngSearchClient.from_resources()
|
||||||
|
fetcher = WebPageFetcher()
|
||||||
|
all_sources = []
|
||||||
|
query_results = []
|
||||||
|
engine_health = Counter()
|
||||||
|
if client is not None:
|
||||||
|
per_query_limit = 4 if depth == "deep" else 2
|
||||||
|
for category, queries in query_plan.items():
|
||||||
|
for query in queries:
|
||||||
|
try:
|
||||||
|
payload = client.search_payload(query)
|
||||||
|
except Exception as exc:
|
||||||
|
query_results.append({"category": category, "query": query, "status": "SEARCH_INFRASTRUCTURE_FAILED", "error": str(exc), "result_count": 0})
|
||||||
|
continue
|
||||||
|
unresponsive = payload.get("unresponsive_engines", []) if isinstance(payload, dict) else []
|
||||||
|
for engine, reason in unresponsive:
|
||||||
|
engine_health[self._engine_status(str(reason))] += 1
|
||||||
|
raw_results = payload.get("results", []) if isinstance(payload, dict) else []
|
||||||
|
query_results.append({"category": category, "query": query, "status": "SEARCH_WORKED", "result_count": len(raw_results), "unresponsive_engines": unresponsive})
|
||||||
|
for item in raw_results[:per_query_limit]:
|
||||||
|
if isinstance(item, dict) and item.get("url"):
|
||||||
|
all_sources.append({"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})
|
||||||
|
pages = fetcher.fetch_many(all_sources, max_pages=10 if depth == "deep" else 5) if all_sources else []
|
||||||
|
page_by_url = {page["url"]: page for page in pages}
|
||||||
|
accepted = []
|
||||||
|
rejected = []
|
||||||
|
seen = set()
|
||||||
|
for source in all_sources:
|
||||||
|
url = source.get("url")
|
||||||
|
if not url or url in seen:
|
||||||
|
continue
|
||||||
|
seen.add(url)
|
||||||
|
enriched = {**source, **({"content": page_by_url[url].get("text", "")} if url in page_by_url else {})}
|
||||||
|
quality = self.crypto_source_quality(proposal, enriched)
|
||||||
|
if quality["accepted"]:
|
||||||
|
accepted.append({**enriched, "quality": quality["quality"], "relevance_score": quality["score"], "acceptance_reason": quality["reason"]})
|
||||||
|
else:
|
||||||
|
rejected.append({**enriched, "quality": "weak", "rejection_reason": quality["reason"], "relevance_score": quality["score"]})
|
||||||
|
covered = {source["category"] for source in accepted if source.get("quality") == "strong"}
|
||||||
|
coverage = {category: category in covered for category in CRYPTO_RESEARCH_CATEGORIES}
|
||||||
|
coverage_ratio = round(sum(1 for value in coverage.values() if value) / len(CRYPTO_RESEARCH_CATEGORIES), 2)
|
||||||
|
acceptance_rate = round(len(accepted) / max(1, len(accepted) + len(rejected)), 2)
|
||||||
|
search_infrastructure_failed = client is None or (not all_sources and any(item.get("status") == "SEARCH_INFRASTRUCTURE_FAILED" for item in query_results))
|
||||||
|
evidence_insufficient = len(accepted) == 0 or coverage_ratio < (0.7 if depth == "deep" else 0.25)
|
||||||
|
research = {"query_plan": query_plan, "query_results": query_results, "sources": accepted, "source_rejections": rejected, "source_count": len(accepted), "source_rejection_count": len(rejected), "page_fetch_count": len(pages), "search_result_count": len(all_sources), "coverage": coverage, "coverage_ratio": coverage_ratio, "category_coverage_confidence": {category: "HIGH" if coverage[category] else "LOW" for category in CRYPTO_RESEARCH_CATEGORIES}, "source_provider_health": "SEARCH_INFRASTRUCTURE_FAILED" if search_infrastructure_failed else "SEARCH_WORKED", "search_engine_health": dict(engine_health), "source_acceptance_rate": acceptance_rate, "research_status": "SEARCH_INFRASTRUCTURE_FAILED" if search_infrastructure_failed else "EVIDENCE_INSUFFICIENT" if evidence_insufficient else "SOURCE_LINKED", "unverified_categories": [category for category, ok in coverage.items() if not ok]}
|
||||||
|
proposal.market_evidence = [*self._as_list(proposal.market_evidence), *accepted, {"type": "crypto_research_coverage", "source": "crypto_research", "summary": f"{depth} crypto research coverage {coverage_ratio}", "coverage": coverage, "depth": depth}]
|
||||||
|
proposal.metadata = {**proposal.metadata, "research": research}
|
||||||
|
proposal.save(update_fields=["market_evidence", "metadata", "updated_at"])
|
||||||
|
self._artifact(proposal, proposal.mandate, "CRYPTO_MARKET_RESEARCH", f"Crypto Research ({depth})", research, f"Crypto research {depth}: {len(accepted)} accepted, {len(rejected)} rejected, coverage {coverage_ratio}.", "crypto_research")
|
||||||
|
return research
|
||||||
|
|
||||||
|
def crypto_query_plan(self, proposal: CompanyProposal, *, depth: str) -> dict[str, list[str]]:
|
||||||
|
crypto = proposal.metadata.get("crypto", {})
|
||||||
|
seed = " ".join([proposal.title, str(crypto.get("territory", "")), proposal.protocol_thesis.protocol_category]).replace("_", " ")
|
||||||
|
terms = {
|
||||||
|
"USER_PAIN": [f"{seed} user pain", f"{proposal.target_customer} decentralized protocol pain"],
|
||||||
|
"EXISTING_PROTOCOLS": [f"{seed} protocol", f"{seed} competitors crypto"],
|
||||||
|
"FAILED_PRECEDENTS": [f"{seed} failed crypto project", f"{seed} postmortem"],
|
||||||
|
"PAYMENT_MODELS": [f"{seed} stablecoin payments", f"{seed} usage fees"],
|
||||||
|
"TOKEN_MODELS": [f"{seed} token model staking", f"{seed} tokenomics"],
|
||||||
|
"STAKING_MODELS": [f"{seed} staking slashing", f"provider staking protocol slashing"],
|
||||||
|
"SLASHING_PRECEDENTS": [f"{seed} slashing precedent", f"objective slashing crypto protocol"],
|
||||||
|
"PROVIDER_ECONOMICS": [f"{seed} provider economics", f"decentralized provider marketplace economics"],
|
||||||
|
"NETWORK_BOOTSTRAP": [f"{seed} network bootstrap", f"crypto protocol bootstrap supply demand"],
|
||||||
|
"ONCHAIN_ALTERNATIVES": [f"{seed} onchain alternatives"],
|
||||||
|
"OFFCHAIN_ALTERNATIVES": [f"{seed} SaaS alternative", f"{seed} offchain alternative"],
|
||||||
|
"SECURITY_INCIDENTS": [f"{seed} security incident", f"{seed} exploit"],
|
||||||
|
"LEGAL_REGULATORY": [f"{seed} token regulatory risk", f"crypto staking slashing regulatory"],
|
||||||
|
"TOKEN_LAUNCH_PRECEDENTS": [f"{seed} token launch", f"protocol token launch precedent"],
|
||||||
|
"PRE_TOKEN_MONETIZATION_PRECEDENTS": [f"{seed} paid beta", f"crypto protocol pre token revenue"],
|
||||||
|
}
|
||||||
|
limit = 4 if depth == "deep" else 2
|
||||||
|
return {category: queries[:limit] for category, queries in terms.items()}
|
||||||
|
|
||||||
|
def crypto_source_quality(self, proposal: CompanyProposal, source: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
text = " ".join(str(source.get(key, "")) for key in ["title", "summary", "content", "url"]).lower()
|
||||||
|
url = str(source.get("url", "")).lower()
|
||||||
|
if any(noisy in url for noisy in ["pinterest", "facebook.com", "instagram.com", "x.com/intent", "webcache", "archive.org", "youtube.com/watch"]):
|
||||||
|
return {"accepted": False, "score": 0.0, "reason": "noisy or low-evidence source"}
|
||||||
|
preferred = ["docs", "github", "whitepaper", "paper", "postmortem", "audit", "security", "research", "protocol", "token", "staking", "slashing", "stablecoin", "attestation", "oracle"]
|
||||||
|
proposal_terms = set(re.findall(r"[a-z0-9]{5,}", self._proposal_text(proposal)))
|
||||||
|
source_terms = set(re.findall(r"[a-z0-9]{5,}", text))
|
||||||
|
overlap = len(proposal_terms & source_terms)
|
||||||
|
preferred_hits = sum(1 for term in preferred if term in text or term in url)
|
||||||
|
score = round(min(1.0, overlap * 0.03 + preferred_hits * 0.08), 2)
|
||||||
|
if score >= 0.22:
|
||||||
|
return {"accepted": True, "quality": "strong", "score": score, "reason": "crypto-relevant technical/protocol evidence"}
|
||||||
|
if score >= 0.12:
|
||||||
|
return {"accepted": True, "quality": "weak", "score": score, "reason": "accepted as weak crypto context"}
|
||||||
|
return {"accepted": False, "score": score, "reason": "insufficient crypto/protocol relevance"}
|
||||||
|
|
||||||
|
def _engine_status(self, reason: str) -> str:
|
||||||
|
lowered = reason.lower()
|
||||||
|
if "captcha" in lowered:
|
||||||
|
return "CAPTCHA"
|
||||||
|
if "rate" in lowered or "too many" in lowered:
|
||||||
|
return "RATE_LIMITED"
|
||||||
|
if "denied" in lowered:
|
||||||
|
return "ACCESS_DENIED"
|
||||||
|
if "timeout" in lowered:
|
||||||
|
return "TIMEOUT"
|
||||||
|
if "protocol" in lowered:
|
||||||
|
return "PROTOCOL_ERROR"
|
||||||
|
if "disabled" in lowered:
|
||||||
|
return "DISABLED"
|
||||||
|
return "UNAVAILABLE"
|
||||||
|
|
||||||
def token_red_team(self, cohort: VentureCohort) -> list[CryptoRedTeamAssessment]:
|
def token_red_team(self, cohort: VentureCohort) -> list[CryptoRedTeamAssessment]:
|
||||||
assessments = [self.red_team_proposal(member.proposal) for member in cohort.members.select_related("proposal")]
|
assessments = [self.red_team_proposal(member.proposal) for member in cohort.members.select_related("proposal")]
|
||||||
counts = Counter(flag for item in assessments for flag in item.flags)
|
counts = Counter(flag for item in assessments for flag in item.flags)
|
||||||
|
|
@ -256,7 +365,13 @@ class CryptoVentureService:
|
||||||
return assessments
|
return assessments
|
||||||
|
|
||||||
def tokenomics_simulation(self, cohort: VentureCohort) -> list[TokenomicsSimulation]:
|
def tokenomics_simulation(self, cohort: VentureCohort) -> list[TokenomicsSimulation]:
|
||||||
simulations = [self.simulate_tokenomics(member.proposal) for member in cohort.members.select_related("proposal")]
|
simulations = []
|
||||||
|
for member in cohort.members.select_related("proposal"):
|
||||||
|
self.assess_pretoken_monetization(member.proposal)
|
||||||
|
self.token_role_decomposition(member.proposal)
|
||||||
|
self.token_economic_model(member.proposal)
|
||||||
|
simulations.append(self.simulate_tokenomics(member.proposal))
|
||||||
|
self.token_launch_readiness(member.proposal)
|
||||||
self.crypto_scenario_lab(cohort)
|
self.crypto_scenario_lab(cohort)
|
||||||
return simulations
|
return simulations
|
||||||
|
|
||||||
|
|
@ -273,7 +388,7 @@ class CryptoVentureService:
|
||||||
members = list(cohort.members.select_related("proposal").order_by("-portfolio_score", "created_at")[:5])
|
members = list(cohort.members.select_related("proposal").order_by("-portfolio_score", "created_at")[:5])
|
||||||
for member in members:
|
for member in members:
|
||||||
before[str(member.proposal_id)] = member.proposal.metadata.get("research", {}).get("coverage_ratio", 0.0)
|
before[str(member.proposal_id)] = member.proposal.metadata.get("research", {}).get("coverage_ratio", 0.0)
|
||||||
self.venture.conduct_market_research(member.proposal, depth="deep")
|
self.crypto_research(member.proposal, depth="deep")
|
||||||
after[str(member.proposal_id)] = member.proposal.metadata.get("research", {}).get("coverage_ratio", 0.0)
|
after[str(member.proposal_id)] = member.proposal.metadata.get("research", {}).get("coverage_ratio", 0.0)
|
||||||
cohort.metrics = {**cohort.metrics, "top5_deep_research_count": len(members), "top5_research_coverage_before": before, "top5_research_coverage_after": after}
|
cohort.metrics = {**cohort.metrics, "top5_deep_research_count": len(members), "top5_research_coverage_before": before, "top5_research_coverage_after": after}
|
||||||
cohort.save(update_fields=["metrics", "updated_at"])
|
cohort.save(update_fields=["metrics", "updated_at"])
|
||||||
|
|
@ -292,7 +407,7 @@ class CryptoVentureService:
|
||||||
member.save(update_fields=["rank", "portfolio_score", "is_top_3", "updated_at"])
|
member.save(update_fields=["rank", "portfolio_score", "is_top_3", "updated_at"])
|
||||||
row["rank"] = rank
|
row["rank"] = rank
|
||||||
review, _ = PortfolioICReview.objects.update_or_create(cohort=cohort, defaults={"rankings": rows, "top_3": top_3, "concentration": self._crypto_concentration(rows), "metadata": {"finalist_shortfall": max(0, 3 - len(top_3)), "no_fund_decision_v01": True}})
|
review, _ = PortfolioICReview.objects.update_or_create(cohort=cohort, defaults={"rankings": rows, "top_3": top_3, "concentration": self._crypto_concentration(rows), "metadata": {"finalist_shortfall": max(0, 3 - len(top_3)), "no_fund_decision_v01": True}})
|
||||||
cohort.metrics = {**cohort.metrics, "crypto_ranked_count": len(rows), "crypto_top_3_count": len(top_3)}
|
cohort.metrics = {**cohort.metrics, "crypto_ranked_count": len(rows), "crypto_top_3_count": len(top_3), "autonomous_crypto_survivors": len([row for row in rows if row.get("crypto_autonomy_class") == CryptoAutonomyClass.AUTONOMOUS_CRYPTO]), "finalists": len(top_3)}
|
||||||
cohort.status = "CRYPTO_IC_COMPLETE"
|
cohort.status = "CRYPTO_IC_COMPLETE"
|
||||||
cohort.save(update_fields=["metrics", "status", "updated_at"])
|
cohort.save(update_fields=["metrics", "status", "updated_at"])
|
||||||
return review
|
return review
|
||||||
|
|
@ -306,18 +421,31 @@ class CryptoVentureService:
|
||||||
required = ["contract", "oracle", "key", "admin", "pause", "treasury"]
|
required = ["contract", "oracle", "key", "admin", "pause", "treasury"]
|
||||||
missing = [item for item in required if item not in text]
|
missing = [item for item in required if item not in text]
|
||||||
critical = any(self._positive_phrase_present(text, term) for term in ["unaudited mainnet", "custody user funds", "bridge dependency", "upgradeable without timelock"])
|
critical = any(self._positive_phrase_present(text, term) for term in ["unaudited mainnet", "custody user funds", "bridge dependency", "upgradeable without timelock"])
|
||||||
status = "BLOCKED_HUMAN_SECURITY_GATE" if critical or len(missing) >= 4 else "PASS_WITH_SECURITY_REVIEW"
|
decision = ProtocolSecurityDecision.BLOCK_TESTNET if critical or len(missing) >= 5 else ProtocolSecurityDecision.REVISE_SECURITY_MODEL if len(missing) >= 3 else ProtocolSecurityDecision.PASS_FOR_TESTNET_DESIGN
|
||||||
if status.startswith("BLOCKED"):
|
if decision == ProtocolSecurityDecision.BLOCK_TESTNET:
|
||||||
blocked += 1
|
blocked += 1
|
||||||
else:
|
else:
|
||||||
passed += 1
|
passed += 1
|
||||||
self._artifact(proposal, cohort.mandate, "CRYPTO_PROTOCOL_SECURITY_GATE", f"Protocol Security Gate: {proposal.title}", {"status": status, "missing_controls": missing, "critical_risk": critical, "mainnet_allowed": False}, f"{status}. Missing controls: {', '.join(missing) or 'none'}. Mainnet is not allowed in V0.1.", "crypto_protocol_security_gate", graph_run=cohort.graph_run)
|
assessment = ProtocolSecurityAssessment.objects.update_or_create(proposal=proposal, defaults={"decision": decision, "threat_model": {"contract_architecture": "design-stage only", "staking_slashing_logic": "requires objective measurable failures", "oracle_assumptions": "must avoid circular/manual oracle dependency", "upgradeability": "timelock/admin controls required", "pause_emergency_controls": "required before testnet", "mainnet_allowed": False}, "missing_controls": missing, "privileged_roles": ["admin", "treasury", "pauser"], "economic_attack_surfaces": ["sybil providers", "collusion", "oracle manipulation", "slash griefing"], "key_management_assumptions": ["no production key custody in V0.2", "multisig/timelock required later"], "rationale": "Guard-style protocol threat model review; no Solidity audit or mainnet approval."})[0]
|
||||||
proposal.metadata = {**proposal.metadata, "crypto_security_gate": {"status": status, "missing_controls": missing, "critical_risk": critical, "mainnet_allowed": False}}
|
self._artifact(proposal, cohort.mandate, "CRYPTO_PROTOCOL_SECURITY_GATE", f"Protocol Security Gate: {proposal.title}", {"decision": assessment.decision, "missing_controls": missing, "critical_risk": critical, "mainnet_allowed": False}, f"{assessment.decision}. Missing controls: {', '.join(missing) or 'none'}. Mainnet is not allowed in V0.2.", "crypto_guard_workflow", graph_run=cohort.graph_run)
|
||||||
|
proposal.metadata = {**proposal.metadata, "crypto_security_gate": {"decision": assessment.decision, "missing_controls": missing, "critical_risk": critical, "mainnet_allowed": False}}
|
||||||
proposal.save(update_fields=["metadata", "updated_at"])
|
proposal.save(update_fields=["metadata", "updated_at"])
|
||||||
cohort.metrics = {**cohort.metrics, "protocol_security_gate_blocked": blocked, "protocol_security_gate_passed": passed}
|
cohort.metrics = {**cohort.metrics, "protocol_security_gate_blocked": blocked, "protocol_security_gate_passed": passed}
|
||||||
cohort.save(update_fields=["metrics", "updated_at"])
|
cohort.save(update_fields=["metrics", "updated_at"])
|
||||||
return {"blocked": blocked, "passed": passed}
|
return {"blocked": blocked, "passed": passed}
|
||||||
|
|
||||||
|
def token_launch_readiness(self, proposal: CompanyProposal) -> TokenLaunchReadinessAssessment:
|
||||||
|
simulation = getattr(proposal, "tokenomics_simulation", None)
|
||||||
|
token = getattr(proposal, "token_utility_assessment", None) or self.assess_token_necessity(proposal)
|
||||||
|
security = getattr(proposal, "protocol_security_assessment", None)
|
||||||
|
if token.classification in {TokenNecessityClassification.TOKEN_OPTIONAL, TokenNecessityClassification.TOKEN_UNNECESSARY}:
|
||||||
|
status = TokenLaunchReadinessStatus.TOKEN_MODEL_REVISION_REQUIRED
|
||||||
|
elif security and security.decision == ProtocolSecurityDecision.BLOCK_TESTNET:
|
||||||
|
status = TokenLaunchReadinessStatus.SECURITY_REVIEW_REQUIRED
|
||||||
|
else:
|
||||||
|
status = TokenLaunchReadinessStatus.PRODUCT_TRACTION_REQUIRED
|
||||||
|
return TokenLaunchReadinessAssessment.objects.update_or_create(proposal=proposal, defaults={"real_users": False, "repeat_usage": False, "real_protocol_fees": False, "token_necessity_validated": False, "token_demand_loop_validated": False, "security_audit_status": "NOT_AUDITED_DESIGN_ONLY", "testnet_stability": "NOT_PROVEN", "tokenomics_stress_tests": simulation.summary if simulation else {}, "legal_review_status": "LEGAL_REVIEW_REQUIRED", "jurisdiction_policy_status": "US_EXCLUDED_TOKEN_SALE_DISABLED", "admin_key_controls": "DESIGN_REQUIRED", "treasury_controls": "DESIGN_REQUIRED", "decentralization_readiness": "NOT_READY", "launch_readiness_status": status})[0]
|
||||||
|
|
||||||
def regulatory_gate(self, cohort: VentureCohort) -> None:
|
def regulatory_gate(self, cohort: VentureCohort) -> None:
|
||||||
gated = 0
|
gated = 0
|
||||||
for member in cohort.members.select_related("proposal"):
|
for member in cohort.members.select_related("proposal"):
|
||||||
|
|
@ -328,8 +456,20 @@ class CryptoVentureService:
|
||||||
cohort.save(update_fields=["metrics", "updated_at"])
|
cohort.save(update_fields=["metrics", "updated_at"])
|
||||||
|
|
||||||
def capability_analysis(self, cohort: VentureCohort) -> list[dict[str, Any]]:
|
def capability_analysis(self, cohort: VentureCohort) -> list[dict[str, Any]]:
|
||||||
gaps = ["smart contract audit", "testnet deployment", "wallet auth", "key management", "oracle/provider monitoring", "token simulation harness", "legal review workflow"]
|
capabilities = [
|
||||||
rows = [{"capability": gap, "count": cohort.members.count(), "status": "MISSING", "earliest_stage": "BEFORE_VALIDATION"} for gap in gaps]
|
("Guard model", "AVAILABLE"),
|
||||||
|
("protocol threat modelling", "AVAILABLE"),
|
||||||
|
("Solidity implementation", "AVAILABLE"),
|
||||||
|
("token simulation harness", "AVAILABLE"),
|
||||||
|
("crypto research workflow", "PARTIAL"),
|
||||||
|
("testnet deployment", "PARTIAL"),
|
||||||
|
("wallet auth", "MISSING"),
|
||||||
|
("key management", "MISSING"),
|
||||||
|
("contract deployment pipeline", "MISSING"),
|
||||||
|
("oracle/provider monitoring", "MISSING"),
|
||||||
|
("legal review workflow", "MISSING"),
|
||||||
|
]
|
||||||
|
rows = [{"capability": capability, "count": cohort.members.count(), "status": status, "earliest_stage": "BEFORE_VALIDATION"} for capability, status in capabilities]
|
||||||
review = getattr(cohort, "portfolio_review", None)
|
review = getattr(cohort, "portfolio_review", None)
|
||||||
if review:
|
if review:
|
||||||
review.capability_demand = rows
|
review.capability_demand = rows
|
||||||
|
|
@ -356,19 +496,36 @@ class CryptoVentureService:
|
||||||
def produce_crypto_cohort_report(self, cohort: VentureCohort) -> VentureArtifact:
|
def produce_crypto_cohort_report(self, cohort: VentureCohort) -> VentureArtifact:
|
||||||
review = cohort.portfolio_review
|
review = cohort.portfolio_review
|
||||||
rows = review.rankings
|
rows = review.rankings
|
||||||
content = {"title": "CRYPTO VENTURE COHORT V0.1 REPORT", "cohort_id": cohort.cohort_id, "graph_run": str(cohort.graph_run_id or ""), "runtime": cohort.metrics, "accepted_protocols": cohort.members.count(), "generation_attempts": cohort.metrics.get("generation_attempts", 0), "duplicate_token_necessity_rejections": {"duplicate_rejections": cohort.metrics.get("duplicate_rejections", 0), "token_unnecessary_rejections": cohort.metrics.get("token_unnecessary_rejections", 0)}, "ranking": rows, "top_3": review.top_3, "token_red_team_failures": cohort.metrics.get("token_red_team_failures", {}), "token_utility_distribution": dict(Counter(util for row in rows for util in row.get("token_utility", []))), "crypto_thesis_saturation": review.concentration, "capability_gaps": review.capability_demand, "system_metrics": cohort.metrics, "stop_conditions": {"token_sale": False, "fundraising": False, "mainnet_issuance": False, "user_contact": False, "real_spend": 0}}
|
research_health = self._research_health(rows)
|
||||||
artifact = self._artifact(None, cohort.mandate, "CRYPTO_VENTURE_COHORT_REPORT", "Crypto Venture Cohort V0.1 Report", content, self._readable_report(content), "crypto_portfolio_ic", graph_run=cohort.graph_run)
|
content = {"title": "CRYPTO VENTURE COHORT V0.2", "cohort_id": cohort.cohort_id, "graph_run": str(cohort.graph_run_id or ""), "runtime": cohort.metrics, "raw_protocols_generated": cohort.metrics.get("raw_generated", 0), "token_unnecessary": cohort.metrics.get("token_unnecessary_rejections", 0), "token_optional_routed_saas": cohort.metrics.get("token_optional_route_to_saas", 0), "duplicates": cohort.metrics.get("duplicate_rejections", 0), "crypto_survivors": cohort.members.count(), "autonomous_crypto_survivors": cohort.metrics.get("autonomous_crypto_survivors", 0), "security_blocked": cohort.metrics.get("protocol_security_gate_blocked", 0), "finalists": len(review.top_3), "generation_attempts": cohort.metrics.get("generation_attempts", 0), "ranking": rows, "top_3": review.top_3, "token_red_team_failures": cohort.metrics.get("token_red_team_failures", {}), "token_utility_distribution": dict(Counter(util for row in rows for util in row.get("token_utility", []))), "crypto_thesis_saturation": review.concentration, "capability_gaps": review.capability_demand, "research_health": research_health, "system_metrics": cohort.metrics, "stop_conditions": {"token_sale": False, "nft_sale": False, "founding_membership_sale": False, "fundraising": False, "mainnet_issuance": False, "user_contact": False, "real_spend": 0}}
|
||||||
|
artifact = self._artifact(None, cohort.mandate, "CRYPTO_VENTURE_COHORT_REPORT", "Crypto Venture Cohort V0.2 Report", content, self._readable_report(content), "crypto_portfolio_ic", graph_run=cohort.graph_run)
|
||||||
cohort.status = "COMPLETE"
|
cohort.status = "COMPLETE"
|
||||||
cohort.save(update_fields=["status", "updated_at"])
|
cohort.save(update_fields=["status", "updated_at"])
|
||||||
return artifact
|
return artifact
|
||||||
|
|
||||||
|
def _research_health(self, rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||||
|
engine_counts = Counter()
|
||||||
|
accepted = 0
|
||||||
|
rejected = 0
|
||||||
|
coverage = []
|
||||||
|
for row in rows:
|
||||||
|
research = row.get("research", {})
|
||||||
|
accepted += int(research.get("source_count", 0) or 0)
|
||||||
|
rejected += int(research.get("source_rejection_count", 0) or 0)
|
||||||
|
coverage.append(float(research.get("coverage_ratio", 0) or 0))
|
||||||
|
engine_counts.update(research.get("search_engine_health", {}))
|
||||||
|
return {"search_engines": dict(engine_counts), "sources_accepted": accepted, "sources_rejected": rejected, "average_coverage": round(sum(coverage) / max(1, len(coverage)), 2), "healthy": engine_counts.get("AVAILABLE", 0), "rate_limited": engine_counts.get("RATE_LIMITED", 0), "captcha": engine_counts.get("CAPTCHA", 0), "denied": engine_counts.get("ACCESS_DENIED", 0), "timed_out": engine_counts.get("TIMEOUT", 0)}
|
||||||
|
|
||||||
def assess_token_necessity(self, proposal: CompanyProposal) -> TokenUtilityAssessment:
|
def assess_token_necessity(self, proposal: CompanyProposal) -> TokenUtilityAssessment:
|
||||||
crypto = proposal.metadata.get("crypto", {})
|
crypto = proposal.metadata.get("crypto", {})
|
||||||
text = self._proposal_text(proposal)
|
text = self._proposal_text(proposal)
|
||||||
utilities = [u for u in crypto.get("token_utility", []) if isinstance(u, str)] or self._infer_utilities(text)
|
utilities = [u for u in crypto.get("token_utility", []) if isinstance(u, str)] or self._infer_utilities(text)
|
||||||
|
roles = self.token_role_decomposition(proposal)
|
||||||
strong_count = sum(1 for utility in utilities if utility.lower() in ALLOWED_TOKEN_UTILITIES or any(term in utility.lower() for term in ["stake", "slash", "collateral", "fee", "marketplace", "attestation", "compute", "machine", "security"]))
|
strong_count = sum(1 for utility in utilities if utility.lower() in ALLOWED_TOKEN_UTILITIES or any(term in utility.lower() for term in ["stake", "slash", "collateral", "fee", "marketplace", "attestation", "compute", "machine", "security"]))
|
||||||
weak = any(term in text for term in ["meme", "speculative", "community token", "governance token only", "token gated subscription"])
|
required_roles = len(roles.required_roles)
|
||||||
score = min(100, 35 + strong_count * 18 + (10 if "slash" in text or "slashing" in text else 0) + (8 if "provider" in text else 0) - (35 if weak else 0))
|
weak = any(term in text for term in ["meme", "speculative", "community token", "marketing token", "community marketing", "governance token only", "token gated subscription"])
|
||||||
|
payment_only = roles.required_roles == ["PAYMENT"]
|
||||||
|
score = min(100, 25 + strong_count * 10 + required_roles * 16 + (12 if "slash" in text or "slashing" in text else 0) + (8 if "provider" in text else 0) - (25 if payment_only else 0) - (35 if weak else 0))
|
||||||
sol_review = self._sol_json("Counterfactual token necessity review. If the token were removed and replaced with fiat/stablecoin/database credits, would the product materially degrade? Return JSON with classification TOKEN_ESSENTIAL, TOKEN_STRONGLY_JUSTIFIED, TOKEN_OPTIONAL, or TOKEN_UNNECESSARY; score 0-100; rationale; fiat_or_database_substitution. Proposal: " + json.dumps(proposal.pitch, default=str) + " Crypto: " + json.dumps(crypto, default=str))
|
sol_review = self._sol_json("Counterfactual token necessity review. If the token were removed and replaced with fiat/stablecoin/database credits, would the product materially degrade? Return JSON with classification TOKEN_ESSENTIAL, TOKEN_STRONGLY_JUSTIFIED, TOKEN_OPTIONAL, or TOKEN_UNNECESSARY; score 0-100; rationale; fiat_or_database_substitution. Proposal: " + json.dumps(proposal.pitch, default=str) + " Crypto: " + json.dumps(crypto, default=str))
|
||||||
if sol_review:
|
if sol_review:
|
||||||
score = float(sol_review.get("score", score))
|
score = float(sol_review.get("score", score))
|
||||||
|
|
@ -385,7 +542,57 @@ class CryptoVentureService:
|
||||||
classification = TokenNecessityClassification.TOKEN_UNNECESSARY
|
classification = TokenNecessityClassification.TOKEN_UNNECESSARY
|
||||||
if sol_classification in TokenNecessityClassification.values:
|
if sol_classification in TokenNecessityClassification.values:
|
||||||
classification = sol_classification
|
classification = sol_classification
|
||||||
return TokenUtilityAssessment.objects.update_or_create(proposal=proposal, defaults={"classification": classification, "token_necessity_score": score, "utility_categories": utilities, "fiat_or_database_substitution": str((sol_review or {}).get("fiat_or_database_substitution", "If normal fiat/stablecoin/database credits preserve the core coordination, route to SaaS.")), "rationale": str((sol_review or {}).get("rationale", "Token score is based on explicit non-speculative utility, staking/slashing, marketplace coordination, and usage-linked fee demand.")), "metadata": {"weak_utility_terms": INSUFFICIENT_TOKEN_UTILITIES, "sol_counterfactual_review": sol_review or {}}})[0]
|
return TokenUtilityAssessment.objects.update_or_create(proposal=proposal, defaults={"classification": classification, "token_necessity_score": score, "utility_categories": utilities, "fiat_or_database_substitution": str((sol_review or {}).get("fiat_or_database_substitution", "If normal fiat/stablecoin/database credits preserve the core coordination, route to SaaS.")), "rationale": str((sol_review or {}).get("rationale", "Token score derives from genuinely required roles, not native payment currency alone.")), "metadata": {"weak_utility_terms": INSUFFICIENT_TOKEN_UTILITIES, "sol_counterfactual_review": sol_review or {}, "required_roles": roles.required_roles, "payment_only_penalty": payment_only}})[0]
|
||||||
|
|
||||||
|
def token_role_decomposition(self, proposal: CompanyProposal) -> TokenRoleDecomposition:
|
||||||
|
text = self._proposal_text(proposal)
|
||||||
|
roles = {}
|
||||||
|
role_terms = {
|
||||||
|
"PAYMENT": ["fee", "payment", "settlement"],
|
||||||
|
"SECURITY_BOND": ["bond", "security budget"],
|
||||||
|
"SLASHABLE_COLLATERAL": ["slash", "slashing", "collateral"],
|
||||||
|
"GOVERNANCE": ["governance", "vote"],
|
||||||
|
"RESOURCE_ACCESS": ["access", "scarce", "quota"],
|
||||||
|
"REPUTATION": ["reputation", "attestation"],
|
||||||
|
"REWARD": ["reward", "provider reward"],
|
||||||
|
"TREASURY": ["treasury"],
|
||||||
|
"OTHER": [],
|
||||||
|
}
|
||||||
|
for role, terms in role_terms.items():
|
||||||
|
if role == "PAYMENT" and any(term in text for term in terms):
|
||||||
|
value = TokenRoleRequirement.USEFUL
|
||||||
|
elif role == "GOVERNANCE" and any(term in text for term in terms) and not any(term in text for term in ["parameter", "slashing", "treasury"]):
|
||||||
|
value = TokenRoleRequirement.OPTIONAL
|
||||||
|
elif any(term in text for term in terms):
|
||||||
|
value = TokenRoleRequirement.REQUIRED if role in {"SECURITY_BOND", "SLASHABLE_COLLATERAL", "REPUTATION"} else TokenRoleRequirement.USEFUL
|
||||||
|
else:
|
||||||
|
value = TokenRoleRequirement.UNNECESSARY
|
||||||
|
roles[role] = value
|
||||||
|
required = [role for role, value in roles.items() if value == TokenRoleRequirement.REQUIRED]
|
||||||
|
stablecoin = {"MODEL_A_native_payment_staking_governance": "baseline proposal", "MODEL_B_stablecoin_payment_native_bond": "preferred if payment utility is separable", "MODEL_C_stablecoin_payment_stablecoin_collateral": "valid if slashing/collateral does not need protocol-native exposure", "MODEL_D_centralized_database_credits": "routes to SaaS if verification/settlement/reputation do not degrade", "usdc_identical_payment_utility": roles.get("PAYMENT") != TokenRoleRequirement.REQUIRED, "stable_collateral_identical_security": "SLASHABLE_COLLATERAL" not in required and "SECURITY_BOND" not in required}
|
||||||
|
outcome = "Native token removal materially degrades security/reputation if required roles remain." if required else "Native token removed changes little; route to SaaS or revise token model."
|
||||||
|
return TokenRoleDecomposition.objects.update_or_create(proposal=proposal, defaults={"roles": roles, "required_roles": required, "stablecoin_counterfactual": stablecoin, "native_token_removed_outcome": outcome})[0]
|
||||||
|
|
||||||
|
def assess_pretoken_monetization(self, proposal: CompanyProposal) -> PreTokenMonetizationAssessment:
|
||||||
|
text = self._proposal_text(proposal)
|
||||||
|
mode = PreTokenMonetizationMode.STABLECOIN_USAGE_FEES if "usage" in text or "api" in text else PreTokenMonetizationMode.PAID_BETA_ACCESS if "testnet" in text else PreTokenMonetizationMode.NONE
|
||||||
|
if "membership" in text or "founding" in text:
|
||||||
|
mode = PreTokenMonetizationMode.FOUNDING_MEMBERSHIP_NFT
|
||||||
|
immediate = ["testnet/API access", "usage credits", "priority queues", "protocol analytics"] if mode != PreTokenMonetizationMode.NONE else []
|
||||||
|
score = 0 if mode == PreTokenMonetizationMode.NONE else 55 + (15 if "api" in text else 0) + (10 if "automated" in text or "machine" in text else 0)
|
||||||
|
score = min(100, score)
|
||||||
|
membership_policy = {"no_investment_return": True, "no_equity": True, "no_profit_or_revenue_share": True, "no_guaranteed_native_token_allocation": True, "no_appreciation_promise": True, "useful_without_future_token": True, "public_sale_requires_human_legal_gate": True, "v02_sale_executed": False}
|
||||||
|
ladder = {"1000": "sell small paid beta/API-credit packages", "5000": "10-25 paid beta users or usage-credit customers", "10000": "recurring API/subscription/service-credit revenue without native token"}
|
||||||
|
return PreTokenMonetizationAssessment.objects.update_or_create(proposal=proposal, defaults={"mode": mode, "pre_token_monetization_potential": score, "pre_token_business": "Sell useful product/network access before native token issuance using fiat/stablecoin credits, paid beta, subscription, or legally reviewed membership. No native token required.", "immediate_utility": immediate, "revenue_ladder": ladder, "founding_membership_policy": membership_policy, "autonomous_fulfillment_notes": "Prefer self-service API/testnet onboarding, automated usage metering, and docs-first support."})[0]
|
||||||
|
|
||||||
|
def token_economic_model(self, proposal: CompanyProposal) -> TokenEconomicModel:
|
||||||
|
text = self._proposal_text(proposal)
|
||||||
|
token = getattr(proposal, "token_utility_assessment", None) or self.assess_token_necessity(proposal)
|
||||||
|
avg_fee = round(0.03 + token.token_necessity_score / 1200 + (0.04 if "api" in text else 0), 3)
|
||||||
|
providers = max(3, 4 + len(token.utility_categories) * 2)
|
||||||
|
users = max(25, int(float(proposal.confidence) * 220))
|
||||||
|
stake = 200 + int(token.token_necessity_score * 10)
|
||||||
|
return TokenEconomicModel.objects.update_or_create(proposal=proposal, defaults={"unit_of_service": "verified job/API call/attestation", "expected_usage_frequency": "weekly to daily machine/API usage", "transaction_fee_model": "Per-use stablecoin or credit fee pre-token; protocol fees may later settle through approved token design.", "average_fee": avg_fee, "payment_asset": "stablecoin_or_fiat_pre_token", "stake_requirement": stake, "collateral_requirement": stake, "provider_count": providers, "demand_side_users": users, "slash_event_assumptions": {"baseline_rate": 0.01, "high_slash_rate": 0.08, "objective_conditions_required": True}, "slash_amount": round(stake * 0.15, 2), "emission_schedule": {"v02": "none", "post_launch_model_only": "declining bootstrap subsidies"}, "bootstrap_subsidies": 0.0, "treasury_share": 0.2, "provider_reward_share": 0.8, "token_sinks": ["provider bonds", "slashing penalties", "protocol fees", "security budget"], "unlock_assumptions": {"v02": "none"}, "circulation_assumptions": {"v02_native_token_supply": 0}})[0]
|
||||||
|
|
||||||
def _create_proposal(self, mandate: CompanyMandate, payload: dict[str, Any], source: str, territory: str) -> CompanyProposal:
|
def _create_proposal(self, mandate: CompanyMandate, payload: dict[str, Any], source: str, territory: str) -> CompanyProposal:
|
||||||
title = str(payload.get("name") or payload.get("title") or f"Protocol {territory.title()}")[:255]
|
title = str(payload.get("name") or payload.get("title") or f"Protocol {territory.title()}")[:255]
|
||||||
|
|
@ -435,6 +642,7 @@ class CryptoVentureService:
|
||||||
onchain = self.onchain_assessment(proposal)
|
onchain = self.onchain_assessment(proposal)
|
||||||
value = self.value_capture_assessment(proposal)
|
value = self.value_capture_assessment(proposal)
|
||||||
policy = self.regulatory_policy(proposal)
|
policy = self.regulatory_policy(proposal)
|
||||||
|
roles = self.token_role_decomposition(proposal)
|
||||||
flags = []
|
flags = []
|
||||||
if token.classification in {TokenNecessityClassification.TOKEN_OPTIONAL, TokenNecessityClassification.TOKEN_UNNECESSARY}:
|
if token.classification in {TokenNecessityClassification.TOKEN_OPTIONAL, TokenNecessityClassification.TOKEN_UNNECESSARY}:
|
||||||
flags.append(TokenRedTeamFlag.TOKEN_NOT_REQUIRED)
|
flags.append(TokenRedTeamFlag.TOKEN_NOT_REQUIRED)
|
||||||
|
|
@ -451,7 +659,26 @@ class CryptoVentureService:
|
||||||
flags.append(TokenRedTeamFlag.SPECULATION_DEPENDENT)
|
flags.append(TokenRedTeamFlag.SPECULATION_DEPENDENT)
|
||||||
if "governance" in text and "slash" not in text and "fee" not in text:
|
if "governance" in text and "slash" not in text and "fee" not in text:
|
||||||
flags.append(TokenRedTeamFlag.GOVERNANCE_THEATER)
|
flags.append(TokenRedTeamFlag.GOVERNANCE_THEATER)
|
||||||
sol_review = self._sol_json("Independent Token Red Team. Return JSON with flags list using only TOKEN_NOT_REQUIRED, SPECULATION_DEPENDENT, UNSUSTAINABLE_EMISSIONS, VALUE_CAPTURE_BROKEN, MERCENARY_INCENTIVES, GOVERNANCE_THEATER, SECURITY_MODEL_WEAK, TOKEN_VELOCITY_TOO_HIGH, BOOTSTRAP_PROBLEM, CENTRALIZATION_CONTRADICTION, REGULATORY_RISK_HIGH, ONCHAIN_NOT_REQUIRED, NO_REAL_USER_DEMAND; severity LOW/MEDIUM/HIGH; critique. Proposal: " + json.dumps(self.crypto_score_context(proposal), default=str))
|
if len([role for role, value in roles.roles.items() if value in {TokenRoleRequirement.REQUIRED, TokenRoleRequirement.USEFUL}]) >= 6:
|
||||||
|
flags.append(TokenRedTeamFlag.TOKEN_DOES_TOO_MUCH)
|
||||||
|
if roles.roles.get("PAYMENT") in {TokenRoleRequirement.USEFUL, TokenRoleRequirement.OPTIONAL}:
|
||||||
|
flags.append(TokenRedTeamFlag.NATIVE_PAYMENT_NOT_REQUIRED)
|
||||||
|
if roles.stablecoin_counterfactual.get("stable_collateral_identical_security"):
|
||||||
|
flags.append(TokenRedTeamFlag.STABLECOIN_SUBSTITUTE_WORKS)
|
||||||
|
flags.append(TokenRedTeamFlag.COLLATERAL_CAN_BE_EXTERNAL)
|
||||||
|
if any(term in text for term in ["buyback", "revenue share", "profit share"]):
|
||||||
|
flags.append(TokenRedTeamFlag.REVENUE_CLAIM_LANGUAGE)
|
||||||
|
if "buyback" in text:
|
||||||
|
flags.append(TokenRedTeamFlag.BUYBACK_DEPENDENCY)
|
||||||
|
if "yield" in text or "apy" in text:
|
||||||
|
flags.append(TokenRedTeamFlag.YIELD_DEPENDENCY)
|
||||||
|
if "manual dispute" in text or "human court" in text:
|
||||||
|
flags.append(TokenRedTeamFlag.HUMAN_DISPUTE_DEPENDENCY)
|
||||||
|
if "central operator" in text:
|
||||||
|
flags.append(TokenRedTeamFlag.CENTRAL_OPERATOR_CONTRADICTION)
|
||||||
|
if "subjective" in text and "slashing" in text:
|
||||||
|
flags.append(TokenRedTeamFlag.SLASHING_NOT_OBJECTIVELY_MEASURABLE)
|
||||||
|
sol_review = self._sol_json("Independent Token Red Team V0.2. Return JSON with flags list using only TOKEN_NOT_REQUIRED, SPECULATION_DEPENDENT, UNSUSTAINABLE_EMISSIONS, VALUE_CAPTURE_BROKEN, MERCENARY_INCENTIVES, GOVERNANCE_THEATER, SECURITY_MODEL_WEAK, TOKEN_VELOCITY_TOO_HIGH, BOOTSTRAP_PROBLEM, CENTRALIZATION_CONTRADICTION, REGULATORY_RISK_HIGH, ONCHAIN_NOT_REQUIRED, NO_REAL_USER_DEMAND, TOKEN_DOES_TOO_MUCH, NATIVE_PAYMENT_NOT_REQUIRED, STABLECOIN_SUBSTITUTE_WORKS, COLLATERAL_CAN_BE_EXTERNAL, REVENUE_CLAIM_LANGUAGE, BUYBACK_DEPENDENCY, YIELD_DEPENDENCY, SPECULATIVE_BOOTSTRAP, EMISSIONS_DEPENDENCY, SECURITY_TOKEN_CONCENTRATION, SLASHING_NOT_OBJECTIVELY_MEASURABLE, ORACLE_CIRCULARITY, HUMAN_DISPUTE_DEPENDENCY, CENTRAL_OPERATOR_CONTRADICTION, NETWORK_NOT_READY_FOR_DECENTRALIZATION; severity LOW/MEDIUM/HIGH; critique. Proposal: " + json.dumps(self.crypto_score_context(proposal), default=str))
|
||||||
if sol_review:
|
if sol_review:
|
||||||
for flag in self._as_list(sol_review.get("flags")):
|
for flag in self._as_list(sol_review.get("flags")):
|
||||||
if str(flag) in TokenRedTeamFlag.values:
|
if str(flag) in TokenRedTeamFlag.values:
|
||||||
|
|
@ -489,26 +716,27 @@ class CryptoVentureService:
|
||||||
|
|
||||||
def simulate_tokenomics(self, proposal: CompanyProposal) -> TokenomicsSimulation:
|
def simulate_tokenomics(self, proposal: CompanyProposal) -> TokenomicsSimulation:
|
||||||
scenarios = {}
|
scenarios = {}
|
||||||
text = self._proposal_text(proposal)
|
model = getattr(proposal, "token_economic_model", None) or self.token_economic_model(proposal)
|
||||||
token = getattr(proposal, "token_utility_assessment", None) or self.assess_token_necessity(proposal)
|
roles = getattr(proposal, "token_role_decomposition", None) or self.token_role_decomposition(proposal)
|
||||||
value = getattr(proposal, "value_capture", None) or self.value_capture_assessment(proposal)
|
base_users = max(1, model.demand_side_users)
|
||||||
base_users = max(20, int(float(proposal.confidence) * 180))
|
tx_per_user = 10
|
||||||
fee_per_tx = 0.04 + (token.token_necessity_score / 1000) + (0.03 if "enterprise" in text or "infrastructure" in text else 0)
|
scenario_multipliers = {"LOW_USAGE": 0.3, "EXPECTED_USAGE": 1.0, "HIGH_USAGE": 3.0, "NO_SUBSIDY": 0.9, "TOKEN_PRICE_DOWN_80_PERCENT": 0.8, "TOKEN_PRICE_UP_10X": 1.2, "PROVIDER_CHURN_50_PERCENT": 0.7, "USER_GROWTH_10X": 10.0, "HIGH_SLASH_RATE": 1.0, "ZERO_NEW_TOKEN_EMISSIONS": 1.0, "STABLECOIN_PAYMENT_SUBSTITUTE": 1.0, "NATIVE_TOKEN_REMOVED": 1.0}
|
||||||
tx_per_user = 6 + len(token.utility_categories) * 2 + (4 if "agent" in text or "machine" in text else 0)
|
for name, multiplier in scenario_multipliers.items():
|
||||||
staking_per_provider = 150 + int(value.value_accrual_quality_score * 8)
|
|
||||||
emission_rate = max(0.005, (100 - value.tokenomics_sustainability_score) / 1400)
|
|
||||||
for name, multiplier in {"LOW_USAGE": 0.3, "EXPECTED": 1.0, "HIGH_USAGE": 3.0, "SUBSIDY_REMOVED": 0.8, "TOKEN_PRICE_DOWN_80_PERCENT": 0.8, "PROVIDER_CHURN": 0.6, "USER_GROWTH_10X": 10.0}.items():
|
|
||||||
users = int(base_users * multiplier)
|
users = int(base_users * multiplier)
|
||||||
tx = int(users * tx_per_user)
|
tx = int(users * tx_per_user)
|
||||||
fees = round(tx * fee_per_tx, 2)
|
fees = round(tx * model.average_fee, 2)
|
||||||
provider_supply = max(3, int(users / (15 if "market" in text else 25)))
|
provider_supply = max(1, int(model.provider_count * multiplier))
|
||||||
staking = provider_supply * staking_per_provider
|
staking = provider_supply * model.stake_requirement
|
||||||
emissions = 0 if name == "SUBSIDY_REMOVED" else round(max(3, tx * emission_rate), 2)
|
slash_rate = float(model.slash_event_assumptions.get("high_slash_rate" if name == "HIGH_SLASH_RATE" else "baseline_rate", 0.01))
|
||||||
if name == "PROVIDER_CHURN":
|
emissions = 0 if name in {"NO_SUBSIDY", "ZERO_NEW_TOKEN_EMISSIONS"} else round(max(0, tx * 0.01), 2)
|
||||||
|
if name == "PROVIDER_CHURN_50_PERCENT":
|
||||||
provider_supply = max(1, int(provider_supply * 0.45))
|
provider_supply = max(1, int(provider_supply * 0.45))
|
||||||
staking = max(100, int(staking * 0.45))
|
staking = max(100, int(staking * 0.45))
|
||||||
scenarios[name] = {"users": users, "transactions": tx, "fees": fees, "token_demand": fees + staking * 0.01, "provider_supply": provider_supply, "staking": staking, "emissions": emissions, "treasury": round(1000 + fees * 0.2 - emissions * 0.1, 2), "circulating_supply": 1_000_000 + emissions, "token_velocity": round(tx / max(1, fees + staking * 0.01), 2), "reward_coverage": round(fees / max(1, emissions), 2), "network_security_budget": staking}
|
native_removed = name == "NATIVE_TOKEN_REMOVED"
|
||||||
summary = {"token_price_appreciation_primary_success_variable": False, "subsidy_removed_survives": scenarios["SUBSIDY_REMOVED"]["reward_coverage"] >= 1.0, "stress_notes": ["Price-down and provider-churn scenarios must preserve service quality before mainnet."]}
|
stablecoin_substitute = name == "STABLECOIN_PAYMENT_SUBSTITUTE"
|
||||||
|
protocol_degrades = native_removed and bool(roles.required_roles)
|
||||||
|
scenarios[name] = {"users": users, "transactions": tx, "fees": fees, "token_demand": 0 if native_removed else fees + staking * 0.01, "provider_supply": provider_supply, "staking": 0 if native_removed else staking, "emissions": emissions, "treasury": round(1000 + fees * model.treasury_share - emissions * 0.1, 2), "circulating_supply": 0 if native_removed else 1_000_000 + emissions, "token_velocity": 0 if native_removed else round(tx / max(1, fees + staking * 0.01), 2), "reward_coverage": round(fees / max(1, emissions), 2), "network_security_budget": 0 if native_removed else staking, "slash_events": round(tx * slash_rate, 2), "stablecoin_payment_works": stablecoin_substitute or roles.roles.get("PAYMENT") != TokenRoleRequirement.REQUIRED, "protocol_degrades_without_native_token": protocol_degrades}
|
||||||
|
summary = {"token_price_appreciation_primary_success_variable": False, "subsidy_removed_survives": scenarios["NO_SUBSIDY"]["reward_coverage"] >= 1.0, "native_token_removed_degrades_protocol": scenarios["NATIVE_TOKEN_REMOVED"]["protocol_degrades_without_native_token"], "stablecoin_payment_substitute_works": scenarios["STABLECOIN_PAYMENT_SUBSTITUTE"]["stablecoin_payment_works"], "stress_notes": ["Price appreciation is not a success variable.", "If native-token-removed changes little, reduce token necessity."]}
|
||||||
return TokenomicsSimulation.objects.update_or_create(proposal=proposal, defaults={"scenarios": scenarios, "summary": summary})[0]
|
return TokenomicsSimulation.objects.update_or_create(proposal=proposal, defaults={"scenarios": scenarios, "summary": summary})[0]
|
||||||
|
|
||||||
def crypto_scenario_lab(self, cohort: VentureCohort) -> list[CryptoScenarioLab]:
|
def crypto_scenario_lab(self, cohort: VentureCohort) -> list[CryptoScenarioLab]:
|
||||||
|
|
@ -525,6 +753,9 @@ class CryptoVentureService:
|
||||||
policy = self.regulatory_policy(proposal)
|
policy = self.regulatory_policy(proposal)
|
||||||
red = getattr(proposal, "crypto_red_team", None) or self.red_team_proposal(proposal)
|
red = getattr(proposal, "crypto_red_team", None) or self.red_team_proposal(proposal)
|
||||||
sim = getattr(proposal, "tokenomics_simulation", None) or self.simulate_tokenomics(proposal)
|
sim = getattr(proposal, "tokenomics_simulation", None) or self.simulate_tokenomics(proposal)
|
||||||
|
pretoken = getattr(proposal, "pretoken_monetization", None) or self.assess_pretoken_monetization(proposal)
|
||||||
|
roles = getattr(proposal, "token_role_decomposition", None) or self.token_role_decomposition(proposal)
|
||||||
|
security = getattr(proposal, "protocol_security_assessment", None)
|
||||||
autonomy = self.assess_autonomy(proposal)
|
autonomy = self.assess_autonomy(proposal)
|
||||||
text = self._proposal_text(proposal)
|
text = self._proposal_text(proposal)
|
||||||
component_scores = {
|
component_scores = {
|
||||||
|
|
@ -538,10 +769,12 @@ class CryptoVentureService:
|
||||||
"AUTONOMOUS_OPERABILITY": autonomy.autonomous_operability_score,
|
"AUTONOMOUS_OPERABILITY": autonomy.autonomous_operability_score,
|
||||||
"SECURITY_MODEL_QUALITY": min(100, 45 + sum(8 for term in ["contract", "oracle", "key", "pause", "treasury", "slashing"] if term in text)),
|
"SECURITY_MODEL_QUALITY": min(100, 45 + sum(8 for term in ["contract", "oracle", "key", "pause", "treasury", "slashing"] if term in text)),
|
||||||
"REGULATORY_MANAGEABILITY": policy.regulatory_manageability_score,
|
"REGULATORY_MANAGEABILITY": policy.regulatory_manageability_score,
|
||||||
|
"PRE_TOKEN_MONETIZATION_POTENTIAL": pretoken.pre_token_monetization_potential,
|
||||||
}
|
}
|
||||||
score = round(sum(component_scores.values()) / len(component_scores) - len(red.flags) * 4, 1)
|
score = round(sum(component_scores.values()) / len(component_scores) - len(red.flags) * 4, 1)
|
||||||
decision = self._crypto_decision(component_scores, token.classification, red.flags)
|
decision = self._crypto_decision(component_scores, token.classification, red.flags)
|
||||||
return {"proposal_id": str(proposal.id), "company": proposal.title, "one_line_thesis": proposal.pitch.get("One-line thesis", ""), "product_thesis": proposal.protocol_thesis.product_thesis, "protocol_thesis": proposal.protocol_thesis.protocol_thesis, "token_thesis": proposal.protocol_thesis.token_thesis, "why_onchain": proposal.onchain_assessment.rationale, "why_token": token.rationale, "token_utility": token.utility_categories, "token_necessity_classification": token.classification, "token_demand_loop": proposal.token_demand_loop.loop, "value_capture": proposal.value_capture.value_accrual, "network_effect": proposal.protocol_thesis.network_effect, "bootstrap_plan": proposal.protocol_thesis.bootstrap_plan, "autonomous_operability": autonomy.autonomous_operability_score, "regulatory_manageability": policy.regulatory_manageability_score, "security_risk": component_scores["SECURITY_MODEL_QUALITY"], "validation_experiment": proposal.validation_plan, "component_scores": component_scores, "value_accrual_quality": value.value_accrual_quality_score, "tokenomics_sustainability": value.tokenomics_sustainability_score, "simulation_summary": sim.summary, "token_red_team_flags": red.flags, "crypto_ic_decision": decision, "crypto_ic_score": score, "research": proposal.metadata.get("research", {}), "legal_review_required": policy.legal_review_required}
|
autonomy_class = CryptoAutonomyClass.AUTONOMOUS_CRYPTO if autonomy.autonomous_operability_score >= 70 else CryptoAutonomyClass.ASSISTED_CRYPTO
|
||||||
|
return {"proposal_id": str(proposal.id), "company": proposal.title, "one_line_thesis": proposal.pitch.get("One-line thesis", ""), "product_thesis": proposal.protocol_thesis.product_thesis, "protocol_thesis": proposal.protocol_thesis.protocol_thesis, "user": proposal.target_customer, "pre_token_business": pretoken.pre_token_business, "pre_token_monetization": pretoken.mode, "pre_token_monetization_potential": pretoken.pre_token_monetization_potential, "pre_token_revenue_ladder": pretoken.revenue_ladder, "protocol_economy": proposal.protocol_thesis.protocol_thesis, "token_economy": proposal.protocol_thesis.token_thesis, "token_thesis": proposal.protocol_thesis.token_thesis, "why_onchain": proposal.onchain_assessment.rationale, "why_token": token.rationale, "token_role_decomposition": roles.roles, "stablecoin_counterfactual": roles.stablecoin_counterfactual, "native_token_removed_outcome": roles.native_token_removed_outcome, "native_token_removed_degrades_protocol": sim.summary.get("native_token_removed_degrades_protocol", False), "token_utility": token.utility_categories, "token_necessity_classification": token.classification, "token_demand_loop": proposal.token_demand_loop.loop, "value_capture": proposal.value_capture.value_accrual, "network_effect": proposal.protocol_thesis.network_effect, "bootstrap_plan": proposal.protocol_thesis.bootstrap_plan, "autonomous_operability": autonomy.autonomous_operability_score, "crypto_autonomy_class": autonomy_class, "regulatory_manageability": policy.regulatory_manageability_score, "security_assessment": security.decision if security else "NOT_ASSESSED", "security_risk": component_scores["SECURITY_MODEL_QUALITY"], "validation_experiment": proposal.validation_plan, "component_scores": component_scores, "value_accrual_quality": value.value_accrual_quality_score, "tokenomics_sustainability": value.tokenomics_sustainability_score, "simulation_summary": sim.summary, "token_red_team_flags": red.flags, "crypto_ic_decision": decision, "crypto_ic_score": score, "research": proposal.metadata.get("research", {}), "research_confidence": proposal.metadata.get("research", {}).get("research_status", "RESEARCH_INSUFFICIENT"), "legal_review_required": policy.legal_review_required}
|
||||||
|
|
||||||
def assess_autonomy(self, proposal: CompanyProposal) -> AutonomousOperabilityAssessment:
|
def assess_autonomy(self, proposal: CompanyProposal) -> AutonomousOperabilityAssessment:
|
||||||
assessment = self.venture.assess_autonomous_operability(proposal)
|
assessment = self.venture.assess_autonomous_operability(proposal)
|
||||||
|
|
@ -590,7 +823,7 @@ class CryptoVentureService:
|
||||||
|
|
||||||
def _qualifies_finalist(self, row: dict[str, Any]) -> bool:
|
def _qualifies_finalist(self, row: dict[str, Any]) -> bool:
|
||||||
scores = row["component_scores"]
|
scores = row["component_scores"]
|
||||||
return row["token_necessity_classification"] in {TokenNecessityClassification.TOKEN_ESSENTIAL, TokenNecessityClassification.TOKEN_STRONGLY_JUSTIFIED} and scores["TOKEN_NECESSITY"] >= 75 and scores["REAL_USAGE_DEMAND"] >= 65 and scores["ONCHAIN_NECESSITY"] >= 70 and scores["VALUE_ACCRUAL_QUALITY"] >= 65 and scores["TOKENOMICS_SUSTAINABILITY"] >= 65 and scores["AUTONOMOUS_OPERABILITY"] >= 70
|
return row["token_necessity_classification"] in {TokenNecessityClassification.TOKEN_ESSENTIAL, TokenNecessityClassification.TOKEN_STRONGLY_JUSTIFIED} and scores["TOKEN_NECESSITY"] >= 75 and scores["REAL_USAGE_DEMAND"] >= 65 and scores["ONCHAIN_NECESSITY"] >= 70 and scores["VALUE_ACCRUAL_QUALITY"] >= 65 and scores["TOKENOMICS_SUSTAINABILITY"] >= 65 and scores["AUTONOMOUS_OPERABILITY"] >= 70 and row.get("security_assessment") != ProtocolSecurityDecision.BLOCK_TESTNET and row.get("native_token_removed_degrades_protocol") is True
|
||||||
|
|
||||||
def _crypto_concentration(self, rows: list[dict[str, Any]]) -> dict[str, Any]:
|
def _crypto_concentration(self, rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||||
categories = Counter(row["company"].split()[0] for row in rows)
|
categories = Counter(row["company"].split()[0] for row in rows)
|
||||||
|
|
@ -661,7 +894,7 @@ class CryptoVentureService:
|
||||||
return VentureArtifact.objects.create(proposal=proposal, mandate=mandate, graph_run=graph_run, artifact_type=artifact_type, name=name, content=content, readable=readable, generated_by=generated_by)
|
return VentureArtifact.objects.create(proposal=proposal, mandate=mandate, graph_run=graph_run, artifact_type=artifact_type, name=name, content=content, readable=readable, generated_by=generated_by)
|
||||||
|
|
||||||
def _readable_report(self, content: dict[str, Any]) -> str:
|
def _readable_report(self, content: dict[str, Any]) -> str:
|
||||||
lines = ["# CRYPTO VENTURE COHORT V0.1 REPORT", "", f"Cohort ID: {content['cohort_id']}", f"Accepted protocols: {content['accepted_protocols']}", "", "## Ranking"]
|
lines = ["# CRYPTO VENTURE COHORT V0.2", "", f"Cohort ID: {content['cohort_id']}", f"Crypto survivors: {content['crypto_survivors']}", "", "## Ranking"]
|
||||||
for row in content["ranking"]:
|
for row in content["ranking"]:
|
||||||
lines.append(f"- Rank {row.get('rank')}: {row['company']} | {row['crypto_ic_decision']} | score {row['crypto_ic_score']} | token {row['token_necessity_classification']}")
|
lines.append(f"- Rank {row.get('rank')}: {row['company']} | {row['crypto_ic_decision']} | score {row['crypto_ic_score']} | token {row['token_necessity_classification']}")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
@ -671,5 +904,5 @@ class CryptoVentureService:
|
||||||
else:
|
else:
|
||||||
lines.append("Fewer than 3 qualified; weak token ideas were not promoted.")
|
lines.append("Fewer than 3 qualified; weak token ideas were not promoted.")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
lines.append("Stop condition: no token sale, fundraising, mainnet issuance, user contact, or real spend.")
|
lines.append("Stop condition: no token/NFT sale, fundraising, mainnet issuance, user contact, or real spend.")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
|
||||||
|
|
@ -59,11 +59,13 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
def markdown(self, payload: dict[str, Any]) -> str:
|
def markdown(self, payload: dict[str, Any]) -> str:
|
||||||
lines = [
|
lines = [
|
||||||
"# CRYPTO VENTURE COHORT V0.1 REPORT",
|
f"# {payload.get('title', 'CRYPTO VENTURE COHORT REPORT')}",
|
||||||
"",
|
"",
|
||||||
f"Cohort ID: `{payload['cohort_id']}`",
|
f"Cohort ID: `{payload['cohort_id']}`",
|
||||||
f"GraphRun: `{payload['graph_run']}`",
|
f"GraphRun: `{payload['graph_run']}`",
|
||||||
f"Accepted protocols: `{payload['accepted_protocols']}`",
|
f"Raw protocols generated: `{payload.get('raw_protocols_generated', payload.get('accepted_protocols', 0))}`",
|
||||||
|
f"Token unnecessary: `{payload.get('token_unnecessary', 0)}`. Token optional/routed SaaS: `{payload.get('token_optional_routed_saas', 0)}`. Duplicates: `{payload.get('duplicates', 0)}`.",
|
||||||
|
f"Crypto survivors: `{payload.get('crypto_survivors', 0)}`. Autonomous crypto survivors: `{payload.get('autonomous_crypto_survivors', 0)}`. Security-blocked: `{payload.get('security_blocked', 0)}`. Finalists: `{payload.get('finalists', len(payload.get('top_3', [])))}`.",
|
||||||
f"Generation attempts: `{payload['generation_attempts']}`",
|
f"Generation attempts: `{payload['generation_attempts']}`",
|
||||||
"",
|
"",
|
||||||
"## Runtime",
|
"## Runtime",
|
||||||
|
|
@ -88,6 +90,16 @@ class Command(BaseCommand):
|
||||||
"",
|
"",
|
||||||
f"Token thesis: {row['token_thesis']}",
|
f"Token thesis: {row['token_thesis']}",
|
||||||
"",
|
"",
|
||||||
|
f"Pre-token business: {row.get('pre_token_business', '')}",
|
||||||
|
"",
|
||||||
|
f"Pre-token monetization: `{row.get('pre_token_monetization', '')}`. Potential: `{row.get('pre_token_monetization_potential', '')}`. Revenue ladder: {json.dumps(row.get('pre_token_revenue_ladder', {}), default=str)}",
|
||||||
|
"",
|
||||||
|
f"Token role decomposition: {json.dumps(row.get('token_role_decomposition', {}), default=str)}",
|
||||||
|
"",
|
||||||
|
f"Stablecoin counterfactual: {json.dumps(row.get('stablecoin_counterfactual', {}), default=str)}",
|
||||||
|
"",
|
||||||
|
f"Native token removed outcome: {row.get('native_token_removed_outcome', '')}",
|
||||||
|
"",
|
||||||
f"Token demand loop: {self.inline(row['token_demand_loop'])}",
|
f"Token demand loop: {self.inline(row['token_demand_loop'])}",
|
||||||
"",
|
"",
|
||||||
f"Value capture: {row['value_capture']}",
|
f"Value capture: {row['value_capture']}",
|
||||||
|
|
@ -96,7 +108,7 @@ class Command(BaseCommand):
|
||||||
"",
|
"",
|
||||||
f"Bootstrap plan: {row['bootstrap_plan']}",
|
f"Bootstrap plan: {row['bootstrap_plan']}",
|
||||||
"",
|
"",
|
||||||
f"Autonomous operability: `{row['autonomous_operability']}`. Regulatory manageability: `{row['regulatory_manageability']}`. Security risk score: `{row['security_risk']}`.",
|
f"Autonomous operability: `{row['autonomous_operability']}`. Autonomy class: `{row.get('crypto_autonomy_class', '')}`. Regulatory manageability: `{row['regulatory_manageability']}`. Security: `{row.get('security_assessment', '')}`. Security risk score: `{row['security_risk']}`.",
|
||||||
"",
|
"",
|
||||||
f"Scores: {json.dumps(row['component_scores'], default=str)}",
|
f"Scores: {json.dumps(row['component_scores'], default=str)}",
|
||||||
"",
|
"",
|
||||||
|
|
@ -112,7 +124,7 @@ class Command(BaseCommand):
|
||||||
lines.extend(f"- {row['company']}: `{row['crypto_ic_decision']}`" for row in top_3)
|
lines.extend(f"- {row['company']}: `{row['crypto_ic_decision']}`" for row in top_3)
|
||||||
else:
|
else:
|
||||||
lines.append("Fewer than 3 qualified; weak token ideas were not promoted.")
|
lines.append("Fewer than 3 qualified; weak token ideas were not promoted.")
|
||||||
lines.extend(["", "## Token Red Team Failures", "", json.dumps(payload.get("token_red_team_failures", {}), indent=2, default=str), "", "## Token Utility Distribution", "", json.dumps(payload.get("token_utility_distribution", {}), indent=2, default=str), "", "## Capability Gaps", "", json.dumps(payload.get("capability_gaps", []), indent=2, default=str), "", "Stop condition: no token sale, no fundraising, no mainnet issuance, no investor/user contact, no liquidity pool, no market making, no real spend."])
|
lines.extend(["", "## Token Red Team Failures", "", json.dumps(payload.get("token_red_team_failures", {}), indent=2, default=str), "", "## Research Health", "", json.dumps(payload.get("research_health", {}), indent=2, default=str), "", "## Token Utility Distribution", "", json.dumps(payload.get("token_utility_distribution", {}), indent=2, default=str), "", "## Capability Gaps", "", json.dumps(payload.get("capability_gaps", []), indent=2, default=str), "", "Stop condition: no token sale, no NFT sale, no Founding Membership sale, no fundraising, no mainnet issuance, no investor/user contact, no liquidity pool, no market making, no real spend."])
|
||||||
return "\n".join(lines).rstrip()
|
return "\n".join(lines).rstrip()
|
||||||
|
|
||||||
def inline(self, value: Any) -> str:
|
def inline(self, value: Any) -> str:
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class Command(BaseCommand):
|
||||||
help = "Run a Crypto / Protocol Venture Cohort V0.1."
|
help = "Run a Crypto / Protocol Venture Cohort V0.1."
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument("--size", type=int, default=10)
|
parser.add_argument("--size", type=int, default=30)
|
||||||
parser.add_argument("--concurrency", type=int, default=2)
|
parser.add_argument("--concurrency", type=int, default=2)
|
||||||
parser.add_argument("--qwen-only", action="store_true", help="Use Qwen for all crypto cohort model roles.")
|
parser.add_argument("--qwen-only", action="store_true", help="Use Qwen for all crypto cohort model roles.")
|
||||||
parser.add_argument("--sol-final-ic", action="store_true", help="Use Sol for final IC role when configured.")
|
parser.add_argument("--sol-final-ic", action="store_true", help="Use Sol for final IC role when configured.")
|
||||||
|
|
@ -81,6 +81,13 @@ class Command(BaseCommand):
|
||||||
"members": len(members),
|
"members": len(members),
|
||||||
"concurrency": cohort.concurrency,
|
"concurrency": cohort.concurrency,
|
||||||
"metrics": cohort.metrics,
|
"metrics": cohort.metrics,
|
||||||
|
"raw_protocols_generated": cohort.metrics.get("raw_generated", 0),
|
||||||
|
"token_unnecessary": cohort.metrics.get("token_unnecessary_rejections", 0),
|
||||||
|
"token_optional_routed_saas": cohort.metrics.get("token_optional_route_to_saas", 0),
|
||||||
|
"crypto_survivors": cohort.members.count(),
|
||||||
|
"autonomous_crypto_survivors": cohort.metrics.get("autonomous_crypto_survivors", 0),
|
||||||
|
"security_blocked": cohort.metrics.get("protocol_security_gate_blocked", 0),
|
||||||
|
"finalists": cohort.metrics.get("finalists", 0),
|
||||||
"generation_sources": sorted({str(member.proposal.metadata.get("generation_source", "unknown")) for member in members}),
|
"generation_sources": sorted({str(member.proposal.metadata.get("generation_source", "unknown")) for member in members}),
|
||||||
"top_3": review.top_3 if review else [],
|
"top_3": review.top_3 if review else [],
|
||||||
"rejected_token_not_needed": [row for row in (review.rankings if review else []) if row.get("crypto_ic_decision") == "REJECT_TOKEN_NOT_NEEDED"],
|
"rejected_token_not_needed": [row for row in (review.rankings if review else []) if row.get("crypto_ic_decision") == "REJECT_TOKEN_NOT_NEEDED"],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
# Generated by Django 5.2.16 on 2026-08-16 12:19
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ventures', '0005_alter_autonomousoperabilityassessment_venture_track_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PreTokenMonetizationAssessment',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('mode', models.CharField(choices=[('NONE', 'None'), ('PAID_BETA_ACCESS', 'Paid Beta Access'), ('SERVICE_CREDITS', 'Service Credits'), ('STABLECOIN_USAGE_FEES', 'Stablecoin Usage Fees'), ('SUBSCRIPTION', 'Subscription'), ('FOUNDING_MEMBERSHIP', 'Founding Membership'), ('FOUNDING_MEMBERSHIP_NFT', 'Founding Membership Nft'), ('OTHER', 'Other')], default='NONE', max_length=40)),
|
||||||
|
('pre_token_monetization_potential', models.FloatField(default=0.0)),
|
||||||
|
('pre_token_business', models.TextField(blank=True)),
|
||||||
|
('immediate_utility', models.JSONField(blank=True, default=list)),
|
||||||
|
('revenue_ladder', models.JSONField(blank=True, default=dict)),
|
||||||
|
('founding_membership_policy', models.JSONField(blank=True, default=dict)),
|
||||||
|
('autonomous_fulfillment_notes', models.TextField(blank=True)),
|
||||||
|
('metadata', models.JSONField(blank=True, default=dict)),
|
||||||
|
('proposal', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='pretoken_monetization', to='ventures.companyproposal')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ProtocolSecurityAssessment',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('decision', models.CharField(choices=[('PASS_FOR_TESTNET_DESIGN', 'Pass For Testnet Design'), ('REVISE_SECURITY_MODEL', 'Revise Security Model'), ('BLOCK_TESTNET', 'Block Testnet')], max_length=40)),
|
||||||
|
('threat_model', models.JSONField(blank=True, default=dict)),
|
||||||
|
('missing_controls', models.JSONField(blank=True, default=list)),
|
||||||
|
('privileged_roles', models.JSONField(blank=True, default=list)),
|
||||||
|
('economic_attack_surfaces', models.JSONField(blank=True, default=list)),
|
||||||
|
('key_management_assumptions', models.JSONField(blank=True, default=list)),
|
||||||
|
('rationale', models.TextField(blank=True)),
|
||||||
|
('metadata', models.JSONField(blank=True, default=dict)),
|
||||||
|
('proposal', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='protocol_security_assessment', to='ventures.companyproposal')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TokenEconomicModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('unit_of_service', models.CharField(blank=True, max_length=160)),
|
||||||
|
('expected_usage_frequency', models.CharField(blank=True, max_length=160)),
|
||||||
|
('transaction_fee_model', models.TextField(blank=True)),
|
||||||
|
('average_fee', models.FloatField(default=0.0)),
|
||||||
|
('payment_asset', models.CharField(blank=True, max_length=80)),
|
||||||
|
('stake_requirement', models.FloatField(default=0.0)),
|
||||||
|
('collateral_requirement', models.FloatField(default=0.0)),
|
||||||
|
('provider_count', models.PositiveIntegerField(default=0)),
|
||||||
|
('demand_side_users', models.PositiveIntegerField(default=0)),
|
||||||
|
('slash_event_assumptions', models.JSONField(blank=True, default=dict)),
|
||||||
|
('slash_amount', models.FloatField(default=0.0)),
|
||||||
|
('emission_schedule', models.JSONField(blank=True, default=dict)),
|
||||||
|
('bootstrap_subsidies', models.FloatField(default=0.0)),
|
||||||
|
('treasury_share', models.FloatField(default=0.0)),
|
||||||
|
('provider_reward_share', models.FloatField(default=0.0)),
|
||||||
|
('token_sinks', models.JSONField(blank=True, default=list)),
|
||||||
|
('unlock_assumptions', models.JSONField(blank=True, default=dict)),
|
||||||
|
('circulation_assumptions', models.JSONField(blank=True, default=dict)),
|
||||||
|
('metadata', models.JSONField(blank=True, default=dict)),
|
||||||
|
('proposal', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='token_economic_model', to='ventures.companyproposal')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TokenLaunchReadinessAssessment',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('real_users', models.BooleanField(default=False)),
|
||||||
|
('repeat_usage', models.BooleanField(default=False)),
|
||||||
|
('real_protocol_fees', models.BooleanField(default=False)),
|
||||||
|
('token_necessity_validated', models.BooleanField(default=False)),
|
||||||
|
('token_demand_loop_validated', models.BooleanField(default=False)),
|
||||||
|
('security_audit_status', models.CharField(blank=True, max_length=120)),
|
||||||
|
('testnet_stability', models.CharField(blank=True, max_length=120)),
|
||||||
|
('tokenomics_stress_tests', models.JSONField(blank=True, default=dict)),
|
||||||
|
('legal_review_status', models.CharField(blank=True, max_length=120)),
|
||||||
|
('jurisdiction_policy_status', models.CharField(blank=True, max_length=120)),
|
||||||
|
('admin_key_controls', models.CharField(blank=True, max_length=120)),
|
||||||
|
('treasury_controls', models.CharField(blank=True, max_length=120)),
|
||||||
|
('decentralization_readiness', models.CharField(blank=True, max_length=120)),
|
||||||
|
('launch_readiness_status', models.CharField(choices=[('NOT_READY', 'Not Ready'), ('PRODUCT_TRACTION_REQUIRED', 'Product Traction Required'), ('TOKEN_MODEL_REVISION_REQUIRED', 'Token Model Revision Required'), ('SECURITY_REVIEW_REQUIRED', 'Security Review Required'), ('LEGAL_REVIEW_REQUIRED', 'Legal Review Required'), ('READY_FOR_HUMAN_LAUNCH_REVIEW', 'Ready For Human Launch Review')], default='NOT_READY', max_length=60)),
|
||||||
|
('metadata', models.JSONField(blank=True, default=dict)),
|
||||||
|
('proposal', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='token_launch_readiness', to='ventures.companyproposal')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TokenRoleDecomposition',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('roles', models.JSONField(blank=True, default=dict)),
|
||||||
|
('required_roles', models.JSONField(blank=True, default=list)),
|
||||||
|
('stablecoin_counterfactual', models.JSONField(blank=True, default=dict)),
|
||||||
|
('native_token_removed_outcome', models.TextField(blank=True)),
|
||||||
|
('metadata', models.JSONField(blank=True, default=dict)),
|
||||||
|
('proposal', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='token_role_decomposition', to='ventures.companyproposal')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -131,6 +131,59 @@ class TokenRedTeamFlag(models.TextChoices):
|
||||||
REGULATORY_RISK_HIGH = "REGULATORY_RISK_HIGH"
|
REGULATORY_RISK_HIGH = "REGULATORY_RISK_HIGH"
|
||||||
ONCHAIN_NOT_REQUIRED = "ONCHAIN_NOT_REQUIRED"
|
ONCHAIN_NOT_REQUIRED = "ONCHAIN_NOT_REQUIRED"
|
||||||
NO_REAL_USER_DEMAND = "NO_REAL_USER_DEMAND"
|
NO_REAL_USER_DEMAND = "NO_REAL_USER_DEMAND"
|
||||||
|
TOKEN_DOES_TOO_MUCH = "TOKEN_DOES_TOO_MUCH"
|
||||||
|
NATIVE_PAYMENT_NOT_REQUIRED = "NATIVE_PAYMENT_NOT_REQUIRED"
|
||||||
|
STABLECOIN_SUBSTITUTE_WORKS = "STABLECOIN_SUBSTITUTE_WORKS"
|
||||||
|
COLLATERAL_CAN_BE_EXTERNAL = "COLLATERAL_CAN_BE_EXTERNAL"
|
||||||
|
REVENUE_CLAIM_LANGUAGE = "REVENUE_CLAIM_LANGUAGE"
|
||||||
|
BUYBACK_DEPENDENCY = "BUYBACK_DEPENDENCY"
|
||||||
|
YIELD_DEPENDENCY = "YIELD_DEPENDENCY"
|
||||||
|
SPECULATIVE_BOOTSTRAP = "SPECULATIVE_BOOTSTRAP"
|
||||||
|
EMISSIONS_DEPENDENCY = "EMISSIONS_DEPENDENCY"
|
||||||
|
SECURITY_TOKEN_CONCENTRATION = "SECURITY_TOKEN_CONCENTRATION"
|
||||||
|
SLASHING_NOT_OBJECTIVELY_MEASURABLE = "SLASHING_NOT_OBJECTIVELY_MEASURABLE"
|
||||||
|
ORACLE_CIRCULARITY = "ORACLE_CIRCULARITY"
|
||||||
|
HUMAN_DISPUTE_DEPENDENCY = "HUMAN_DISPUTE_DEPENDENCY"
|
||||||
|
CENTRAL_OPERATOR_CONTRADICTION = "CENTRAL_OPERATOR_CONTRADICTION"
|
||||||
|
NETWORK_NOT_READY_FOR_DECENTRALIZATION = "NETWORK_NOT_READY_FOR_DECENTRALIZATION"
|
||||||
|
|
||||||
|
|
||||||
|
class PreTokenMonetizationMode(models.TextChoices):
|
||||||
|
NONE = "NONE"
|
||||||
|
PAID_BETA_ACCESS = "PAID_BETA_ACCESS"
|
||||||
|
SERVICE_CREDITS = "SERVICE_CREDITS"
|
||||||
|
STABLECOIN_USAGE_FEES = "STABLECOIN_USAGE_FEES"
|
||||||
|
SUBSCRIPTION = "SUBSCRIPTION"
|
||||||
|
FOUNDING_MEMBERSHIP = "FOUNDING_MEMBERSHIP"
|
||||||
|
FOUNDING_MEMBERSHIP_NFT = "FOUNDING_MEMBERSHIP_NFT"
|
||||||
|
OTHER = "OTHER"
|
||||||
|
|
||||||
|
|
||||||
|
class TokenRoleRequirement(models.TextChoices):
|
||||||
|
REQUIRED = "REQUIRED"
|
||||||
|
USEFUL = "USEFUL"
|
||||||
|
OPTIONAL = "OPTIONAL"
|
||||||
|
UNNECESSARY = "UNNECESSARY"
|
||||||
|
|
||||||
|
|
||||||
|
class TokenLaunchReadinessStatus(models.TextChoices):
|
||||||
|
NOT_READY = "NOT_READY"
|
||||||
|
PRODUCT_TRACTION_REQUIRED = "PRODUCT_TRACTION_REQUIRED"
|
||||||
|
TOKEN_MODEL_REVISION_REQUIRED = "TOKEN_MODEL_REVISION_REQUIRED"
|
||||||
|
SECURITY_REVIEW_REQUIRED = "SECURITY_REVIEW_REQUIRED"
|
||||||
|
LEGAL_REVIEW_REQUIRED = "LEGAL_REVIEW_REQUIRED"
|
||||||
|
READY_FOR_HUMAN_LAUNCH_REVIEW = "READY_FOR_HUMAN_LAUNCH_REVIEW"
|
||||||
|
|
||||||
|
|
||||||
|
class ProtocolSecurityDecision(models.TextChoices):
|
||||||
|
PASS_FOR_TESTNET_DESIGN = "PASS_FOR_TESTNET_DESIGN"
|
||||||
|
REVISE_SECURITY_MODEL = "REVISE_SECURITY_MODEL"
|
||||||
|
BLOCK_TESTNET = "BLOCK_TESTNET"
|
||||||
|
|
||||||
|
|
||||||
|
class CryptoAutonomyClass(models.TextChoices):
|
||||||
|
AUTONOMOUS_CRYPTO = "AUTONOMOUS_CRYPTO"
|
||||||
|
ASSISTED_CRYPTO = "ASSISTED_CRYPTO"
|
||||||
|
|
||||||
|
|
||||||
class FounderDependencyLevel(models.TextChoices):
|
class FounderDependencyLevel(models.TextChoices):
|
||||||
|
|
@ -389,6 +442,81 @@ class CryptoScenarioLab(TimestampedModel):
|
||||||
metadata = models.JSONField(default=dict, blank=True)
|
metadata = models.JSONField(default=dict, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
class PreTokenMonetizationAssessment(TimestampedModel):
|
||||||
|
proposal = models.OneToOneField(CompanyProposal, on_delete=models.CASCADE, related_name="pretoken_monetization")
|
||||||
|
mode = models.CharField(max_length=40, choices=PreTokenMonetizationMode.choices, default=PreTokenMonetizationMode.NONE)
|
||||||
|
pre_token_monetization_potential = models.FloatField(default=0.0)
|
||||||
|
pre_token_business = models.TextField(blank=True)
|
||||||
|
immediate_utility = models.JSONField(default=list, blank=True)
|
||||||
|
revenue_ladder = models.JSONField(default=dict, blank=True)
|
||||||
|
founding_membership_policy = models.JSONField(default=dict, blank=True)
|
||||||
|
autonomous_fulfillment_notes = models.TextField(blank=True)
|
||||||
|
metadata = models.JSONField(default=dict, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TokenRoleDecomposition(TimestampedModel):
|
||||||
|
proposal = models.OneToOneField(CompanyProposal, on_delete=models.CASCADE, related_name="token_role_decomposition")
|
||||||
|
roles = models.JSONField(default=dict, blank=True)
|
||||||
|
required_roles = models.JSONField(default=list, blank=True)
|
||||||
|
stablecoin_counterfactual = models.JSONField(default=dict, blank=True)
|
||||||
|
native_token_removed_outcome = models.TextField(blank=True)
|
||||||
|
metadata = models.JSONField(default=dict, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TokenEconomicModel(TimestampedModel):
|
||||||
|
proposal = models.OneToOneField(CompanyProposal, on_delete=models.CASCADE, related_name="token_economic_model")
|
||||||
|
unit_of_service = models.CharField(max_length=160, blank=True)
|
||||||
|
expected_usage_frequency = models.CharField(max_length=160, blank=True)
|
||||||
|
transaction_fee_model = models.TextField(blank=True)
|
||||||
|
average_fee = models.FloatField(default=0.0)
|
||||||
|
payment_asset = models.CharField(max_length=80, blank=True)
|
||||||
|
stake_requirement = models.FloatField(default=0.0)
|
||||||
|
collateral_requirement = models.FloatField(default=0.0)
|
||||||
|
provider_count = models.PositiveIntegerField(default=0)
|
||||||
|
demand_side_users = models.PositiveIntegerField(default=0)
|
||||||
|
slash_event_assumptions = models.JSONField(default=dict, blank=True)
|
||||||
|
slash_amount = models.FloatField(default=0.0)
|
||||||
|
emission_schedule = models.JSONField(default=dict, blank=True)
|
||||||
|
bootstrap_subsidies = models.FloatField(default=0.0)
|
||||||
|
treasury_share = models.FloatField(default=0.0)
|
||||||
|
provider_reward_share = models.FloatField(default=0.0)
|
||||||
|
token_sinks = models.JSONField(default=list, blank=True)
|
||||||
|
unlock_assumptions = models.JSONField(default=dict, blank=True)
|
||||||
|
circulation_assumptions = models.JSONField(default=dict, blank=True)
|
||||||
|
metadata = models.JSONField(default=dict, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TokenLaunchReadinessAssessment(TimestampedModel):
|
||||||
|
proposal = models.OneToOneField(CompanyProposal, on_delete=models.CASCADE, related_name="token_launch_readiness")
|
||||||
|
real_users = models.BooleanField(default=False)
|
||||||
|
repeat_usage = models.BooleanField(default=False)
|
||||||
|
real_protocol_fees = models.BooleanField(default=False)
|
||||||
|
token_necessity_validated = models.BooleanField(default=False)
|
||||||
|
token_demand_loop_validated = models.BooleanField(default=False)
|
||||||
|
security_audit_status = models.CharField(max_length=120, blank=True)
|
||||||
|
testnet_stability = models.CharField(max_length=120, blank=True)
|
||||||
|
tokenomics_stress_tests = models.JSONField(default=dict, blank=True)
|
||||||
|
legal_review_status = models.CharField(max_length=120, blank=True)
|
||||||
|
jurisdiction_policy_status = models.CharField(max_length=120, blank=True)
|
||||||
|
admin_key_controls = models.CharField(max_length=120, blank=True)
|
||||||
|
treasury_controls = models.CharField(max_length=120, blank=True)
|
||||||
|
decentralization_readiness = models.CharField(max_length=120, blank=True)
|
||||||
|
launch_readiness_status = models.CharField(max_length=60, choices=TokenLaunchReadinessStatus.choices, default=TokenLaunchReadinessStatus.NOT_READY)
|
||||||
|
metadata = models.JSONField(default=dict, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ProtocolSecurityAssessment(TimestampedModel):
|
||||||
|
proposal = models.OneToOneField(CompanyProposal, on_delete=models.CASCADE, related_name="protocol_security_assessment")
|
||||||
|
decision = models.CharField(max_length=40, choices=ProtocolSecurityDecision.choices)
|
||||||
|
threat_model = models.JSONField(default=dict, blank=True)
|
||||||
|
missing_controls = models.JSONField(default=list, blank=True)
|
||||||
|
privileged_roles = models.JSONField(default=list, blank=True)
|
||||||
|
economic_attack_surfaces = models.JSONField(default=list, blank=True)
|
||||||
|
key_management_assumptions = models.JSONField(default=list, blank=True)
|
||||||
|
rationale = models.TextField(blank=True)
|
||||||
|
metadata = models.JSONField(default=dict, blank=True)
|
||||||
|
|
||||||
|
|
||||||
class CompanyBoardReview(TimestampedModel):
|
class CompanyBoardReview(TimestampedModel):
|
||||||
proposal = models.ForeignKey(CompanyProposal, on_delete=models.CASCADE, related_name="board_reviews")
|
proposal = models.ForeignKey(CompanyProposal, on_delete=models.CASCADE, related_name="board_reviews")
|
||||||
observations = models.JSONField(default=dict, blank=True)
|
observations = models.JSONField(default=dict, blank=True)
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ def crypto_venture_cohort_graph_v1() -> ExecutionGraphSpec:
|
||||||
"produce_crypto_cohort_report",
|
"produce_crypto_cohort_report",
|
||||||
"complete",
|
"complete",
|
||||||
]
|
]
|
||||||
spec = ExecutionGraphSpec(name="crypto_venture_cohort", version=1, graph_type="CRYPTO_VENTURE_COHORT", entry="prepare_crypto_mandate", nodes={node: GraphNodeSpec(node, node if node == "complete" else f"crypto_venture_{node}") for node in nodes}, edges=[GraphEdgeSpec(nodes[index], nodes[index + 1], "success") for index in range(len(nodes) - 1)], terminal_nodes=["complete"], metadata={"description": "Crypto / Protocol Venture Cohort V0.1: token necessity, onchain necessity, token red team, simulation, regulatory gate, no issuance."})
|
spec = ExecutionGraphSpec(name="crypto_venture_cohort", version=2, graph_type="CRYPTO_VENTURE_COHORT", entry="prepare_crypto_mandate", nodes={node: GraphNodeSpec(node, node if node == "complete" else f"crypto_venture_{node}") for node in nodes}, edges=[GraphEdgeSpec(nodes[index], nodes[index + 1], "success") for index in range(len(nodes) - 1)], terminal_nodes=["complete"], metadata={"description": "Crypto / Protocol Venture Cohort V0.2: 30-raw strict funnel, pre-token monetization, hardened research, token economic model, Guard-style security design review."})
|
||||||
spec.validate()
|
spec.validate()
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,9 @@ def test_crypto_track_and_token_gate_remove_weak_then_regenerate() -> None:
|
||||||
weak.refresh_from_db()
|
weak.refresh_from_db()
|
||||||
|
|
||||||
assert weak.status == CompanyProposalStatus.REJECTED
|
assert weak.status == CompanyProposalStatus.REJECTED
|
||||||
assert weak.metadata["routed_to_saas"] is True
|
assert weak.metadata["routed_to_saas"] is False
|
||||||
assert cohort.members.count() == 2
|
assert cohort.members.count() == 0
|
||||||
assert all(member.proposal.token_utility_assessment.classification in {TokenNecessityClassification.TOKEN_ESSENTIAL, TokenNecessityClassification.TOKEN_STRONGLY_JUSTIFIED} for member in cohort.members.select_related("proposal"))
|
assert cohort.metrics["regeneration_policy"].startswith("V0.2")
|
||||||
|
|
||||||
|
|
||||||
def test_sol_counterfactual_red_team_and_final_ic_are_used() -> None:
|
def test_sol_counterfactual_red_team_and_final_ic_are_used() -> None:
|
||||||
|
|
@ -93,8 +93,28 @@ def test_tokenomics_simulation_is_proposal_specific() -> None:
|
||||||
low_sim = svc.simulate_tokenomics(low)
|
low_sim = svc.simulate_tokenomics(low)
|
||||||
high_sim = svc.simulate_tokenomics(high)
|
high_sim = svc.simulate_tokenomics(high)
|
||||||
|
|
||||||
assert low_sim.scenarios["EXPECTED"]["users"] != high_sim.scenarios["EXPECTED"]["users"]
|
assert low_sim.scenarios["EXPECTED_USAGE"]["users"] != high_sim.scenarios["EXPECTED_USAGE"]["users"]
|
||||||
assert low_sim.scenarios["EXPECTED"]["token_demand"] != high_sim.scenarios["EXPECTED"]["token_demand"]
|
assert low_sim.scenarios["EXPECTED_USAGE"]["token_demand"] != high_sim.scenarios["EXPECTED_USAGE"]["token_demand"]
|
||||||
|
assert "NATIVE_TOKEN_REMOVED" in low_sim.scenarios
|
||||||
|
|
||||||
|
|
||||||
|
def test_v02_pretoken_roles_security_and_launch_readiness() -> None:
|
||||||
|
svc = service()
|
||||||
|
cohort = svc.prepare_cohort(size=1, concurrency=1)
|
||||||
|
proposal = svc._create_proposal(cohort.mandate, svc._fallback_payload(0, "DECENTRALIZED_AI_COMPUTE"), "test", "DECENTRALIZED_AI_COMPUTE")
|
||||||
|
VentureCohortMember.objects.create(cohort=cohort, proposal=proposal)
|
||||||
|
|
||||||
|
pretoken = svc.assess_pretoken_monetization(proposal)
|
||||||
|
roles = svc.token_role_decomposition(proposal)
|
||||||
|
model = svc.token_economic_model(proposal)
|
||||||
|
svc.protocol_security_gate(cohort)
|
||||||
|
readiness = svc.token_launch_readiness(proposal)
|
||||||
|
|
||||||
|
assert pretoken.pre_token_monetization_potential > 0
|
||||||
|
assert roles.stablecoin_counterfactual["MODEL_B_stablecoin_payment_native_bond"]
|
||||||
|
assert model.average_fee > 0
|
||||||
|
assert proposal.protocol_security_assessment.decision in {"PASS_FOR_TESTNET_DESIGN", "REVISE_SECURITY_MODEL", "BLOCK_TESTNET"}
|
||||||
|
assert readiness.launch_readiness_status != "READY_FOR_HUMAN_LAUNCH_REVIEW"
|
||||||
|
|
||||||
|
|
||||||
def test_crypto_cohort_graph_real_gates_and_report() -> None:
|
def test_crypto_cohort_graph_real_gates_and_report() -> None:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue