@adobe/spacecat-shared-utils 1.53.0 → 1.55.0
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/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/llmo-config.js +2 -0
- package/src/schemas.js +58 -42
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-utils-v1.55.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.54.0...@adobe/spacecat-shared-utils-v1.55.0) (2025-10-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add topics with nested prompts + make categories and prompts top level entities ([#999](https://github.com/adobe/spacecat-shared/issues/999)) ([1b43970](https://github.com/adobe/spacecat-shared/commit/1b43970912478361b2eb4eaf1e7f173e77ad80e9))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-utils-v1.54.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.53.0...@adobe/spacecat-shared-utils-v1.54.0) (2025-10-02)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* remove topic from brand alias and make regions mandatory in categories + make regions mandatory for categories ([#997](https://github.com/adobe/spacecat-shared/issues/997)) ([78e609a](https://github.com/adobe/spacecat-shared/commit/78e609a336cb2a2645199d94d4464c4843bd8c4a))
|
|
14
|
+
|
|
1
15
|
# [@adobe/spacecat-shared-utils-v1.53.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.52.0...@adobe/spacecat-shared-utils-v1.53.0) (2025-10-02)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
package/src/llmo-config.js
CHANGED
package/src/schemas.js
CHANGED
|
@@ -38,21 +38,39 @@ const nonEmptyString = z.string().min(1);
|
|
|
38
38
|
|
|
39
39
|
const region = z.string().length(2).regex(/^[a-z][a-z]$/i);
|
|
40
40
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
z.
|
|
45
|
-
])
|
|
41
|
+
const prompt = z.object({
|
|
42
|
+
prompt: nonEmptyString,
|
|
43
|
+
regions: z.array(region),
|
|
44
|
+
origin: z.union([z.literal('human'), z.literal('ai'), z.string()]),
|
|
45
|
+
source: z.union([z.literal('config'), z.literal('api'), z.string()]),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const entity = z.object({
|
|
49
|
+
type: nonEmptyString,
|
|
50
|
+
name: nonEmptyString,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const category = z.object({
|
|
54
|
+
name: nonEmptyString,
|
|
55
|
+
region: z.union([region, z.array(region)]),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const topic = z.object({
|
|
59
|
+
name: nonEmptyString,
|
|
60
|
+
prompts: z.array(prompt).min(1),
|
|
61
|
+
category: z.union([z.uuid(), nonEmptyString]),
|
|
62
|
+
});
|
|
46
63
|
|
|
47
64
|
export const llmoConfig = z.object({
|
|
48
65
|
entities: z.record(z.uuid(), entity),
|
|
66
|
+
categories: z.record(z.uuid(), category),
|
|
67
|
+
topics: z.record(z.uuid(), topic),
|
|
49
68
|
brands: z.object({
|
|
50
69
|
aliases: z.array(
|
|
51
70
|
z.object({
|
|
52
71
|
aliases: z.array(nonEmptyString),
|
|
53
72
|
category: z.uuid(),
|
|
54
73
|
region: z.union([region, z.array(region)]),
|
|
55
|
-
topic: z.uuid(),
|
|
56
74
|
}),
|
|
57
75
|
),
|
|
58
76
|
}),
|
|
@@ -68,75 +86,73 @@ export const llmoConfig = z.object({
|
|
|
68
86
|
),
|
|
69
87
|
}),
|
|
70
88
|
}).superRefine((value, ctx) => {
|
|
71
|
-
const {
|
|
89
|
+
const {
|
|
90
|
+
categories, topics, brands, competitors,
|
|
91
|
+
} = value;
|
|
72
92
|
|
|
73
93
|
brands.aliases.forEach((alias, index) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
ensureRegionCompatibility(entities, ctx, alias.category, alias.region, ['brands', 'aliases', index, 'region'], 'brand alias');
|
|
94
|
+
ensureCategoryExists(categories, ctx, alias.category, ['brands', 'aliases', index, 'category']);
|
|
95
|
+
ensureRegionCompatibility(categories, ctx, alias.category, alias.region, ['brands', 'aliases', index, 'region'], 'brand alias');
|
|
77
96
|
});
|
|
78
97
|
|
|
79
98
|
competitors.competitors.forEach((competitor, index) => {
|
|
80
|
-
|
|
81
|
-
ensureRegionCompatibility(
|
|
99
|
+
ensureCategoryExists(categories, ctx, competitor.category, ['competitors', 'competitors', index, 'category']);
|
|
100
|
+
ensureRegionCompatibility(categories, ctx, competitor.category, competitor.region, ['competitors', 'competitors', index, 'region'], 'competitor');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Validate topic prompts regions against their category
|
|
104
|
+
Object.entries(topics).forEach(([topicId, topicEntity]) => {
|
|
105
|
+
if (topicEntity.prompts && topicEntity.category) {
|
|
106
|
+
// If category is a UUID, validate against the referenced category entity
|
|
107
|
+
if (topicEntity.category.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) {
|
|
108
|
+
topicEntity.prompts.forEach((promptItem, promptIndex) => {
|
|
109
|
+
ensureRegionCompatibility(
|
|
110
|
+
categories,
|
|
111
|
+
ctx,
|
|
112
|
+
topicEntity.category,
|
|
113
|
+
promptItem.regions,
|
|
114
|
+
['topics', topicId, 'prompts', promptIndex, 'regions'],
|
|
115
|
+
'topic prompt',
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
82
120
|
});
|
|
83
121
|
});
|
|
84
122
|
|
|
85
123
|
/**
|
|
86
|
-
* @param {LLMOConfig['
|
|
124
|
+
* @param {LLMOConfig['categories']} categories
|
|
87
125
|
* @param {z.RefinementCtx} ctx
|
|
88
126
|
* @param {string} id
|
|
89
|
-
* @param {string} expectedType
|
|
90
127
|
* @param {Array<number | string>} path
|
|
91
|
-
* @param {string} refLabel
|
|
92
128
|
*/
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
if (!entityValue) {
|
|
129
|
+
function ensureCategoryExists(categories, ctx, id, path) {
|
|
130
|
+
if (!categories[id]) {
|
|
96
131
|
ctx.addIssue({
|
|
97
132
|
code: 'custom',
|
|
98
133
|
path,
|
|
99
|
-
message: `
|
|
100
|
-
});
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (entityValue.type !== expectedType) {
|
|
105
|
-
ctx.addIssue({
|
|
106
|
-
code: 'custom',
|
|
107
|
-
path,
|
|
108
|
-
message: `Entity ${id} referenced as ${refLabel} must have type "${expectedType}" but was "${entityValue.type}"`,
|
|
134
|
+
message: `Category ${id} does not exist`,
|
|
109
135
|
});
|
|
110
136
|
}
|
|
111
137
|
}
|
|
112
138
|
|
|
113
139
|
/**
|
|
114
|
-
* @param {LLMOConfig['
|
|
140
|
+
* @param {LLMOConfig['categories']} categories
|
|
115
141
|
* @param {z.RefinementCtx} ctx
|
|
116
142
|
* @param {string} categoryId
|
|
117
143
|
* @param {string | string[]} itemRegion
|
|
118
144
|
* @param {Array<number | string>} path
|
|
119
145
|
* @param {string} itemLabel
|
|
120
146
|
*/
|
|
121
|
-
function ensureRegionCompatibility(
|
|
122
|
-
const categoryEntity =
|
|
147
|
+
function ensureRegionCompatibility(categories, ctx, categoryId, itemRegion, path, itemLabel) {
|
|
148
|
+
const categoryEntity = categories[categoryId];
|
|
123
149
|
if (!categoryEntity) {
|
|
124
|
-
// Category validation is handled by
|
|
150
|
+
// Category validation is handled by ensureCategoryExists
|
|
125
151
|
return;
|
|
126
152
|
}
|
|
127
153
|
|
|
128
154
|
const categoryRegions = categoryEntity.region;
|
|
129
155
|
|
|
130
|
-
// If category has no regions defined, item should not have regions
|
|
131
|
-
if (!categoryRegions) {
|
|
132
|
-
ctx.addIssue({
|
|
133
|
-
code: 'custom',
|
|
134
|
-
path,
|
|
135
|
-
message: `${itemLabel} cannot have regions when the referenced category has no regions defined`,
|
|
136
|
-
});
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
156
|
// Normalize regions to arrays for comparison
|
|
141
157
|
const categoryRegionArray = Array.isArray(categoryRegions) ? categoryRegions : [categoryRegions];
|
|
142
158
|
const itemRegionArray = Array.isArray(itemRegion) ? itemRegion : [itemRegion];
|