levrops-contracts 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,8 @@ Source of truth for LevrOps API and event contracts, JSON schemas, and generated
8
8
  - `asyncapi/` – AsyncAPI contracts for event/webhook payloads (`events.yaml`)
9
9
  - `schemas/` – Shared JSON Schemas for core LevrOps domain objects
10
10
  - `contracts/content/` – Content contracts for Sanity schema generation
11
+ - `contracts/designops/` – DesignOps contracts for design system orchestration (design systems, projects, artifacts, agents)
12
+ - `examples/` – Example contract instances for reference
11
13
  - `clients/ts/` – TypeScript SDK generated from the OpenAPI specification
12
14
  - `sanity/generated/` – Generated Sanity schema from content contracts
13
15
  - `COMPAT.json` – Compatibility matrix between API, events, and SDK releases
@@ -134,6 +136,87 @@ export default {
134
136
 
135
137
  See `docs/sanity-integration.md` for detailed integration instructions.
136
138
 
139
+ ## DesignOps Contracts
140
+
141
+ DesignOps contracts enable ConductorOps and other agents to reason about and orchestrate design work (Figma, design systems, UI flows) in the same way they already reason about backend/frontend/ops.
142
+
143
+ **Contract Types:**
144
+ - **DesignSystemContract** - Reusable design systems (colors, typography, spacing, components)
145
+ - **DesignProjectContract** - Design projects (apps, features, flows, landing pages)
146
+ - **DesignArtifactContract** - Maps Figma components/frames to code components/tokens
147
+ - **DesignOpsAgentContract** - Agent configuration for orchestrating design workflows
148
+
149
+ **Schema Files:**
150
+ - `schemas/design_system.json` - DesignSystemContract schema
151
+ - `schemas/design_project.json` - DesignProjectContract schema
152
+ - `schemas/design_artifact.json` - DesignArtifactContract schema
153
+ - `schemas/designops_agent.json` - DesignOpsAgentContract schema
154
+
155
+ **Examples:**
156
+ - `examples/designops/levrops-core-design-system.json` - Example design system
157
+ - `examples/designops/conductorops-dashboard-project.json` - Example design project
158
+ - `examples/designops/design-artifacts-example.json` - Example artifact mappings
159
+ - `examples/designops/designops-agent-example.json` - Example agent configuration
160
+
161
+ See `docs/designops.md` for detailed documentation on DesignOps contracts and integration with ConductorOps.
162
+
163
+ ## E-commerce Contracts
164
+
165
+ E-commerce contracts enable reporting and management of preorder deposits across multi-tenant e-commerce integrations.
166
+
167
+ **Contract Types:**
168
+ - **PreorderDepositReport** - Aggregated report of preorder deposits with summary statistics
169
+ - **PreorderDepositOrder** - Individual preorder deposit order with deposit and balance information
170
+
171
+ **Schema Files:**
172
+ - `schemas/preorder_deposit_report.json` - PreorderDepositReport schema
173
+ - `schemas/preorder_deposit_order.json` - PreorderDepositOrder schema
174
+
175
+ **API Endpoints:**
176
+ - `GET /api/v1/tenants/{tenantId}/ecommerce/preorder-deposits` - Get preorder deposit report
177
+ - `GET /api/v1/tenants/{tenantId}/ecommerce/preorder-deposits/{orderId}` - Get order details
178
+ - `POST /api/v1/tenants/{tenantId}/ecommerce/preorder-deposits/{orderId}/charge-balance` - Charge balance
179
+
180
+ **Examples:**
181
+ - `examples/ecommerce/preorder-deposit-report-example.json` - Example preorder deposit report
182
+
183
+ ## Editorial Content Engine Contracts
184
+
185
+ The Editorial Content Engine provides standardized content management capabilities for creating, scheduling, and publishing content across multiple channels (social media, blogs, email campaigns) across all LevrOps products.
186
+
187
+ **Contract Types:**
188
+ - **Campaign** - Marketing campaigns that organize multiple content assets
189
+ - **ContentAsset** - Content assets (articles, blog posts, social posts, emails, videos) with rich content support
190
+ - **Channel** - Publishing channels (Instagram, Facebook, Newsletter, Sanity, Shopify Blog, etc.)
191
+ - **ContentTopic** - Topics/categories for organizing content
192
+
193
+ **Schema Files:**
194
+ - `schemas/campaign.json` - Campaign schema
195
+ - `schemas/content_asset.json` - ContentAsset schema (updated with rich content, media attachments, timezone support)
196
+ - `schemas/channel.json` - Channel schema
197
+ - `schemas/content_topic.json` - ContentTopic schema
198
+
199
+ **API Endpoints:**
200
+ All endpoints are under `/api/v1/editorial/`:
201
+ - Campaign management: `/campaigns/`, `/campaigns/{id}/`, `/campaigns/{id}/add-assets/`, `/campaigns/{id}/remove-assets/`
202
+ - Content asset management: `/assets/`, `/assets/{id}/`, `/assets/{id}/publish/`, `/assets/{id}/publish-status/`
203
+ - Channel management: `/channels/`, `/channels/{id}/`
204
+ - Topic management: `/topics/`, `/topics/{id}/`
205
+
206
+ **Key Features:**
207
+ - Rich content support (HTML body + structured JSON for Portable Text, Slate, etc.)
208
+ - Media attachments with metadata
209
+ - Timezone-aware scheduling
210
+ - Multi-channel publishing (Instagram, Facebook, TikTok, Newsletter, Sanity, Shopify Blog)
211
+ - Campaign organization and performance tracking
212
+ - Publishing workflow status tracking
213
+
214
+ **Examples:**
215
+ - `examples/editorial/summer-collection-campaign.json` - Example campaign
216
+ - `examples/editorial/summer-collection-preview-asset.json` - Example content asset
217
+ - `examples/editorial/instagram-channel.json` - Example channel
218
+ - `examples/editorial/fashion-topic.json` - Example topic
219
+
137
220
  ## Versioning
138
221
 
139
222
  - Use semantic versioning for the repo (`VERSION`) and the TypeScript SDK.
@@ -0,0 +1,220 @@
1
+ $schema: "https://json-schema.org/draft/2020-12/schema"
2
+ $id: "https://contracts.levrops.com/schemas/backend/migration_discipline.json"
3
+ title: "Migration Drift Prevention Contract"
4
+ description: |
5
+ Defines rules and constraints for Django migrations in a multi-tenant
6
+ django-tenants environment to prevent migration drift, cross-tenancy FK
7
+ violations, and improper migration ordering.
8
+
9
+ type: object
10
+ required:
11
+ - version
12
+ - rules
13
+ - enforcement
14
+
15
+ properties:
16
+ version:
17
+ type: string
18
+ description: "Contract version (semver)"
19
+ pattern: "^\\d+\\.\\d+\\.\\d+$"
20
+ default: "1.0.0"
21
+
22
+ rules:
23
+ type: object
24
+ description: "Migration discipline rules"
25
+ required:
26
+ - migration_commands
27
+ - cross_tenancy_fk
28
+ - migration_dependencies
29
+
30
+ properties:
31
+ migration_commands:
32
+ type: object
33
+ description: "Rules for allowed migration commands"
34
+ properties:
35
+ forbidden_patterns:
36
+ type: array
37
+ description: |
38
+ Command patterns that are forbidden. These patterns match
39
+ app-level migrate commands that bypass schema/tenant isolation.
40
+ items:
41
+ type: string
42
+ default:
43
+ - "manage.py migrate core"
44
+ - "manage.py migrate org"
45
+ - "manage.py migrate [a-z_]+"
46
+ examples:
47
+ - "manage.py migrate core"
48
+ - "manage.py migrate org"
49
+ - "manage.py migrate api"
50
+
51
+ required_patterns:
52
+ type: array
53
+ description: |
54
+ Command patterns that must be used instead. These ensure
55
+ proper schema/tenant isolation.
56
+ items:
57
+ type: string
58
+ default:
59
+ - "manage.py migrate"
60
+ - "manage.py migrate_schemas --shared"
61
+ - "manage.py migrate_schemas --tenant"
62
+
63
+ explanation:
64
+ type: string
65
+ description: "Human-readable explanation of why app-level migrations are forbidden"
66
+ default: |
67
+ In django-tenants, app-level migrations (e.g., `migrate core`)
68
+ bypass schema isolation and can cause migration drift. Always use:
69
+ - `manage.py migrate` (for public schema)
70
+ - `manage.py migrate_schemas --shared` (for shared apps)
71
+ - `manage.py migrate_schemas --tenant` (for tenant apps)
72
+
73
+ cross_tenancy_fk:
74
+ type: object
75
+ description: "Rules preventing cross-tenancy foreign key violations"
76
+ properties:
77
+ forbidden:
78
+ type: boolean
79
+ description: "Whether cross-tenancy FKs are forbidden"
80
+ default: true
81
+
82
+ definition:
83
+ type: string
84
+ description: "What constitutes a cross-tenancy FK violation"
85
+ default: |
86
+ A model in SHARED_APPS having a ForeignKey, OneToOneField, or
87
+ ManyToManyField to a model in TENANT_APPS (or vice versa).
88
+
89
+ allowed_exceptions:
90
+ type: array
91
+ description: |
92
+ Specific model/field combinations that are allowed despite
93
+ being cross-tenancy. Use sparingly and document why.
94
+ items:
95
+ type: object
96
+ properties:
97
+ app:
98
+ type: string
99
+ model:
100
+ type: string
101
+ field:
102
+ type: string
103
+ reason:
104
+ type: string
105
+ default: []
106
+
107
+ explanation:
108
+ type: string
109
+ description: "Why cross-tenancy FKs are problematic"
110
+ default: |
111
+ Cross-tenancy FKs break schema isolation. Shared apps are
112
+ schema-agnostic and cannot reference tenant-specific models.
113
+ Tenant apps cannot reference shared models via FK (use string
114
+ IDs or separate lookup tables instead).
115
+
116
+ migration_dependencies:
117
+ type: object
118
+ description: "Rules for migration dependency declarations"
119
+ properties:
120
+ require_explicit_dependencies:
121
+ type: boolean
122
+ description: |
123
+ Whether migrations that create cross-app FKs must explicitly
124
+ declare dependencies on the target app's migrations.
125
+ default: true
126
+
127
+ dependency_detection:
128
+ type: object
129
+ description: "How to detect missing dependencies"
130
+ properties:
131
+ scan_patterns:
132
+ type: array
133
+ description: "File patterns to scan for migrations"
134
+ items:
135
+ type: string
136
+ default:
137
+ - "apps/*/migrations/*.py"
138
+ - "*/migrations/*.py"
139
+
140
+ field_types:
141
+ type: array
142
+ description: "Field types that create dependencies"
143
+ items:
144
+ type: string
145
+ default:
146
+ - "ForeignKey"
147
+ - "OneToOneField"
148
+ - "ManyToManyField"
149
+
150
+ explanation:
151
+ type: string
152
+ description: "Why explicit dependencies are required"
153
+ default: |
154
+ When a migration in app A creates a FK to app B, it must
155
+ explicitly depend on app B's latest migration. This ensures
156
+ migrations run in the correct order and prevents dependency
157
+ errors in fresh databases.
158
+
159
+ enforcement:
160
+ type: object
161
+ description: "How these rules are enforced"
162
+ properties:
163
+ pre_commit_checks:
164
+ type: boolean
165
+ description: "Whether checks run pre-commit"
166
+ default: true
167
+
168
+ ci_checks:
169
+ type: boolean
170
+ description: "Whether checks run in CI"
171
+ default: true
172
+
173
+ scripts:
174
+ type: object
175
+ description: "Scripts that enforce these rules"
176
+ properties:
177
+ guard_migrate_commands:
178
+ type: string
179
+ description: "Script that guards against forbidden migrate commands"
180
+ default: "scripts/guard_migrate_commands.py"
181
+
182
+ check_cross_tenancy_fk:
183
+ type: string
184
+ description: "Script that checks for cross-tenancy FK violations"
185
+ default: "scripts/check_cross_tenancy_fk.py"
186
+
187
+ check_migration_dependencies:
188
+ type: string
189
+ description: "Script that checks migration dependency declarations"
190
+ default: "scripts/check_migration_dependencies.py"
191
+
192
+ make_target:
193
+ type: string
194
+ description: "Make target that runs all checks"
195
+ default: "check-schema-discipline"
196
+
197
+ examples:
198
+ - |
199
+ version: "1.0.0"
200
+ rules:
201
+ migration_commands:
202
+ forbidden_patterns:
203
+ - "manage.py migrate core"
204
+ - "manage.py migrate org"
205
+ required_patterns:
206
+ - "manage.py migrate"
207
+ - "manage.py migrate_schemas --shared"
208
+ - "manage.py migrate_schemas --tenant"
209
+ cross_tenancy_fk:
210
+ forbidden: true
211
+ allowed_exceptions: []
212
+ migration_dependencies:
213
+ require_explicit_dependencies: true
214
+ enforcement:
215
+ ci_checks: true
216
+ scripts:
217
+ guard_migrate_commands: "scripts/guard_migrate_commands.py"
218
+ check_cross_tenancy_fk: "scripts/check_cross_tenancy_fk.py"
219
+ check_migration_dependencies: "scripts/check_migration_dependencies.py"
220
+
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Lead Capture Module Content Contract
3
+ *
4
+ * Reusable lead capture form module for Heirloom Supply.
5
+ * Can be placed inline, in hero, footer, or as a modal.
6
+ */
7
+
8
+ import type { ContentContract } from "../types";
9
+
10
+ export const leadCaptureModuleContract: ContentContract = {
11
+ id: "lead-capture-module",
12
+ title: "Lead Capture Module",
13
+ type: "document",
14
+ description: "Reusable lead capture form module with configurable variants and messaging",
15
+ scope: {
16
+ tenants: ["hewn"],
17
+ },
18
+ fields: [
19
+ {
20
+ name: "title",
21
+ type: "string",
22
+ title: "Title",
23
+ description: "Internal title for this module (not displayed)",
24
+ required: true,
25
+ options: {
26
+ maxLength: 200,
27
+ },
28
+ },
29
+ {
30
+ name: "slug",
31
+ type: "slug",
32
+ title: "Slug",
33
+ description: "Unique identifier for this module (used for tracking)",
34
+ required: true,
35
+ options: {
36
+ source: "title",
37
+ },
38
+ },
39
+ {
40
+ name: "variant",
41
+ type: "string",
42
+ title: "Variant",
43
+ description: "Visual variant/style of the form",
44
+ required: true,
45
+ options: {
46
+ options: ["inline", "hero", "footer", "modal"],
47
+ },
48
+ },
49
+ {
50
+ name: "headline",
51
+ type: "string",
52
+ title: "Headline",
53
+ description: "Main headline text",
54
+ required: true,
55
+ options: {
56
+ maxLength: 100,
57
+ },
58
+ },
59
+ {
60
+ name: "subcopy",
61
+ type: "text",
62
+ title: "Subcopy",
63
+ description: "Supporting text below headline",
64
+ required: false,
65
+ },
66
+ {
67
+ name: "ctaText",
68
+ type: "string",
69
+ title: "CTA Text",
70
+ description: "Button text (e.g., 'Subscribe', 'Get Started')",
71
+ required: true,
72
+ options: {
73
+ maxLength: 50,
74
+ },
75
+ },
76
+ {
77
+ name: "disclaimer",
78
+ type: "text",
79
+ title: "Disclaimer",
80
+ description: "Optional disclaimer text (privacy, terms, etc.)",
81
+ required: false,
82
+ },
83
+ {
84
+ name: "listId",
85
+ type: "string",
86
+ title: "List ID",
87
+ description: "Optional email list ID for segmentation",
88
+ required: false,
89
+ },
90
+ {
91
+ name: "tag",
92
+ type: "string",
93
+ title: "Tag",
94
+ description: "Optional tag for lead categorization",
95
+ required: false,
96
+ },
97
+ {
98
+ name: "successMessage",
99
+ type: "string",
100
+ title: "Success Message",
101
+ description: "Message shown after successful submission",
102
+ required: true,
103
+ options: {
104
+ maxLength: 200,
105
+ },
106
+ },
107
+ {
108
+ name: "designOverrides",
109
+ type: "object",
110
+ title: "Design Overrides",
111
+ description: "Optional design system overrides (colors, spacing, etc.)",
112
+ required: false,
113
+ },
114
+ ],
115
+ preview: {
116
+ select: {
117
+ title: "title",
118
+ variant: "variant",
119
+ slug: "slug.current",
120
+ },
121
+ prepare: (selection) => ({
122
+ title: selection.title,
123
+ subtitle: `${selection.variant} • ${selection.slug || "no slug"}`,
124
+ }),
125
+ },
126
+ };
127
+
128
+ export const marketingSettingsContract: ContentContract = {
129
+ id: "marketing-settings",
130
+ title: "Marketing Settings",
131
+ type: "document",
132
+ description: "Global marketing settings including lead capture placements",
133
+ scope: {
134
+ tenants: ["hewn"],
135
+ },
136
+ fields: [
137
+ {
138
+ name: "globalLeadModule",
139
+ type: "reference",
140
+ title: "Global Lead Module",
141
+ description: "Default lead capture module for global placements",
142
+ required: false,
143
+ options: {
144
+ to: ["lead-capture-module"],
145
+ },
146
+ },
147
+ {
148
+ name: "showHeaderBar",
149
+ type: "boolean",
150
+ title: "Show Header Bar",
151
+ description: "Display lead capture in header bar",
152
+ required: false,
153
+ },
154
+ {
155
+ name: "showExitModal",
156
+ type: "boolean",
157
+ title: "Show Exit Modal",
158
+ description: "Display lead capture modal on exit intent",
159
+ required: false,
160
+ },
161
+ {
162
+ name: "exitModalModule",
163
+ type: "reference",
164
+ title: "Exit Modal Module",
165
+ description: "Lead capture module to show in exit modal",
166
+ required: false,
167
+ options: {
168
+ to: ["lead-capture-module"],
169
+ },
170
+ },
171
+ ],
172
+ preview: {
173
+ select: {
174
+ showHeader: "showHeaderBar",
175
+ showExit: "showExitModal",
176
+ },
177
+ prepare: (selection) => ({
178
+ title: "Marketing Settings",
179
+ subtitle: `Header: ${selection.showHeader ? "On" : "Off"} • Exit Modal: ${selection.showExit ? "On" : "Off"}`,
180
+ }),
181
+ },
182
+ };
183
+
184
+ export const moduleRefBlockContract: ContentContract = {
185
+ id: "module-ref-block",
186
+ title: "Module Reference Block",
187
+ type: "object",
188
+ description: "Block type for referencing a lead capture module in page builder",
189
+ scope: {
190
+ tenants: ["hewn"],
191
+ },
192
+ fields: [
193
+ {
194
+ name: "module",
195
+ type: "reference",
196
+ title: "Module",
197
+ description: "Lead capture module to render",
198
+ required: true,
199
+ options: {
200
+ to: ["lead-capture-module"],
201
+ },
202
+ },
203
+ {
204
+ name: "anchorId",
205
+ type: "string",
206
+ title: "Anchor ID",
207
+ description: "Optional anchor ID for deep linking",
208
+ required: false,
209
+ },
210
+ ],
211
+ };
212
+
@@ -18,6 +18,11 @@
18
18
  import type { ContentContract } from "./types";
19
19
  import { blogPostContract, heroSectionContract } from "./example";
20
20
  import { productContract } from "./heirloom/product";
21
+ import {
22
+ leadCaptureModuleContract,
23
+ marketingSettingsContract,
24
+ moduleRefBlockContract,
25
+ } from "./heirloom/lead-capture";
21
26
  import { artistContract } from "./studioops/artist";
22
27
 
23
28
  /**
@@ -35,6 +40,9 @@ export const contentContracts: ContentContract[] = [
35
40
 
36
41
  // Heirloom Supply contracts
37
42
  productContract,
43
+ leadCaptureModuleContract,
44
+ marketingSettingsContract,
45
+ moduleRefBlockContract,
38
46
 
39
47
  // StudioOps contracts
40
48
  artistContract,
@@ -0,0 +1,21 @@
1
+ # Creative Whisperer Contracts
2
+
3
+ JSON schemas for Creative Whisperer synthesis—extracting and synthesizing creative signals from artist assets.
4
+
5
+ ## Schemas
6
+
7
+ | Schema | Description |
8
+ |--------|-------------|
9
+ | `creative_signal_profile.schema.json` | Extracted profile from a single asset (emotions, themes, energy, intensity, etc.) |
10
+ | `creative_edge_alignment.schema.json` | Alignment metrics between two profiles (semantic, emotional, theme overlap) |
11
+ | `creative_cluster.schema.json` | Cluster of related profiles grouped by affinity |
12
+ | `creative_whisper.schema.json` | Synthesis output (cluster summary, patterns, citations, follow-up questions) |
13
+
14
+ ## Enums
15
+
16
+ - **asset_type**: `note` | `audio` | `url` | `lyric` | `image` | `other`
17
+ - **energy**: `low` | `mid` | `high`
18
+
19
+ ## References
20
+
21
+ - `$id` base: `https://contracts.levrops.com/creative/`
@@ -0,0 +1,44 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.levrops.com/creative/creative_cluster.schema.json",
4
+ "title": "CreativeCluster",
5
+ "description": "A cluster of related creative signal profiles grouped by semantic/emotional affinity for Creative Whisperer synthesis.",
6
+ "type": "object",
7
+ "required": ["cluster_id", "asset_ids"],
8
+ "properties": {
9
+ "cluster_id": {
10
+ "type": "string",
11
+ "description": "Unique identifier for the cluster",
12
+ "minLength": 1,
13
+ "maxLength": 255
14
+ },
15
+ "asset_ids": {
16
+ "type": "array",
17
+ "description": "IDs of assets in this cluster",
18
+ "items": { "type": "string", "minLength": 1, "maxLength": 255 },
19
+ "minItems": 1,
20
+ "maxItems": 100
21
+ },
22
+ "dominant_emotions": {
23
+ "type": "array",
24
+ "description": "Dominant emotions across the cluster",
25
+ "items": { "type": "string", "maxLength": 100 },
26
+ "maxItems": 10,
27
+ "default": []
28
+ },
29
+ "dominant_themes": {
30
+ "type": "array",
31
+ "description": "Dominant themes across the cluster",
32
+ "items": { "type": "string", "maxLength": 100 },
33
+ "maxItems": 10,
34
+ "default": []
35
+ },
36
+ "cohesion_score": {
37
+ "type": "number",
38
+ "description": "How tightly the cluster coheres (0–1)",
39
+ "minimum": 0,
40
+ "maximum": 1
41
+ }
42
+ },
43
+ "additionalProperties": false
44
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.levrops.com/creative/creative_edge_alignment.schema.json",
4
+ "title": "CreativeEdgeAlignment",
5
+ "description": "Alignment metrics between two creative signal profiles. Used to measure semantic, emotional, and thematic overlap for Creative Whisperer synthesis.",
6
+ "type": "object",
7
+ "required": [
8
+ "source_asset_id",
9
+ "target_asset_id",
10
+ "semantic_score",
11
+ "emotional_score",
12
+ "theme_overlap_count",
13
+ "motif_overlap_count",
14
+ "resonance_score"
15
+ ],
16
+ "properties": {
17
+ "source_asset_id": {
18
+ "type": "string",
19
+ "description": "ID of the source asset",
20
+ "minLength": 1,
21
+ "maxLength": 255
22
+ },
23
+ "target_asset_id": {
24
+ "type": "string",
25
+ "description": "ID of the target asset being aligned",
26
+ "minLength": 1,
27
+ "maxLength": 255
28
+ },
29
+ "semantic_score": {
30
+ "type": "number",
31
+ "description": "Semantic similarity score (0–1)",
32
+ "minimum": 0,
33
+ "maximum": 1
34
+ },
35
+ "emotional_score": {
36
+ "type": "number",
37
+ "description": "Emotional alignment score (0–1)",
38
+ "minimum": 0,
39
+ "maximum": 1
40
+ },
41
+ "theme_overlap_count": {
42
+ "type": "integer",
43
+ "description": "Number of overlapping themes",
44
+ "minimum": 0
45
+ },
46
+ "motif_overlap_count": {
47
+ "type": "integer",
48
+ "description": "Number of overlapping motifs/imagery",
49
+ "minimum": 0
50
+ },
51
+ "resonance_score": {
52
+ "type": "number",
53
+ "description": "Overall resonance score (0–1)",
54
+ "minimum": 0,
55
+ "maximum": 1
56
+ }
57
+ },
58
+ "additionalProperties": false
59
+ }