|
| 1 | +"""Dataset card renderer. |
| 2 | +
|
| 3 | +Produces the ``dataset_card.md`` artifact from a :class:`WorldSpec`. |
| 4 | +The card follows the structure required by the architecture spec (§14.3). |
| 5 | +""" |
| 6 | + |
| 7 | +from __future__ import annotations |
| 8 | + |
| 9 | +from typing import TYPE_CHECKING |
| 10 | + |
| 11 | +if TYPE_CHECKING: |
| 12 | + from leadforge.core.models import WorldSpec |
| 13 | + |
| 14 | + |
| 15 | +def render_dataset_card(world_spec: WorldSpec) -> str: |
| 16 | + """Return a Markdown dataset card string for *world_spec*. |
| 17 | +
|
| 18 | + Sections present at all milestones: |
| 19 | + - Header (recipe id, version, seed, exposure mode) |
| 20 | + - Narrative summary (company, product, market, GTM) |
| 21 | + - Primary task and label definition |
| 22 | + - Suggested use cases |
| 23 | + - Caveats |
| 24 | +
|
| 25 | + Sections populated in later milestones (rendered as stubs here): |
| 26 | + - Table inventory |
| 27 | + - Feature categories |
| 28 | + """ |
| 29 | + cfg = world_spec.config |
| 30 | + narrative = world_spec.narrative |
| 31 | + |
| 32 | + lines: list[str] = [] |
| 33 | + |
| 34 | + # ------------------------------------------------------------------ |
| 35 | + # Header |
| 36 | + # ------------------------------------------------------------------ |
| 37 | + lines += [ |
| 38 | + "# leadforge dataset card", |
| 39 | + "", |
| 40 | + "| Field | Value |", |
| 41 | + "|---|---|", |
| 42 | + f"| Recipe | `{cfg.recipe_id}` |", |
| 43 | + f"| Package version | `{cfg.package_version}` |", |
| 44 | + f"| Seed | `{cfg.seed}` |", |
| 45 | + f"| Exposure mode | `{cfg.exposure_mode}` |", |
| 46 | + f"| Difficulty | `{cfg.difficulty}` |", |
| 47 | + f"| Horizon | {cfg.horizon_days} days |", |
| 48 | + "", |
| 49 | + ] |
| 50 | + |
| 51 | + # ------------------------------------------------------------------ |
| 52 | + # Narrative summary |
| 53 | + # ------------------------------------------------------------------ |
| 54 | + lines.append("## Narrative summary") |
| 55 | + lines.append("") |
| 56 | + if narrative is not None: |
| 57 | + c = narrative.company |
| 58 | + p = narrative.product |
| 59 | + m = narrative.market |
| 60 | + gtm = narrative.gtm_motion |
| 61 | + lines += [ |
| 62 | + f"**Vendor:** {c.name} ({c.stage}, founded {c.founded_year}," |
| 63 | + f" {c.hq_city}, {c.hq_country})", |
| 64 | + "", |
| 65 | + f"**Product:** {p.name} — {p.category}. " |
| 66 | + f"Deployment: {p.deployment}. " |
| 67 | + f"Pricing: {p.pricing_model}. " |
| 68 | + f"ACV range: ${p.acv_range_usd[0]:,}–${p.acv_range_usd[1]:,}.", |
| 69 | + "", |
| 70 | + f"**Target market:** {m.icp_employee_range[0]}–{m.icp_employee_range[1]}-employee" |
| 71 | + f" firms in {', '.join(m.geographies)}. " |
| 72 | + f"Key industries: {', '.join(m.icp_industries)}. " |
| 73 | + f"Average deal size: ${m.avg_deal_size_usd:,}. " |
| 74 | + f"Average sales cycle: {m.avg_sales_cycle_days} days.", |
| 75 | + "", |
| 76 | + f"**GTM motion:** {', '.join(gtm.channels)} " |
| 77 | + f"({gtm.inbound_share:.0%} inbound / " |
| 78 | + f"{gtm.outbound_share:.0%} outbound / " |
| 79 | + f"{gtm.partner_share:.0%} partner).", |
| 80 | + "", |
| 81 | + "**Buyer personas:**", |
| 82 | + "", |
| 83 | + ] |
| 84 | + for persona in narrative.personas: |
| 85 | + ellipsis = "…" if len(persona.title_variants) > 2 else "" |
| 86 | + lines.append( |
| 87 | + f"- **{persona.role}** ({persona.decision_authority}) — " |
| 88 | + f"{', '.join(persona.title_variants[:2])}{ellipsis}" |
| 89 | + ) |
| 90 | + lines.append("") |
| 91 | + else: |
| 92 | + lines += ["*Narrative unavailable for this dataset.*", ""] |
| 93 | + |
| 94 | + # ------------------------------------------------------------------ |
| 95 | + # Primary task |
| 96 | + # ------------------------------------------------------------------ |
| 97 | + lines += [ |
| 98 | + "## Primary task", |
| 99 | + "", |
| 100 | + "**Task:** `converted_within_90_days`", |
| 101 | + "", |
| 102 | + "**Label definition:** A lead is considered converted if a `closed_won` event " |
| 103 | + "is recorded within 90 days of the lead's snapshot anchor date. " |
| 104 | + "The label is derived from simulated events — it is never sampled directly.", |
| 105 | + "", |
| 106 | + ] |
| 107 | + |
| 108 | + # ------------------------------------------------------------------ |
| 109 | + # Table inventory (stub — populated in later milestones) |
| 110 | + # ------------------------------------------------------------------ |
| 111 | + lines += [ |
| 112 | + "## Table inventory", |
| 113 | + "", |
| 114 | + "*Table counts will appear here once the simulation layer is implemented (v0.3.0+).*", |
| 115 | + "", |
| 116 | + ] |
| 117 | + |
| 118 | + # ------------------------------------------------------------------ |
| 119 | + # Feature categories (stub) |
| 120 | + # ------------------------------------------------------------------ |
| 121 | + lines += [ |
| 122 | + "## Feature categories", |
| 123 | + "", |
| 124 | + "*Feature dictionary will appear here once the schema layer is implemented (v0.3.0+).*", |
| 125 | + "", |
| 126 | + ] |
| 127 | + |
| 128 | + # ------------------------------------------------------------------ |
| 129 | + # Suggested use cases |
| 130 | + # ------------------------------------------------------------------ |
| 131 | + lines += [ |
| 132 | + "## Suggested use cases", |
| 133 | + "", |
| 134 | + "- Teaching binary classification on realistic CRM data", |
| 135 | + "- Portfolio projects demonstrating end-to-end ML pipelines", |
| 136 | + "- Benchmarking lead-scoring models under controlled signal/noise conditions", |
| 137 | + "- Research on causal structure in funnel conversion data", |
| 138 | + "", |
| 139 | + ] |
| 140 | + |
| 141 | + # ------------------------------------------------------------------ |
| 142 | + # Caveats |
| 143 | + # ------------------------------------------------------------------ |
| 144 | + lines += [ |
| 145 | + "## Caveats", |
| 146 | + "", |
| 147 | + "- This is **synthetic** data. It does not represent any real company, product, or market.", |
| 148 | + "- The hidden world structure varies by motif family and stochastic rewiring; " |
| 149 | + "no two seeds produce the same DGP.", |
| 150 | + "- Features are anchored at the snapshot date. No post-anchor data is " |
| 151 | + "included (leakage-free by construction).", |
| 152 | + "- In `student_public` mode, the latent world graph, mechanism summary, " |
| 153 | + "and full world spec are withheld.", |
| 154 | + "", |
| 155 | + ] |
| 156 | + |
| 157 | + return "\n".join(lines) |
0 commit comments