"""Lane B harvest: P2HS-Home pages → frozen capability coverage.

Marketing discovers demand. Marketing does not admit architecture.
Identifiers must already exist in Core catalogs, or be recorded as gaps.
"""

from __future__ import annotations

import json
import subprocess
import sys
from collections import Counter
from pathlib import Path

import yaml

ROOT = Path(__file__).resolve().parents[1]
HOME = ROOT.parent / "P2HS-Home"
OUT = ROOT / "docs/coverage/MARKETING_PRODUCT_COVERAGE.yaml"
RECIPE_OUT = ROOT / "src/domain/app_blocks/recipes/marketing_coverage.yaml"

if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))

# C5/C6 engines and reporting wait for Architecture. Honest C0/C2/C4 cores get recipes.
SKIP_RECIPE_CLUSTERS = frozenset({"specialist_engine"})

BLOCK_UI = {
    "forms.contact": ("Enquiry", "Send message", "Public enquiry form"),
    "forms.quote_request": ("Request a quote", "Request quote", "Quote request form"),
    "crm.lead": ("Leads", "No leads yet", "Lead pipeline"),
    "crm.customer": ("Customers", "No customers yet", "Customer records"),
    "crm.pipeline": ("Pipeline", "No pipeline records yet", "Sales pipeline"),
    "case.management": ("Cases", "No cases yet", "Case tracking"),
    "workflow.assignment": ("Assignments", "No assignments yet", "Assignment tracking"),
    "task.management": ("Tasks", "No tasks yet", "Task tracking"),
    "activity.timeline": ("Activity", "No activity yet", "Activity timeline"),
    "notifications.in_app": ("Notifications", "No notifications yet", "In-app notifications"),
    "files.attachments": ("Attachments", "No attachments yet", "File attachments"),
    "property.unit": ("Property units", "No property units yet", "Property units"),
    "asset.serialized": ("Serialized assets", "No serialized assets yet", "Serialized asset custody"),
    "lab.sample": ("Samples", "No samples yet", "Sample custody"),
    "lab.result_review": ("Result review", "No results awaiting review", "Laboratory result review"),
    "data.import": ("Data imports", "No imports yet", "Import reconciliation"),
    "education.student": ("Students", "No students yet", "Student and enrollment records"),
    "content.publishing": ("Content", "No content yet", "Content publishing workflow"),
    "inventory.warehouse": ("Warehouses", "No warehouse records yet", "Warehouse operations"),
    "logistics.shipment": ("Shipments", "No shipments yet", "Shipment operations"),
    "manufacturing.work_order": ("Work orders", "No work orders yet", "Manufacturing work orders"),
    "finance.loan_operations": ("Loans", "No loans yet", "Loan servicing operations"),
    "aviation.maintenance": ("Maintenance", "No maintenance records yet", "Aviation maintenance operations"),
    "billing.subscription_operations": ("Subscriptions", "No subscriptions yet", "Subscription operations"),
    "billing.portal": ("Billing", "Manage billing", "Customer billing portal"),
    "billing.tax": ("Tax", "Calculate tax", "Tax calculation"),
    "education.course_delivery": ("Courses", "No courses yet", "Course delivery operations"),
    "education.progress": ("Progress", "No progress yet", "Learner progress"),
    "education.assessment": ("Assessments", "No assessments yet", "Course assessments"),
    "education.certificate": ("Certificates", "No certificates yet", "Completion certificates"),
    "education.attendance": ("Attendance", "No attendance yet", "Attendance records"),
    "documents": ("Documents", "No documents yet", "Documents"),
    "booking.appointment": ("Appointments", "Reserve", "Appointment booking"),
    "support.ticket": ("Tickets", "No tickets yet", "Support tickets"),
    "catalog.product": ("Catalog", "No catalog items yet", "Product catalog"),
    "orders.order": ("Orders", "No orders yet", "Orders"),
    "billing.invoice": ("Invoices", "No invoices yet", "Invoices"),
    "payments.checkout": ("Checkout", "Continue to payment", "Checkout"),
    "inventory.stock": ("Inventory", "No inventory records yet", "Inventory"),
    "membership.member": ("Membership", "No membership records yet", "Membership roster"),
    "workflow.approval": ("Approvals", "No approval requests yet", "Approval requests"),
    "auth.sign_in": ("Sign in", None, "Sign-in"),
    "auth.profile": ("Profile", None, "User profile"),
}

ADMITTED_BLOCKS = frozenset(
    {
        "forms.contact",
        "forms.quote_request",
        "crm.lead",
        "crm.customer",
        "crm.pipeline",
        "case.management",
        "admin.shell",
        "admin.record_table",
        "admin.record_detail",
        "auth.sign_in",
        "auth.profile",
        "workflow.assignment",
        "task.management",
        "activity.timeline",
        "notifications.in_app",
        "files.attachments",
        "property.unit",
        "asset.serialized",
        "lab.sample",
        "lab.result_review",
        "data.import",
        "education.student",
        "content.publishing",
        "inventory.warehouse",
        "logistics.shipment",
        "manufacturing.work_order",
        "finance.loan_operations",
        "aviation.maintenance",
        "billing.subscription_operations",
        "billing.portal",
        "billing.tax",
        "education.course_delivery",
        "education.progress",
        "education.assessment",
        "education.certificate",
        "education.attendance",
        "documents",
        "booking.appointment",
        "support.ticket",
        "catalog.product",
        "orders.order",
        "billing.invoice",
        "inventory.stock",
        "membership.member",
        "workflow.approval",
        "reporting.dashboard",
        "crm.organization",
        "crm.team",
        "crm.org_unit",
        "crm.quote",
        "collaboration.comment",
        "governance.group",
        "customer_success.account",
        "contract.contract",
        "workforce.worker",
        "payments.checkout",
        "visualization.chart",
        "calculation.calculator",
        "content.collection",
    }
)

UNIVERSAL_FORBIDDEN = (
    "payments.checkout",
    "membership.portal",
    "saas-membership",
    "private_vertical_backend",
)

# Admitted App Block → frozen Feature Pack. Auth/admin shells have no pack.
# Checkout is admitted; saas-membership remains deliberately separate.
BLOCK_TO_PACK = {
    "forms.contact": "contact-capture",
    "forms.quote_request": "contact-capture",
    "crm.lead": "lead-lifecycle",
    "crm.customer": "customer-records",
    "crm.pipeline": "crm-pipeline",
    "case.management": "case-lifecycle",
    "workflow.assignment": "assignment-tracking",
    "task.management": "task-management",
    "activity.timeline": "activity-timeline",
    "notifications.in_app": "notifications-in-app",
        "files.attachments": "file-attachments",
        "property.unit": None,
        "asset.serialized": None,
        "lab.sample": None,
        "lab.result_review": None,
        "data.import": None,
    "education.student": None,
    "content.publishing": None,
    "inventory.warehouse": None,
    "logistics.shipment": None,
    "manufacturing.work_order": None,
    "finance.loan_operations": None,
    "aviation.maintenance": None,
    "billing.subscription_operations": "subscription-operations",
    "billing.portal": "billing-portal",
    "billing.tax": "tax-calculation",
    "education.course_delivery": None,
    "education.progress": "education-learning",
    "education.assessment": "education-learning",
    "education.certificate": "education-learning",
    "education.attendance": "education-learning",
    "documents": "documents-lifecycle",
    "booking.appointment": "appointment-booking",
    "support.ticket": "support-inbox",
    "catalog.product": "product-catalog",
    "orders.order": "order-management",
    "billing.invoice": "billing-management",
    "inventory.stock": "inventory-management",
    "membership.member": "membership-management",
    "payments.checkout": "payment-checkout",
    "workflow.approval": "approval-workflow",
    "reporting.dashboard": "reporting-dashboards",
    "crm.organization": "organization-records",
    "crm.quote": "quote-lifecycle",
    "collaboration.comment": "comment-threads",
    "governance.group": "governance-groups",
    "customer_success.account": "customer-success-accounts",
    "contract.contract": "contract-management",
    "workforce.worker": "workforce-directory",
}

# Explicit backend composition additions that are not UI App Blocks. These are
# still part of the generated project's Feature Pack contract and therefore
# must be visible in the marketing matrix.
CLUSTER_EXTRA_PACKS = {
    "membership_roster": ["renewal-operations"],
    "membership_entitlements": ["renewal-operations", "saas-membership"],
    "membership_access": ["renewal-operations", "saas-membership"],
    "client_portal": ["client-portal"],
}

COVERAGE_BY_CLASS = {
    "C0": {
        "architecture": "covered",
        "app_blocks": "covered",
        "feature_packs": "covered",
        "starter": "covered",
        "composition": "covered",
    },
    "C1": {
        "architecture": "covered",
        "app_blocks": "partial",
        "feature_packs": "covered",
        "starter": "covered",
        "composition": "covered",
    },
    "C2": {
        "architecture": "covered",
        "app_blocks": "covered",
        "feature_packs": "partial",
        "starter": "covered",
        "composition": "missing",
    },
    "C3": {
        "architecture": "covered",
        "app_blocks": "covered",
        "feature_packs": "covered",
        "starter": "missing",
        "composition": "covered",
    },
    "C4": {
        "architecture": "partial",
        "app_blocks": "partial",
        "feature_packs": "blocked",
        "starter": "covered",
        "composition": "blocked",
    },
    "C5": {
        "architecture": "missing",
        "app_blocks": "partial",
        "feature_packs": "blocked",
        "starter": "missing",
        "composition": "blocked",
    },
    "C6": {
        "architecture": "mismatch",
        "app_blocks": "mismatch",
        "feature_packs": "mismatch",
        "starter": "mismatch",
        "composition": "mismatch",
    },
}

# Honest staff-operational core only. Marketing extras go in mismatches.
CLUSTERS: dict[str, dict] = {
    "approval_ops": {
        "family": "approval_operations",
        "archetype": "approval_operations",
        "class": "C0",
        "app_blocks": [
            "workflow.approval",
            "documents",
            "files.attachments",
            "task.management",
        ],
        "live_recipe_batches": ["approval_ops_06"],
        "gaps": ["archetype_starter"],
        "mismatches": ["bank_payout", "remote_provisioning"],
        "architecture_family": None,
        "action": "reuse_approval_ops_06; do_not_invent_approval_engine",
        "slugs": [
            "purchase-order-approval-software",
            "content-approval-workflow",
            "employee-expense-approval",
            "website-change-request-portal",
            "time-off-request-approval",
            "contractor-timesheet-approval",
            "it-access-request-portal",
            "sales-quote-approval",
            "policy-exception-request",
            "change-control-request",
            "refund-request-approval",
            "construction-change-order-management",
        ],
    },
    "case_ops": {
        "family": "case_operations",
        "archetype": "case_operations",
        "class": "C0",
        "app_blocks": [
            "case.management",
            "task.management",
            "documents",
            "files.attachments",
            "activity.timeline",
        ],
        "live_recipe_batches": ["case_ops_02"],
        "gaps": ["archetype_starter"],
        "mismatches": ["claims_payment"],
        "architecture_family": None,
        "action": "reuse_case_ops_02; findings_are_case_or_task_records",
        "slugs": [
            "bug-tracking-issue-triage",
            "workplace-incident-report",
            "field-inspection-app",
            "shipment-exception-management",
            "customer-escalation-management",
            "customer-complaint-management",
            "legal-case-management-software",
            "immigration-case-management-software",
            "corporate-legal-matter-management",
            "legal-client-intake-software",
            "law-firm-management-software",
            "insurance-claims-management-software",
            "claims-intake-fnol-portal",
            "security-incident-response-management",
            "ehs-management-software",
            "capa-management-software",
            "audit-management-software",
            "nonconformance-management-software",
            "quality-management-system-qms",
            "food-safety-management-software",
            "haccp-management-software",
            "food-recall-management-system",
            "construction-rfi-management",
            "risk-register-management-software",
            "grant-application-management",
            "scholarship-application-portal",
            "student-admissions-management-software",
            "loan-origination-software",
            "commercial-lending-workflow",
            "mortgage-loan-management-platform",
            "accreditation-management-software",
        ],
    },
    "task_ops": {
        "family": "task_operations",
        "archetype": "internal_operations",
        "class": "C0",
        "app_blocks": ["task.management", "workflow.assignment", "activity.timeline"],
        "live_recipe_batches": ["task_ops_07"],
        "gaps": ["archetype_starter"],
        "mismatches": [],
        "architecture_family": None,
        "action": "reuse_task_ops_07",
        "slugs": [
            "project-management-saas",
            "staff-onboarding-checklist",
            "customer-onboarding-software",
            "implementation-project-portal",
            "franchise-location-launch",
            "retail-operations-management",
            "multi-location-operations-platform",
            "provider-onboarding-platform",
            "maintenance-work-order-app",
        ],
    },
    "documents_ops": {
        "family": "documents_operations",
        "archetype": "document_heavy_operations",
        "class": "C0",
        "app_blocks": ["documents", "files.attachments", "workflow.approval"],
        "live_recipe_batches": ["documents_ops_08", "approval_ops_06"],
        "gaps": ["archetype_starter"],
        "mismatches": ["google_docs_clone", "publishing_api"],
        "architecture_family": None,
        "action": "reuse_documents_ops_08",
        "slugs": [
            "document-control-software",
            "translation-review-workflow",
            "tender-response-coordination",
            "compliance-evidence-management",
            "compliance-management-platform",
        ],
    },
    "support_ops": {
        "family": "support_operations",
        "archetype": "support_operations",
        "class": "C0",
        "app_blocks": ["support.ticket", "workflow.assignment", "activity.timeline"],
        "live_recipe_batches": ["support_ops_05"],
        "gaps": ["archetype_starter"],
        "mismatches": [],
        "architecture_family": None,
        "action": "reuse_support_ops_05",
        "slugs": ["internal-help-desk"],
    },
    "ai_support": {
        "family": "support_operations",
        "archetype": "support_operations",
        "class": "C2",
        "app_blocks": ["support.ticket", "workflow.assignment", "activity.timeline"],
        "live_recipe_batches": ["support_ops_05", "marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_support_queues_with_suggested_ai_replies_and_human_escalation",
        "slugs": ["ai-customer-support-dashboard"],
    },
    "crm_sales": {
        "family": "crm_sales",
        "archetype": "crm_pipeline",
        "class": "C0",
        "app_blocks": [
            "crm.customer",
            "crm.lead",
            "crm.pipeline",
            "task.management",
            "activity.timeline",
        ],
        "live_recipe_batches": ["crm_intake_01"],
        "gaps": ["archetype_starter"],
        "mismatches": ["mls_sync", "ad_platform_capture"],
        "architecture_family": None,
        "action": "reuse_crm_intake_01",
        "slugs": [
            "ai-crm",
            "digital-marketing-agency-crm",
            "car-dealership-crm",
            "insurance-agency-crm",
            "partner-deal-registration",
            "customer-reference-program",
            "customer-success-management-platform",
            "partner-portal-software",
            "recruitment-ats",
        ],
    },
    "crm_plus_booking": {
        "family": "crm_sales",
        "archetype": "service_operations",
        "class": "C2",
        "app_blocks": [
            "crm.customer",
            "crm.lead",
            "crm.pipeline",
            "booking.appointment",
            "task.management",
        ],
        "live_recipe_batches": ["crm_intake_01", "booking_03"],
        "gaps": [],
        "mismatches": ["paid_booking"],
        "architecture_family": None,
        "action": "compose_crm_and_booking_recipe_without_checkout",
        "slugs": [
            "real-estate-crm",
            "beauty-business-crm",
            "photography-studio-management",
            "wedding-planning-platform",
        ],
    },
    "membership_roster": {
        "family": "membership_operations",
        "archetype": "membership_operations",
        "class": "C2",
        "app_blocks": ["membership.member", "crm.customer", "documents", "task.management"],
        "live_recipe_batches": ["marketing_coverage"],
        "gaps": ["archetype_starter"],
        "mismatches": ["stripe_subscription"],
        "architecture_family": None,
        "action": "compose_membership_roster_records_not_saas_or_portal",
        "slugs": [
            "trade-association-management",
            "community-association-management",
            "church-community-management-platform",
            "nonprofit-management-system",
            "hoa-management-software",
            "donation-intake-distribution",
            "gym-management-software",
        ],
    },
    "membership_entitlements": {
        "family": "membership_operations",
        "archetype": "membership_operations",
        "class": "C2",
        "app_blocks": ["membership.member", "crm.customer", "documents", "task.management"],
        "live_recipe_batches": ["marketing_coverage"],
        "gaps": ["archetype_starter"],
        "mismatches": ["stripe_subscription"],
        "architecture_family": None,
        "action": "compose_membership_records_with_membership_scoped_access_rules",
        "slugs": [
            "membership-management-system",
            "coworking-space-management-software",
        ],
    },
    "membership_access": {
        "family": "membership_operations",
        "archetype": "membership_operations",
        "class": "C2",
        "app_blocks": ["membership.member", "crm.customer", "documents", "task.management"],
        "live_recipe_batches": ["marketing_coverage"],
        "gaps": ["archetype_starter"],
        "mismatches": ["stripe_subscription"],
        "architecture_family": None,
        "action": "compose_membership_records_with_membership_scoped_event_access",
        "slugs": ["association-management-software"],
    },
    "board_governance": {
        "family": "governance_operations",
        "archetype": "document_heavy_operations",
        "class": "C2",
        "app_blocks": [
            "documents",
            "files.attachments",
            "task.management",
            "workflow.approval",
        ],
        "live_recipe_batches": ["documents_ops_08", "task_ops_07", "approval_ops_06"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_documents_tasks_approvals; do_not_install_membership.member",
        "slugs": ["board-management-portal", "committee-management-portal"],
    },
    "billing_records": {
        "family": "billing_records",
        "archetype": "order_operations",
        "class": "C2",
        "app_blocks": ["billing.invoice", "billing.tax", "crm.customer", "task.management"],
        "live_recipe_batches": ["marketing_coverage"],
        "gaps": ["archetype_starter"],
        "mismatches": ["stripe_charge"],
        "architecture_family": None,
        "action": "compose_invoice_records_without_stripe_or_gl",
        "slugs": [
            "invoice-management-system",
            "accounting-practice-software",
            "freelancer-management-platform",
            "retainer-management-software",
            "professional-services-automation-software",
            "consulting-firm-management-software",
        ],
    },
    "catalog_orders_inventory": {
        "family": "commerce_records",
        "archetype": "catalog_operations",
        "class": "C2",
        "app_blocks": [
            "catalog.product",
            "orders.order",
            "inventory.stock",
            "crm.customer",
            "billing.invoice",
            "payments.checkout",
        ],
        "live_recipe_batches": ["catalog_04"],
        "gaps": [],
        "mismatches": ["member_price"],
        "architecture_family": None,
        "action": "compose_catalog_orders_inventory_with_provider_checkout_boundary",
        "slugs": [
            "e-commerce-admin-panel",
            "wholesale-management-platform",
            "order-management-system",
            "b2b-order-portal",
            "dealer-portal-software",
            "inventory-management-system",
            "cycle-count-inventory-app",
            "inventory-lot-serial-tracking",
            "product-sample-request-portal",
            "return-merchandise-authorization",
            "supplier-order-collaboration-portal",
            "restaurant-management-software",
        ],
    },
    "booking_ops": {
        "family": "booking_operations",
        "archetype": "booking_business",
        "class": "C0",
        "app_blocks": [
            "booking.appointment",
            "crm.customer",
            "task.management",
            "billing.invoice",
        ],
        "live_recipe_batches": ["booking_03"],
        "gaps": [],
        "mismatches": ["paid_booking"],
        "architecture_family": None,
        "action": "compose_booking_crm_invoice_without_entitlements",
        "slugs": [
            "salon-management-software",
            "meeting-room-booking-app",
            "equipment-rental-booking-software",
            "tutoring-management-platform",
            "volunteer-scheduling-app",
            "usability-research-scheduling",
            "warehouse-dock-scheduling",
            "hotel-management-system",
            "event-management-platform",
            "travel-agency-management-system",
            "fitness-coach-platform",
            "pet-care-management-system",
            "childcare-daycare-management",
            "short-term-rental-operations-software",
            "condo-management-software",
        ],
    },
    "clinic_admin_not_ehr": {
        "family": "booking_operations",
        "archetype": "booking_business",
        "class": "C2",
        "app_blocks": ["booking.appointment", "crm.customer", "billing.invoice", "documents"],
        "live_recipe_batches": ["booking_03"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_staff_clinic_operations_without_regulated_ehr_or_insurance_claim",
        "slugs": [
            "dental-clinic-software",
            "medical-clinic-software",
            "veterinary-clinic-management",
            "mental-health-practice-management",
            "therapy-practice-management-software",
            "home-care-agency-management-software",
            "patient-intake-portal",
            "patient-referral-management-software",
            "prior-authorization-tracking-software",
        ],
    },
    "field_service": {
        "family": "field_service_operations",
        "archetype": "service_operations",
        "class": "C2",
        "app_blocks": [
            "crm.customer",
            "booking.appointment",
            "task.management",
            "case.management",
            "billing.invoice",
            "files.attachments",
        ],
        "live_recipe_batches": ["booking_03", "case_ops_02", "task_ops_07"],
        "gaps": [],
        "mismatches": ["live_gps"],
        "architecture_family": None,
        "action": "compose_crm_booking_tasks_invoices_cases",
        "slugs": [
            "field-service-management-software",
            "hvac-service-management-software",
            "plumbing-business-management-software",
            "electrical-contractor-management-software",
            "roofing-contractor-software",
            "landscaping-business-management-software",
            "auto-repair-shop-management",
            "utility-field-service-management",
            "solar-installer-management-software",
            "fleet-management-software",
            "courier-delivery-platform",
            "logistics-management-system",
            "freight-broker-management-software",
        ],
    },
    "client_portal": {
        "family": "client_portal",
        "archetype": "client_portal",
        "class": "C2",
        "app_blocks": [
            "auth.sign_in",
            "auth.profile",
            "documents",
            "files.attachments",
            "workflow.approval",
            "task.management",
            "crm.customer",
            "billing.portal",
        ],
        "live_recipe_batches": ["approval_ops_06", "documents_ops_08", "task_ops_07"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_auth_documents_approvals_without_new_acl",
        "slugs": [
            "client-onboarding-portal",
            "client-project-portal",
            "client-approval-portal",
            "legal-client-portal",
            "supplier-portal",
            "vendor-onboarding-software",
            "student-enrollment-portal",
        ],
    },
    "contract_lifecycle": {
        "family": "contract_lifecycle",
        "archetype": "document_heavy_operations",
        "class": "C2",
        "app_blocks": [
            "contract.contract",
            "documents",
            "files.attachments",
            "workflow.approval",
            "crm.customer",
        ],
        "live_recipe_batches": ["documents_ops_08", "approval_ops_06"],
        "gaps": [],
        "mismatches": ["esign_provider_required_for_signature_authority"],
        "architecture_family": None,
        "action": "contract_admitted_compose_without_esign_requirement",
        "slugs": [
            "contract-lifecycle-management-software",
            "contract-renewal-management-software",
            "renewal-management-software",
            "software-license-renewal-tracker",
            "permit-license-renewal-tracker",
        ],
    },
    "reporting": {
        "family": "reporting",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": ["reporting.dashboard", "crm.customer", "task.management", "billing.invoice"],
        "live_recipe_batches": ["marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "reporting_admitted_compose_without_bi_warehouse",
        "slugs": [
            "customer-health-score-dashboard",
            "project-profitability-dashboard",
            "supplier-performance-scorecard",
            "customer-feedback-roadmap",
        ],
    },
    "workforce": {
        "family": "workforce_operations",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": [
            "workforce.worker",
            "task.management",
            "documents",
            "workflow.approval",
            "crm.customer",
        ],
        "live_recipe_batches": ["task_ops_07", "documents_ops_08"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "workforce_admitted_compose_without_payroll_or_membership_reuse",
        "slugs": [
            "employee-scheduling-software",
            "shift-management-software",
            "workforce-credential-tracking",
            "employee-certification-tracker",
            "contractor-workforce-management",
            "staff-compliance-management",
            "hr-management-system",
            "resource-planning-software",
            "healthcare-provider-credentialing-software",
            "access-review-certification-software",
        ],
    },
    "property_assets": {
        "family": "property_operations",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": [
            "case.management",
            "task.management",
            "crm.customer",
            "property.unit",
            "asset.serialized",
            "booking.appointment",
            "billing.invoice",
        ],
        "live_recipe_batches": ["case_ops_02", "task_ops_07", "booking_03"],
        "gaps": [],
        "mismatches": ["rent_ach_collection"],
        "architecture_family": None,
        "action": "maintenance_is_case_or_task; unit_lease_needs_architecture_admission",
        "slugs": [
            "property-management-software",
            "facilities-management-software",
            "multi-location-facilities-management",
            "self-storage-management-software",
            "serialized-asset-management",
            "asset-custody-transfer",
            "equipment-calibration-tracker",
            "visitor-sign-in-app",
        ],
    },
    "procurement": {
        "family": "procurement_operations",
        "archetype": "approval_operations",
        "class": "C2",
        "app_blocks": [
            "workflow.approval",
            "documents",
            "crm.customer",
            "orders.order",
            "case.management",
        ],
        "live_recipe_batches": ["approval_ops_06", "case_ops_02"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_approvals_documents_vendor_records_orders",
        "slugs": [
            "procurement-intake-portal",
            "vendor-risk-management-software",
            "vendor-security-assessment-portal",
            "rfx-sourcing-management",
            "supplier-quality-management-software",
            "supplier-corrective-action",
            "subcontractor-prequalification-portal",
            "construction-bid-management-software",
        ],
    },
    "saas_analytics": {
        "family": "reporting",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": [
            "reporting.dashboard",
            "visualization.chart",
            "calculation.calculator",
            "crm.customer",
            "task.management",
            "billing.invoice",
        ],
        "live_recipe_batches": ["marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_saas_metrics_charts_and_calculation_from_bounded_record_aggregates",
        "slugs": ["saas-analytics-dashboard"],
    },
    "procurement_match": {
        "family": "procurement_operations",
        "archetype": "approval_operations",
        "class": "C2",
        "app_blocks": [
            "workflow.approval",
            "documents",
            "crm.customer",
            "orders.order",
            "billing.invoice",
            "case.management",
            "calculation.calculator",
        ],
        "live_recipe_batches": ["approval_ops_06", "case_ops_02", "marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_procurement_orders_invoices_approval_and_authoritative_three_way_calculation",
        "slugs": ["procurement-management-software"],
    },
    "education_cert": {
        "family": "education_operations",
        "archetype": "case_operations",
        "class": "C2",
        "app_blocks": [
            "case.management",
            "documents",
            "workflow.approval",
            "membership.member",
            "crm.customer",
        ],
        "live_recipe_batches": ["case_ops_02"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_case_documents_membership_roster_not_lms",
        "slugs": [
            "education-consultancy-management",
            "internship-placement-management",
            "grant-management-software",
        ],
    },
    "certification_operations": {
        "family": "education_operations",
        "archetype": "case_operations",
        "class": "C2",
        "app_blocks": [
            "case.management",
            "documents",
            "workflow.approval",
            "membership.member",
            "education.progress",
            "education.assessment",
            "education.certificate",
        ],
        "live_recipe_batches": ["case_ops_02", "marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_certification_assessment_certificate_and_renewal_operations",
        "slugs": ["certification-program-management"],
    },
    "clinical_case": {
        "family": "clinical_case_operations",
        "archetype": "case_operations",
        "class": "C2",
        "app_blocks": [
            "case.management",
            "task.management",
            "documents",
            "files.attachments",
            "activity.timeline",
            "calculation.calculator",
        ],
        "live_recipe_batches": ["case_ops_02", "marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_staff_case_workflow_with_treatment_fields_and_settlement_calculation",
        "slugs": ["personal-injury-case-management"],
    },
    "education_lms": {
        "family": "education_operations",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": [
            "education.course_delivery",
            "education.progress",
            "education.assessment",
            "education.certificate",
            "content.collection",
            "documents",
            "membership.member",
            "task.management",
        ],
        "live_recipe_batches": ["marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_course_delivery_progress_assessment_certificates_and_team_tasks",
        "slugs": ["employee-training-platform"],
    },
    "school_operations": {
        "family": "education_operations",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": [
            "education.student",
            "case.management",
            "documents",
            "workflow.approval",
            "membership.member",
            "payments.checkout",
            "education.attendance",
        ],
        "live_recipe_batches": ["case_ops_02", "marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_student_enrollment_and_case_operations_without_sis_or_lms_claim",
        "slugs": ["school-management-system"],
    },
    "social_operations": {
        "family": "content_operations",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": [
            "content.publishing",
            "content.collection",
            "workflow.approval",
            "task.management",
            "files.attachments",
        ],
        "live_recipe_batches": ["marketing_coverage", "approval_ops_06", "task_ops_07"],
        "gaps": [],
        "mismatches": ["social_platform_credentials"],
        "architecture_family": None,
        "action": "compose_content_calendar_and_approval_with_approved_social_publish_adapters",
        "slugs": ["social-media-management-dashboard"],
    },
    "warehouse_operations": {
        "family": "warehouse_operations",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": [
            "inventory.warehouse",
            "inventory.stock",
            "logistics.shipment",
            "task.management",
            "documents",
            "calculation.calculator",
        ],
        "live_recipe_batches": ["marketing_coverage", "task_ops_07", "documents_ops_08"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_warehouse_bins_stock_shipments_and_location_ordered_pick_lists",
        "slugs": ["warehouse-management-system"],
    },
    "transport_operations": {
        "family": "transport_operations",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": ["logistics.shipment", "task.management", "documents", "crm.customer"],
        "live_recipe_batches": ["marketing_coverage", "task_ops_07", "documents_ops_08"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_shipment_dispatch_and_delivery_records_without_tms_optimization",
        "slugs": ["transportation-management-system"],
    },
    "manufacturing_operations": {
        "family": "manufacturing_operations",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": ["manufacturing.work_order", "inventory.stock", "task.management", "workflow.approval", "reporting.dashboard"],
        "live_recipe_batches": ["marketing_coverage", "task_ops_07", "approval_ops_06"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_work_orders_inventory_approvals_and_plant_reporting",
        "slugs": ["manufacturing-erp-dashboard"],
    },
    "loan_operations": {
        "family": "loan_operations",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": ["finance.loan_operations", "case.management", "documents", "billing.invoice"],
        "live_recipe_batches": ["marketing_coverage", "case_ops_02", "documents_ops_08"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_loan_servicing_cases_schedules_and_documents",
        "slugs": ["loan-servicing-software"],
    },
    "aviation_operations": {
        "family": "aviation_operations",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": [
            "aviation.maintenance",
            "asset.serialized",
            "case.management",
            "task.management",
            "workflow.approval",
            "documents",
        ],
        "live_recipe_batches": ["marketing_coverage", "case_ops_02", "task_ops_07", "documents_ops_08"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_maintenance_evidence_technician_approval_and_release_gate",
        "slugs": ["aviation-maintenance-management-software"],
    },
    "subscription_operations": {
        "family": "subscription_operations",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": ["billing.subscription_operations", "billing.tax", "crm.customer"],
        "live_recipe_batches": ["marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_subscription_operations_with_explicit_recurrence_integration",
        "slugs": ["subscription-management-platform"],
    },
    "course_delivery": {
        "family": "course_delivery",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": ["education.course_delivery", "education.progress", "education.assessment", "education.certificate", "payments.checkout", "content.collection", "documents", "membership.member"],
        "live_recipe_batches": ["marketing_coverage", "documents_ops_08"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_course_catalog_and_enrollment_with_explicit_lms_runtime_integration",
        "slugs": ["online-course-lms-platform"],
    },
    "construction_precon": {
        "family": "construction_operations",
        "archetype": "case_operations",
        "class": "C2",
        "app_blocks": [
            "case.management",
            "task.management",
            "documents",
            "crm.customer",
            "forms.quote_request",
        ],
        "live_recipe_batches": ["case_ops_02", "task_ops_07"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_cases_tasks_documents_quotes",
        "slugs": [
            "construction-management-software",
        ],
    },
    "construction_estimating": {
        "family": "construction_operations",
        "archetype": "case_operations",
        "class": "C2",
        "app_blocks": [
            "case.management",
            "task.management",
            "documents",
            "crm.customer",
            "forms.quote_request",
            "calculation.calculator",
        ],
        "live_recipe_batches": ["case_ops_02", "task_ops_07", "marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_estimating_quantity_entry_quotes_and_calculation",
        "slugs": ["construction-estimating-software"],
    },
    "lab_trace": {
        "family": "laboratory_operations",
        "archetype": "case_operations",
        "class": "C2",
        "app_blocks": [
            "case.management",
            "documents",
            "inventory.stock",
            "files.attachments",
            "lab.sample",
            "lab.result_review",
        ],
        "live_recipe_batches": ["marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_sample_custody_result_review_and_traceability",
        "slugs": [
            "laboratory-sample-tracking-software",
            "food-traceability-software",
            "production-batch-traceability",
        ],
    },
    "lims_operations": {
        "family": "laboratory_operations",
        "archetype": "case_operations",
        "class": "C2",
        "app_blocks": [
            "case.management",
            "documents",
            "inventory.stock",
            "files.attachments",
            "lab.sample",
            "lab.result_review",
        ],
        "live_recipe_batches": ["marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_lims_sample_assignment_result_review_and_client_reports",
        "slugs": ["laboratory-information-management-system-lims"],
    },
    "imports_ops": {
        "family": "data_operations",
        "archetype": "internal_operations",
        "class": "C2",
        "app_blocks": ["data.import", "case.management", "documents"],
        "live_recipe_batches": ["marketing_coverage"],
        "gaps": [],
        "mismatches": [],
        "architecture_family": None,
        "action": "compose_explicit_import_review_without_specialist_engine",
        "slugs": ["data-import-reconciliation"],
    },
    "specialist_engine": {
        "family": "specialist_engine",
        "archetype": "internal_operations",
        "class": "C6",
        "app_blocks": [],
        "live_recipe_batches": [],
        "gaps": ["specialist_engine_out_of_scope"],
        "mismatches": [
            "warehouse_bin_wms",
            "tms_optimization",
            "manufacturing_mrp",
            "school_sis",
            "lms_checkout",
            "loan_servicing_posting",
            "aviation_release_to_service",
            "csv_production_connector",
        ],
        "architecture_family": None,
        "action": "correct_marketing_or_explicit_roadmap; do_not_codegen_engine",
        "slugs": [
        ],
    },
}


def _slug_index() -> dict[str, str]:
    index: dict[str, str] = {}
    for cluster_id, cluster in CLUSTERS.items():
        for slug in cluster["slugs"]:
            if slug in index:
                raise SystemExit(f"duplicate slug {slug} in {index[slug]} and {cluster_id}")
            index[slug] = cluster_id
    return index


def load_home_pages() -> list[dict]:
    if not (HOME / "scripts/market-pages.js").is_file():
        raise SystemExit(f"P2HS-Home corpus not found at {HOME}")
    script = (
        "const pages=require('./scripts/market-pages');"
        "process.stdout.write(JSON.stringify(pages.map(p=>({slug:p.slug,name:p.name,"
        "category:p.category||'',entities:p.entities||[],prompt:p.prompt||''}))));"
    )
    raw = subprocess.check_output(
        ["node", "-e", script],
        cwd=HOME,
        encoding="utf-8",
    )
    return json.loads(raw)


def classify(page: dict, cluster_id: str) -> dict:
    from src.domain.app_blocks.job_packs import job_pack_ids_for_cluster
    from src.domain.starter_library.archetypes import known_archetype_ids, starter_id_for_cluster

    cluster = CLUSTERS[cluster_id]
    blocks = list(cluster["app_blocks"])
    unknown = [block for block in blocks if block not in ADMITTED_BLOCKS]
    if unknown:
        raise SystemExit(f"{cluster_id} invented blocks {unknown}")
    packs = []
    for block in blocks:
        pack = BLOCK_TO_PACK.get(block)
        if pack and pack not in packs:
            packs.append(pack)
    for pack in CLUSTER_EXTRA_PACKS.get(cluster_id, []):
        if pack not in packs:
            packs.append(pack)
    cls = cluster["class"]
    batches = list(cluster["live_recipe_batches"])
    if cluster_id not in SKIP_RECIPE_CLUSTERS and "marketing_coverage" not in batches:
        batches.append("marketing_coverage")
    starter = starter_id_for_cluster(cluster_id)
    if starter and starter not in known_archetype_ids():
        raise SystemExit(f"{cluster_id} unknown starter {starter}")
    coverage = dict(COVERAGE_BY_CLASS[cls])
    if starter:
        coverage["starter"] = "mismatch" if cls == "C6" else "covered"
    gaps = [
        gap
        for gap in cluster["gaps"]
        if not (starter and gap in {"archetype_starter", "customer_surface_starter"})
    ]
    if starter and not gaps and "marketing_coverage" in batches:
        coverage["composition"] = "covered"
    return {
        "slug": page["slug"],
        "name": page["name"],
        "category": page["category"],
        "family": cluster["family"],
        "archetype": cluster["archetype"],
        "class": cls,
        "app_blocks": blocks,
        "feature_packs": packs,
        "job_feature_packs": job_pack_ids_for_cluster(cluster_id),
        "starter": starter or ("mismatch" if cls == "C6" else "missing"),
        "live_recipe_batches": batches,
        "coverage": coverage,
        "gaps": gaps,
        "forbidden": [
            item
            for item in UNIVERSAL_FORBIDDEN
            if item not in blocks
            and not (item == "saas-membership" and "saas-membership" in packs)
        ],
        "mismatches": list(cluster["mismatches"]),
        "architecture_family": cluster["architecture_family"],
        "action": cluster["action"],
        "prompt": page.get("prompt") or "",
    }


def _existing_recipe_phrases() -> set[str]:
    phrases: set[str] = set()
    recipes = ROOT / "src/domain/app_blocks/recipes"
    for path in recipes.glob("*.yaml"):
        if path.name == "marketing_coverage.yaml":
            continue
        data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
        for recipe in data.get("recipes") or []:
            phrases.update(str(item).lower() for item in recipe.get("intent_phrases") or [])
    return phrases


def _13a_prompts() -> list[str]:
    path = ROOT / "scripts/phase_13a_corpus.txt"
    if not path.is_file():
        return []
    prompts: list[str] = []
    for line in path.read_text(encoding="utf-8").splitlines():
        if not line or line.startswith("#"):
            continue
        parts = line.split("|", 2)
        if len(parts) == 3:
            prompts.append(parts[2].lower())
    return prompts


def _phrases_for_page(page: dict, corpus: list[str]) -> list[str]:
    candidates: list[str] = []
    name = (page.get("name") or "").strip().lower()
    slug_words = page["slug"].replace("-", " ")
    prompt = (page.get("prompt") or "").strip().lower()
    for candidate in (name, slug_words):
        words = candidate.split()
        if len(candidate) >= 18 or len(words) >= 3:
            candidates.append(candidate)
    if prompt:
        first = prompt.split(".")[0].strip()
        if 24 <= len(first) <= 160:
            candidates.append(first)
        prefix = prompt[:88].rsplit(" ", 1)[0].strip(" .,")
        if len(prefix) >= 24:
            candidates.append(prefix)
    seen: set[str] = set()
    safe: list[str] = []
    for phrase in candidates:
        if phrase in seen or any(phrase in haystack for haystack in corpus):
            continue
        seen.add(phrase)
        safe.append(phrase)
    return safe


def recipe_catalog(rows: list[dict]) -> dict:
    corpus = _13a_prompts()
    reserved = _existing_recipe_phrases()
    by_cluster: dict[str, list[dict]] = {}
    index = _slug_index()
    for row in rows:
        cluster_id = index[row["slug"]]
        by_cluster.setdefault(cluster_id, []).append(row)
    recipes = []
    for cluster_id, cluster in CLUSTERS.items():
        if cluster_id in SKIP_RECIPE_CLUSTERS or not cluster["app_blocks"]:
            continue
        pages = by_cluster.get(cluster_id) or []
        phrases: list[str] = [f"mkt {cluster_id.replace('_', ' ')}"]
        for page in pages:
            for phrase in _phrases_for_page(page, corpus):
                if phrase in reserved or phrase in phrases:
                    continue
                phrases.append(phrase)
        if len(phrases) < 2:
            continue
        terminology = {}
        labels = {}
        for block in cluster["app_blocks"]:
            ui = BLOCK_UI.get(block)
            if not ui:
                continue
            heading, empty, label = ui
            slot: dict[str, str] = {"heading": heading}
            if empty and block not in {"auth.sign_in", "auth.profile"}:
                if block == "booking.appointment":
                    slot["submit_label"] = empty
                    slot["success_message"] = "Appointment reserved."
                    slot["route"] = "/book"
                elif block in {"forms.contact", "forms.quote_request"}:
                    slot["submit_label"] = empty
                else:
                    slot["empty_state"] = empty
            terminology[block] = slot
            labels[block] = label
        installed_packs = {
            BLOCK_TO_PACK.get(block)
            for block in cluster["app_blocks"]
            if BLOCK_TO_PACK.get(block)
        }
        installed_packs.update(CLUSTER_EXTRA_PACKS.get(cluster_id, []))
        unsupported = list(cluster["mismatches"]) + [
            f"forbidden:{item}"
            for item in UNIVERSAL_FORBIDDEN
            if item not in cluster["app_blocks"]
            and not (item == "saas-membership" and item in installed_packs)
        ]
        recipes.append(
            {
                "id": f"mkt_{cluster_id}",
                "name": cluster["family"].replace("_", " ").title(),
                "archetype": cluster["archetype"],
                "proof_profile": "MARKETING_COVERAGE",
                "intent_phrases": phrases,
                "requires": {"app_blocks": list(cluster["app_blocks"])},
                "terminology": terminology,
                "public_labels": labels,
                "unsupported_assumptions": unsupported,
            }
        )
    return {
        "schema_version": 1,
        "lane": "fast_lane",
        "batch": "marketing_coverage",
        "ready_for_fast_lane_composition": True,
        "recipes": recipes,
    }


def dump_recipes(catalog: dict) -> None:
    header = (
        "# Lane C: P2HS-Home demand composed onto admitted App Blocks.\n"
        "# Generated by scripts/harvest_marketing_product_coverage.py. Do not hand-edit.\n"
        "# Not Fast Lane batch 11. Marketing cannot admit capabilities.\n"
    )
    RECIPE_OUT.write_text(
        header
        + yaml.safe_dump(catalog, sort_keys=False, allow_unicode=True, width=100),
        encoding="utf-8",
        newline="\n",
    )


def harvest() -> dict:
    pages = load_home_pages()
    index = _slug_index()
    missing = [page["slug"] for page in pages if page["slug"] not in index]
    extra = sorted(set(index) - {page["slug"] for page in pages})
    if missing or extra:
        raise SystemExit(f"slug drift missing={missing} extra={extra}")
    rows = [classify(page, index[page["slug"]]) for page in pages]
    counts = Counter(row["class"] for row in rows)
    unlock = Counter(
        row["architecture_family"]
        for row in rows
        if row["class"] in {"C4", "C5"} and row["architecture_family"]
    )
    return {
        "schema_version": 1,
        "program": "marketing_to_product_coverage",
        "lane": "B",
        "corpus": "P2HS-Home/scripts/market-pages.js",
        "authority": "frozen Core catalogs; marketing cannot admit capabilities",
        "page_count": len(rows),
        "class_counts": dict(counts),
        "architecture_unlock_rank": [
            {"family": family, "c4_c5_pages": n} for family, n in unlock.most_common()
        ],
        "sequential_architecture_family": "none",
        "fast_lane_recipe_work_membership": "live",
        "fast_lane_batch_11_started": False,
        "rule": "SEO page exists → demand recognized → capability graph checked → only admitted primitives used",
        "pages": rows,
    }


def dump(payload: dict, catalog: dict) -> None:
    OUT.parent.mkdir(parents=True, exist_ok=True)
    OUT.write_text(
        yaml.safe_dump(payload, sort_keys=False, allow_unicode=True, width=100),
        encoding="utf-8",
        newline="\n",
    )
    dump_recipes(catalog)


def main() -> int:
    payload = harvest()
    catalog = recipe_catalog(payload["pages"])
    if "--check" in sys.argv:
        current = yaml.safe_load(OUT.read_text(encoding="utf-8")) if OUT.is_file() else None
        if current != payload:
            print("MARKETING_PRODUCT_COVERAGE.yaml is stale; rerun harvest", file=sys.stderr)
            return 1
        on_disk = yaml.safe_load(RECIPE_OUT.read_text(encoding="utf-8")) if RECIPE_OUT.is_file() else None
        if on_disk != catalog:
            print("marketing_coverage.yaml is stale; rerun harvest", file=sys.stderr)
            return 1
        print(
            f"coverage ok pages={payload['page_count']} classes={payload['class_counts']} "
            f"recipes={len(catalog['recipes'])}"
        )
        return 0
    dump(payload, catalog)
    print(f"wrote {OUT} pages={payload['page_count']} classes={payload['class_counts']}")
    print(f"wrote {RECIPE_OUT} recipes={len(catalog['recipes'])}")
    print("unlock", payload["architecture_unlock_rank"])
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
