@zodic/shared 0.0.225 → 0.0.226

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.
@@ -1034,27 +1034,29 @@ export class ConceptService {
1034
1034
  conceptSlug: Concept,
1035
1035
  maxEntries?: number
1036
1036
  ) {
1037
- console.log(`🔄 [START] Regenerating duplicate names for concept: ${conceptSlug}...`);
1037
+ console.log(
1038
+ `🔄 [START] Regenerating duplicate names for concept: ${conceptSlug}...`
1039
+ );
1038
1040
  const startTime = Date.now();
1039
-
1041
+
1040
1042
  const db = drizzle(this.context.env.DB);
1041
-
1043
+
1042
1044
  // ✅ Step 1: Fetch duplicate entries filtered by conceptSlug
1043
1045
  console.log(`📡 Fetching duplicate entries for concept: ${conceptSlug}...`);
1044
-
1046
+
1045
1047
  const duplicateEntries = await db
1046
- .select({
1047
- id: conceptsData.id,
1048
- name: conceptsData.name,
1049
- description: conceptsData.description,
1050
- combination: conceptsData.combination,
1051
- })
1052
- .from(conceptsData)
1053
- .where(
1054
- and(
1055
- eq(conceptsData.language, 'en-us'), // ✅ English only
1056
- eq(conceptsData.conceptSlug, conceptSlug), // ✅ Filter by concept
1057
- sql`
1048
+ .select({
1049
+ id: conceptsData.id,
1050
+ name: conceptsData.name,
1051
+ description: conceptsData.description,
1052
+ combination: conceptsData.combination,
1053
+ })
1054
+ .from(conceptsData)
1055
+ .where(
1056
+ and(
1057
+ eq(conceptsData.language, 'en-us'), // ✅ English only
1058
+ eq(conceptsData.conceptSlug, conceptSlug), // ✅ Filter by concept
1059
+ sql`
1058
1060
  name IN (
1059
1061
  SELECT name
1060
1062
  FROM concepts_data
@@ -1063,19 +1065,24 @@ export class ConceptService {
1063
1065
  HAVING COUNT(*) > 1
1064
1066
  )
1065
1067
  `
1068
+ )
1066
1069
  )
1067
- )
1068
- .orderBy(conceptsData.name) // ✅ Order by name for better grouping
1069
- .limit(maxEntries || 50) // ✅ Apply optional limit
1070
- .execute();
1071
-
1070
+ .orderBy(conceptsData.name) // ✅ Order by name for better grouping
1071
+ .limit(maxEntries || 50) // ✅ Apply optional limit
1072
+ .execute();
1073
+
1072
1074
  if (!duplicateEntries.length) {
1073
1075
  console.log(`🎉 No duplicates found for ${conceptSlug}.`);
1074
- return { message: `No duplicates detected for ${conceptSlug}.` };
1076
+ return {
1077
+ message: `No duplicates detected for ${conceptSlug}.`,
1078
+ remainingDuplicates: 0,
1079
+ };
1075
1080
  }
1076
-
1077
- console.log(`🔍 Found ${duplicateEntries.length} duplicate entries to process.`);
1078
-
1081
+
1082
+ console.log(
1083
+ `🔍 Found ${duplicateEntries.length} duplicate entries to process.`
1084
+ );
1085
+
1079
1086
  // ✅ Group duplicates by name
1080
1087
  const duplicateGroups = new Map<string, typeof duplicateEntries>();
1081
1088
  for (const entry of duplicateEntries) {
@@ -1084,50 +1091,60 @@ export class ConceptService {
1084
1091
  }
1085
1092
  duplicateGroups.get(entry.name)!.push(entry);
1086
1093
  }
1087
-
1094
+
1088
1095
  let recentDuplicates = new Set<string>(); // ✅ Track newly generated names
1089
1096
  let updatedEntries = 0;
1090
1097
  let skippedEntries = 0;
1091
1098
  let failedEntries = 0;
1092
-
1099
+
1093
1100
  for (const [duplicateName, entries] of duplicateGroups.entries()) {
1094
- console.log(`🔄 Processing duplicate group: "${duplicateName}" with ${entries.length} occurrences`);
1095
-
1101
+ console.log(
1102
+ `🔄 Processing duplicate group: "${duplicateName}" with ${entries.length} occurrences`
1103
+ );
1104
+
1096
1105
  // ✅ Leave the first entry unchanged and only process the extras
1097
1106
  const [keepEntry, ...entriesToProcess] = entries;
1098
1107
  console.log(`✅ Keeping original name for ID: ${keepEntry.id}`);
1099
-
1108
+
1100
1109
  for (const entry of entriesToProcess) {
1101
1110
  const { id, description, combination } = entry;
1102
-
1111
+
1103
1112
  console.log(`🎭 Processing duplicate ID ${id}: "${duplicateName}"`);
1104
-
1113
+
1105
1114
  let newNameEN: string | null = null;
1106
1115
  let newNamePT: string | null = null;
1107
1116
  let attempts = 0;
1108
1117
  const maxAttempts = 3;
1109
-
1118
+
1110
1119
  while (attempts < maxAttempts) {
1111
1120
  attempts++;
1112
- console.log(`📡 Attempt ${attempts}/${maxAttempts} - Requesting AI for a new name...`);
1113
-
1121
+ console.log(
1122
+ `📡 Attempt ${attempts}/${maxAttempts} - Requesting AI for a new name...`
1123
+ );
1124
+
1114
1125
  // ✅ Generate a new name, passing dynamically tracked duplicates
1115
- const messages = this.context.buildLLMMessages().regenerateConceptName({
1116
- conceptSlug,
1117
- oldNameEN: duplicateName,
1118
- description,
1119
- recentNames: Array.from(recentDuplicates),
1120
- });
1121
-
1122
- const aiResponse = await this.context.api().callTogether.single(messages, {});
1123
-
1126
+ const messages = this.context
1127
+ .buildLLMMessages()
1128
+ .regenerateConceptName({
1129
+ conceptSlug,
1130
+ oldNameEN: duplicateName,
1131
+ description,
1132
+ recentNames: Array.from(recentDuplicates),
1133
+ });
1134
+
1135
+ const aiResponse = await this.context
1136
+ .api()
1137
+ .callTogether.single(messages, {});
1138
+
1124
1139
  if (!aiResponse) {
1125
- console.warn(`⚠️ AI failed to generate a new name for ID ${id}, skipping.`);
1140
+ console.warn(
1141
+ `⚠️ AI failed to generate a new name for ID ${id}, skipping.`
1142
+ );
1126
1143
  break;
1127
1144
  }
1128
-
1145
+
1129
1146
  console.log(`📝 Raw AI Response:\n${aiResponse}`);
1130
-
1147
+
1131
1148
  // ✅ Mapping for concept names with proper PT articles
1132
1149
  const conceptMapping = {
1133
1150
  amulet: { en: 'Amulet', pt: 'Amuleto', articlePT: 'O' },
@@ -1137,7 +1154,7 @@ export class ConceptService {
1137
1154
  ring: { en: 'Ring', pt: 'Anel', articlePT: 'O' },
1138
1155
  orb: { en: 'Orb', pt: 'Orbe', articlePT: 'O' },
1139
1156
  };
1140
-
1157
+
1141
1158
  const {
1142
1159
  en: conceptEN,
1143
1160
  pt: conceptPT,
@@ -1147,80 +1164,120 @@ export class ConceptService {
1147
1164
  pt: conceptSlug,
1148
1165
  articlePT: 'O', // Default to "O" if concept is missing
1149
1166
  };
1150
-
1167
+
1151
1168
  // ✅ Strict regex matching for English and Portuguese
1152
- const enMatch = aiResponse.match(new RegExp(`EN:\\s*The ${conceptEN} of (.+)`));
1153
- const ptMatch = aiResponse.match(new RegExp(`PT:\\s*${articlePT} ${conceptPT} de (.+)`));
1154
-
1169
+ const enMatch = aiResponse.match(
1170
+ new RegExp(`EN:\\s*The ${conceptEN} of (.+)`)
1171
+ );
1172
+ const ptMatch = aiResponse.match(
1173
+ new RegExp(`PT:\\s*${articlePT} ${conceptPT} de (.+)`)
1174
+ );
1175
+
1155
1176
  if (!enMatch || !ptMatch) {
1156
- console.error(`❌ Invalid AI response format for ID ${id}, retrying...`);
1177
+ console.error(
1178
+ `❌ Invalid AI response format for ID ${id}, retrying...`
1179
+ );
1157
1180
  continue;
1158
1181
  }
1159
-
1182
+
1160
1183
  newNameEN = `The ${conceptEN} of ${enMatch[1].trim()}`;
1161
1184
  newNamePT = `${articlePT} ${conceptPT} de ${ptMatch[1].trim()}`;
1162
-
1163
- console.log(`🎨 Extracted Names: EN - "${newNameEN}", PT - "${newNamePT}"`);
1164
-
1185
+
1186
+ console.log(
1187
+ `🎨 Extracted Names: EN - "${newNameEN}", PT - "${newNamePT}"`
1188
+ );
1189
+
1165
1190
  // ✅ Check if the new name is already in use in D1 before updating
1166
1191
  const existingEntry = await db
1167
1192
  .select({ id: conceptsData.id })
1168
1193
  .from(conceptsData)
1169
1194
  .where(eq(conceptsData.name, newNameEN))
1170
1195
  .execute();
1171
-
1196
+
1172
1197
  if (existingEntry.length > 0 || recentDuplicates.has(newNameEN)) {
1173
1198
  console.warn(`⚠️ Duplicate detected: "${newNameEN}", retrying...`);
1174
1199
  continue;
1175
1200
  }
1176
-
1201
+
1177
1202
  recentDuplicates.add(newNameEN);
1178
1203
  break; // ✅ Found a valid new name
1179
1204
  }
1180
-
1205
+
1181
1206
  if (!newNameEN || !newNamePT) {
1182
- console.error(`🚨 Failed to generate a unique name for ID ${id} after ${maxAttempts} attempts.`);
1207
+ console.error(
1208
+ `🚨 Failed to generate a unique name for ID ${id} after ${maxAttempts} attempts.`
1209
+ );
1183
1210
  failedEntries++;
1184
1211
  continue;
1185
1212
  }
1186
-
1187
- console.log(`✅ Updating names: EN - "${newNameEN}", PT - "${newNamePT}"`);
1188
-
1213
+
1214
+ console.log(
1215
+ `✅ Updating names: EN - "${newNameEN}", PT - "${newNamePT}"`
1216
+ );
1217
+
1189
1218
  // ✅ Update English name using the ID
1190
1219
  await db
1191
1220
  .update(conceptsData)
1192
1221
  .set({ name: newNameEN })
1193
- .where(and(eq(conceptsData.id, id), eq(conceptsData.language, 'en-us')))
1222
+ .where(
1223
+ and(eq(conceptsData.id, id), eq(conceptsData.language, 'en-us'))
1224
+ )
1194
1225
  .execute();
1195
-
1226
+
1196
1227
  // ✅ Convert English ID to Portuguese ID by replacing `:en-us:` with `:pt-br:`
1197
1228
  const ptId = id.replace(':en-us:', ':pt-br:');
1198
-
1229
+
1199
1230
  // ✅ Update Portuguese name using the derived PT ID
1200
1231
  await db
1201
1232
  .update(conceptsData)
1202
1233
  .set({ name: newNamePT })
1203
- .where(and(eq(conceptsData.id, ptId), eq(conceptsData.language, 'pt-br')))
1234
+ .where(
1235
+ and(eq(conceptsData.id, ptId), eq(conceptsData.language, 'pt-br'))
1236
+ )
1204
1237
  .execute();
1205
-
1206
- console.log(`📝 Updated IDs: EN (${id}) -> "${newNameEN}", PT (${ptId}) -> "${newNamePT}"`);
1207
-
1238
+
1239
+ console.log(
1240
+ `📝 Updated IDs: EN (${id}) -> "${newNameEN}", PT (${ptId}) -> "${newNamePT}"`
1241
+ );
1242
+
1208
1243
  updatedEntries++;
1209
1244
  }
1210
1245
  }
1211
-
1246
+
1212
1247
  const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(2);
1213
-
1248
+
1214
1249
  console.log(`🎉 [COMPLETE] Regeneration finished in ${elapsedTime}s.`);
1215
1250
  console.log(`✅ Successfully updated: ${updatedEntries}`);
1216
1251
  console.log(`⚠️ Skipped (already unique): ${skippedEntries}`);
1217
1252
  console.log(`🚨 Failed attempts: ${failedEntries}`);
1218
-
1253
+
1254
+ const remainingDuplicates = await db
1255
+ .select({ count: sql<number>`COUNT(*)` }) // ✅ Count only duplicates
1256
+ .from(conceptsData)
1257
+ .where(
1258
+ and(
1259
+ eq(conceptsData.conceptSlug, conceptSlug), // ✅ Filter by concept
1260
+ sql`name IN (
1261
+ SELECT name FROM concepts_data
1262
+ WHERE concept_slug = ${conceptSlug}
1263
+ GROUP BY name
1264
+ HAVING COUNT(*) > 1
1265
+ )`
1266
+ )
1267
+ )
1268
+ .execute()
1269
+ .then((res) => res[0]?.count || 0);
1270
+
1271
+ console.log(
1272
+ `🔄 Remaining duplicates for ${conceptSlug}: ${remainingDuplicates}`
1273
+ );
1274
+
1219
1275
  return {
1220
1276
  message: `Duplicate names processed and regenerated for ${conceptSlug}.`,
1221
1277
  updated: updatedEntries,
1222
1278
  skipped: skippedEntries,
1223
1279
  failed: failedEntries,
1280
+ remainingDuplicates,
1224
1281
  elapsedTime,
1225
1282
  };
1226
1283
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.225",
3
+ "version": "0.0.226",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {