@zodic/shared 0.0.225 → 0.0.227

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.
@@ -2,7 +2,7 @@ import {
2
2
  KVNamespaceListKey,
3
3
  KVNamespaceListResult,
4
4
  } from '@cloudflare/workers-types';
5
- import { and, eq, sql } from 'drizzle-orm';
5
+ import { and, eq, inArray, sql } from 'drizzle-orm';
6
6
  import { drizzle } from 'drizzle-orm/d1';
7
7
  import { inject, injectable } from 'inversify';
8
8
  import 'reflect-metadata';
@@ -377,22 +377,52 @@ export class ConceptService {
377
377
  async generateContent(
378
378
  conceptSlug: Concept,
379
379
  combinationString: string,
380
- override: boolean = false // ✅ New optional override parameter
380
+ override: boolean = false // ✅ Optional override flag
381
381
  ): Promise<void> {
382
382
  console.log(
383
383
  `🚀 Generating content for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`
384
384
  );
385
385
 
386
- const kvStore = this.context.kvConceptsStore();
387
- const kvFailuresStore = this.context.kvConceptFailuresStore(); // 🔴 Replace with actual KV store
388
- const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
389
- const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
390
- const failureKey = `failures:content:${conceptSlug}:${combinationString}`;
386
+ const db = drizzle(this.context.env.DB); // ✅ Initialize Drizzle with Cloudflare D1
391
387
 
392
- // ✅ Ensure basic info is available before generating content
393
- const conceptEN = await this.getKVConcept(kvKeyEN);
394
- const conceptPT = await this.getKVConcept(kvKeyPT);
388
+ // ✅ Fetch concept data from D1
389
+ console.log(
390
+ `📡 Fetching concept data for ${conceptSlug}:${combinationString}`
391
+ );
392
+
393
+ const conceptEntries = await db
394
+ .select({
395
+ id: conceptsData.id,
396
+ language: conceptsData.language,
397
+ name: conceptsData.name,
398
+ description: conceptsData.description,
399
+ poem: conceptsData.poem,
400
+ content: conceptsData.content,
401
+ })
402
+ .from(conceptsData)
403
+ .where(
404
+ and(
405
+ eq(conceptsData.conceptSlug, conceptSlug),
406
+ eq(conceptsData.combination, combinationString),
407
+ inArray(conceptsData.language, ['en-us', 'pt-br']) // ✅ Fetch both languages
408
+ )
409
+ )
410
+ .execute();
411
+
412
+ if (conceptEntries.length !== 2) {
413
+ throw new Error(
414
+ `❌ Missing concept data for ${conceptSlug}:${combinationString}. Ensure basic info is populated first.`
415
+ );
416
+ }
417
+
418
+ const conceptEN = conceptEntries.find((c) => c.language === 'en-us');
419
+ const conceptPT = conceptEntries.find((c) => c.language === 'pt-br');
420
+
421
+ if (!conceptEN || !conceptPT) {
422
+ throw new Error(`❌ Could not find both EN and PT versions.`);
423
+ }
395
424
 
425
+ // ✅ Ensure basic info is available before generating content
396
426
  if (
397
427
  !conceptEN.name ||
398
428
  !conceptEN.description ||
@@ -408,8 +438,8 @@ export class ConceptService {
408
438
 
409
439
  if (
410
440
  !override &&
411
- conceptEN.content?.length > 0 &&
412
- conceptPT.content?.length > 0
441
+ conceptEN?.content!.length > 0 &&
442
+ conceptPT?.content!.length > 0
413
443
  ) {
414
444
  console.log(`⚡ Content already exists for ${conceptSlug}, skipping.`);
415
445
  return; // ✅ Skip regeneration
@@ -432,7 +462,6 @@ export class ConceptService {
432
462
  combination: combinationString,
433
463
  name: conceptEN.name, // Use English name since both languages match in meaning
434
464
  description: conceptEN.description,
435
- poem: conceptEN.poem,
436
465
  });
437
466
 
438
467
  // ✅ Call ChatGPT API
@@ -447,18 +476,39 @@ export class ConceptService {
447
476
 
448
477
  // ✅ Parse structured content for both languages
449
478
  const { structuredContentEN, structuredContentPT } =
450
- this.parseStructuredContent(response);
479
+ this.parseStructuredContent(conceptSlug, response);
451
480
 
452
- // 🌍 Store English content
453
- Object.assign(conceptEN, { content: structuredContentEN });
454
- await kvStore.put(kvKeyEN, JSON.stringify(conceptEN));
481
+ // Store content in D1
482
+ console.log(
483
+ `💾 Storing structured content for ${conceptSlug}:${combinationString} in D1...`
484
+ );
455
485
 
456
- // 🇧🇷 Store Portuguese content
457
- Object.assign(conceptPT, { content: structuredContentPT });
458
- await kvStore.put(kvKeyPT, JSON.stringify(conceptPT));
486
+ await db.transaction(async (tx) => {
487
+ await tx
488
+ .update(conceptsData)
489
+ .set({ content: JSON.stringify(structuredContentEN) })
490
+ .where(
491
+ and(
492
+ eq(conceptsData.id, conceptEN.id),
493
+ eq(conceptsData.language, 'en-us')
494
+ )
495
+ )
496
+ .execute();
497
+
498
+ await tx
499
+ .update(conceptsData)
500
+ .set({ content: JSON.stringify(structuredContentPT) })
501
+ .where(
502
+ and(
503
+ eq(conceptsData.id, conceptPT.id),
504
+ eq(conceptsData.language, 'pt-br')
505
+ )
506
+ )
507
+ .execute();
508
+ });
459
509
 
460
510
  console.log(
461
- `✅ Structured content stored for ${conceptSlug}, combination: ${combinationString}, in both languages.`
511
+ `✅ Structured content stored successfully for ${conceptSlug}:${combinationString}.`
462
512
  );
463
513
  return; // ✅ Exit loop if successful
464
514
  } catch (error) {
@@ -468,7 +518,9 @@ export class ConceptService {
468
518
  );
469
519
 
470
520
  // ✅ Store failure details in KV for manual review
471
- await kvFailuresStore.put(
521
+ const failureKey = `failures:content:${conceptSlug}:${combinationString}`;
522
+
523
+ await this.context.kvConceptFailuresStore().put(
472
524
  failureKey,
473
525
  JSON.stringify({
474
526
  error: (error as Error).message,
@@ -495,11 +547,16 @@ export class ConceptService {
495
547
  }
496
548
  }
497
549
 
498
- private parseStructuredContent(response: string): {
550
+ private parseStructuredContent(
551
+ conceptSlug: Concept,
552
+ response: string
553
+ ): {
499
554
  structuredContentEN: StructuredConceptContent;
500
555
  structuredContentPT: StructuredConceptContent;
501
556
  } {
502
- console.log('📌 [START] Parsing structured content from ChatGPT response.');
557
+ console.log(
558
+ `📌 [START] Parsing structured content for ${conceptSlug} from ChatGPT response.`
559
+ );
503
560
 
504
561
  // ✅ Step 1: Clean artifacts before processing
505
562
  let cleanedResponse = response
@@ -513,23 +570,114 @@ export class ConceptService {
513
570
  cleanedResponse.slice(0, 500) + '...'
514
571
  );
515
572
 
516
- const sectionsEN = [
517
- 'Core Identity',
518
- 'Strengths and Challenges',
519
- 'Path to Fulfillment',
520
- 'Emotional Depth',
521
- 'Vision and Aspirations',
522
- ];
523
-
524
- const sectionsPT = [
525
- 'Identidade Essencial',
526
- 'Forças e Desafios',
527
- 'Caminho para a Plenitude',
528
- 'Profundidade Emocional',
529
- 'Visão e Aspirações',
530
- ];
531
-
532
- // ✅ Step 2: Extract English and Portuguese content separately
573
+ // Step 2: Define Section Titles Per Concept
574
+ const conceptSections: Record<string, { en: string[]; pt: string[] }> = {
575
+ crown: {
576
+ en: [
577
+ 'Core Identity',
578
+ 'Strengths and Challenges',
579
+ 'Path to Fulfillment',
580
+ 'Emotional Depth',
581
+ 'Vision and Aspirations',
582
+ ],
583
+ pt: [
584
+ 'Identidade Essencial',
585
+ 'Forças e Desafios',
586
+ 'Caminho para a Plenitude',
587
+ 'Profundidade Emocional',
588
+ 'Visão e Aspirações',
589
+ ],
590
+ },
591
+ scepter: {
592
+ en: [
593
+ 'The Power of Expression',
594
+ 'The Dance of Action and Reaction',
595
+ 'Navigating Challenges',
596
+ 'The Mirror of Relationships',
597
+ 'Impact on the World',
598
+ ],
599
+ pt: [
600
+ 'O Poder da Expressão',
601
+ 'A Dança da Ação e Reação',
602
+ 'Navegando Desafios',
603
+ 'O Espelho dos Relacionamentos',
604
+ 'Impacto no Mundo',
605
+ ],
606
+ },
607
+ amulet: {
608
+ en: [
609
+ 'The Shield of Protection',
610
+ 'The Strength Within',
611
+ 'Nurturing Growth',
612
+ 'The Cycle of Renewal',
613
+ 'The Anchor of Wisdom',
614
+ ],
615
+ pt: [
616
+ 'O Escudo da Proteção',
617
+ 'A Força Interior',
618
+ 'Cultivando o Crescimento',
619
+ 'O Ciclo da Renovação',
620
+ 'A Âncora da Sabedoria',
621
+ ],
622
+ },
623
+ ring: {
624
+ en: [
625
+ 'Magnetic Pull',
626
+ 'Hidden Desires',
627
+ 'The Fated Dance',
628
+ 'Unveiling the Shadows',
629
+ 'Your Love Power',
630
+ ],
631
+ pt: [
632
+ 'Força Magnética',
633
+ 'Desejos Ocultos',
634
+ 'A Dança do Destino',
635
+ 'Revelando as Sombras',
636
+ 'Seu Poder no Amor',
637
+ ],
638
+ },
639
+ lantern: {
640
+ en: [
641
+ 'The Call to Awakening',
642
+ 'Walking Through Shadows',
643
+ 'The Inner Flame',
644
+ 'Revelations and Truths',
645
+ 'Becoming the Light',
646
+ ],
647
+ pt: [
648
+ 'O Chamado para o Despertar',
649
+ 'Caminhando Pelas Sombras',
650
+ 'A Chama Interior',
651
+ 'Revelações e Verdades',
652
+ 'Tornando-se a Luz',
653
+ ],
654
+ },
655
+ orb: {
656
+ en: [
657
+ 'The Path Unfolding',
658
+ 'A Calling Beyond the Self',
659
+ 'Turning Points of Fate',
660
+ 'Mastering the Journey',
661
+ 'Legacy and Impact',
662
+ ],
663
+ pt: [
664
+ 'O Caminho que se Revela',
665
+ 'Um Chamado Além de Si Mesmo',
666
+ 'Pontos de Virada do Destino',
667
+ 'Dominando a Jornada',
668
+ 'Legado e Impacto',
669
+ ],
670
+ },
671
+ };
672
+
673
+ const sectionsEN = conceptSections[conceptSlug]?.en;
674
+ const sectionsPT = conceptSections[conceptSlug]?.pt;
675
+
676
+ if (!sectionsEN || !sectionsPT) {
677
+ throw new Error(`❌ Unknown concept: ${conceptSlug}`);
678
+ }
679
+
680
+ // ✅ Step 3: Extract English and Portuguese content separately
533
681
  const enMatches = cleanedResponse.match(/EN:\s*([\s\S]+?)\s*PT:/);
534
682
  const ptMatches = cleanedResponse.match(/PT:\s*([\s\S]+)/);
535
683
 
@@ -552,7 +700,7 @@ export class ConceptService {
552
700
  ptContent.slice(0, 500) + '...'
553
701
  );
554
702
 
555
- // ✅ Step 3: Extract structured sections
703
+ // ✅ Step 4: Extract structured sections
556
704
  function extractSections(text: string, sectionTitles: string[]) {
557
705
  return sectionTitles.map((title) => {
558
706
  console.log(`🔍 [PROCESSING] Extracting section: "${title}"`);
@@ -1034,27 +1182,29 @@ export class ConceptService {
1034
1182
  conceptSlug: Concept,
1035
1183
  maxEntries?: number
1036
1184
  ) {
1037
- console.log(`🔄 [START] Regenerating duplicate names for concept: ${conceptSlug}...`);
1185
+ console.log(
1186
+ `🔄 [START] Regenerating duplicate names for concept: ${conceptSlug}...`
1187
+ );
1038
1188
  const startTime = Date.now();
1039
-
1189
+
1040
1190
  const db = drizzle(this.context.env.DB);
1041
-
1191
+
1042
1192
  // ✅ Step 1: Fetch duplicate entries filtered by conceptSlug
1043
1193
  console.log(`📡 Fetching duplicate entries for concept: ${conceptSlug}...`);
1044
-
1194
+
1045
1195
  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`
1196
+ .select({
1197
+ id: conceptsData.id,
1198
+ name: conceptsData.name,
1199
+ description: conceptsData.description,
1200
+ combination: conceptsData.combination,
1201
+ })
1202
+ .from(conceptsData)
1203
+ .where(
1204
+ and(
1205
+ eq(conceptsData.language, 'en-us'), // ✅ English only
1206
+ eq(conceptsData.conceptSlug, conceptSlug), // ✅ Filter by concept
1207
+ sql`
1058
1208
  name IN (
1059
1209
  SELECT name
1060
1210
  FROM concepts_data
@@ -1063,19 +1213,24 @@ export class ConceptService {
1063
1213
  HAVING COUNT(*) > 1
1064
1214
  )
1065
1215
  `
1216
+ )
1066
1217
  )
1067
- )
1068
- .orderBy(conceptsData.name) // ✅ Order by name for better grouping
1069
- .limit(maxEntries || 50) // ✅ Apply optional limit
1070
- .execute();
1071
-
1218
+ .orderBy(conceptsData.name) // ✅ Order by name for better grouping
1219
+ .limit(maxEntries || 50) // ✅ Apply optional limit
1220
+ .execute();
1221
+
1072
1222
  if (!duplicateEntries.length) {
1073
1223
  console.log(`🎉 No duplicates found for ${conceptSlug}.`);
1074
- return { message: `No duplicates detected for ${conceptSlug}.` };
1224
+ return {
1225
+ message: `No duplicates detected for ${conceptSlug}.`,
1226
+ remainingDuplicates: 0,
1227
+ };
1075
1228
  }
1076
-
1077
- console.log(`🔍 Found ${duplicateEntries.length} duplicate entries to process.`);
1078
-
1229
+
1230
+ console.log(
1231
+ `🔍 Found ${duplicateEntries.length} duplicate entries to process.`
1232
+ );
1233
+
1079
1234
  // ✅ Group duplicates by name
1080
1235
  const duplicateGroups = new Map<string, typeof duplicateEntries>();
1081
1236
  for (const entry of duplicateEntries) {
@@ -1084,50 +1239,60 @@ export class ConceptService {
1084
1239
  }
1085
1240
  duplicateGroups.get(entry.name)!.push(entry);
1086
1241
  }
1087
-
1242
+
1088
1243
  let recentDuplicates = new Set<string>(); // ✅ Track newly generated names
1089
1244
  let updatedEntries = 0;
1090
1245
  let skippedEntries = 0;
1091
1246
  let failedEntries = 0;
1092
-
1247
+
1093
1248
  for (const [duplicateName, entries] of duplicateGroups.entries()) {
1094
- console.log(`🔄 Processing duplicate group: "${duplicateName}" with ${entries.length} occurrences`);
1095
-
1249
+ console.log(
1250
+ `🔄 Processing duplicate group: "${duplicateName}" with ${entries.length} occurrences`
1251
+ );
1252
+
1096
1253
  // ✅ Leave the first entry unchanged and only process the extras
1097
1254
  const [keepEntry, ...entriesToProcess] = entries;
1098
1255
  console.log(`✅ Keeping original name for ID: ${keepEntry.id}`);
1099
-
1256
+
1100
1257
  for (const entry of entriesToProcess) {
1101
1258
  const { id, description, combination } = entry;
1102
-
1259
+
1103
1260
  console.log(`🎭 Processing duplicate ID ${id}: "${duplicateName}"`);
1104
-
1261
+
1105
1262
  let newNameEN: string | null = null;
1106
1263
  let newNamePT: string | null = null;
1107
1264
  let attempts = 0;
1108
1265
  const maxAttempts = 3;
1109
-
1266
+
1110
1267
  while (attempts < maxAttempts) {
1111
1268
  attempts++;
1112
- console.log(`📡 Attempt ${attempts}/${maxAttempts} - Requesting AI for a new name...`);
1113
-
1269
+ console.log(
1270
+ `📡 Attempt ${attempts}/${maxAttempts} - Requesting AI for a new name...`
1271
+ );
1272
+
1114
1273
  // ✅ 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
-
1274
+ const messages = this.context
1275
+ .buildLLMMessages()
1276
+ .regenerateConceptName({
1277
+ conceptSlug,
1278
+ oldNameEN: duplicateName,
1279
+ description,
1280
+ recentNames: Array.from(recentDuplicates),
1281
+ });
1282
+
1283
+ const aiResponse = await this.context
1284
+ .api()
1285
+ .callTogether.single(messages, {});
1286
+
1124
1287
  if (!aiResponse) {
1125
- console.warn(`⚠️ AI failed to generate a new name for ID ${id}, skipping.`);
1288
+ console.warn(
1289
+ `⚠️ AI failed to generate a new name for ID ${id}, skipping.`
1290
+ );
1126
1291
  break;
1127
1292
  }
1128
-
1293
+
1129
1294
  console.log(`📝 Raw AI Response:\n${aiResponse}`);
1130
-
1295
+
1131
1296
  // ✅ Mapping for concept names with proper PT articles
1132
1297
  const conceptMapping = {
1133
1298
  amulet: { en: 'Amulet', pt: 'Amuleto', articlePT: 'O' },
@@ -1137,7 +1302,7 @@ export class ConceptService {
1137
1302
  ring: { en: 'Ring', pt: 'Anel', articlePT: 'O' },
1138
1303
  orb: { en: 'Orb', pt: 'Orbe', articlePT: 'O' },
1139
1304
  };
1140
-
1305
+
1141
1306
  const {
1142
1307
  en: conceptEN,
1143
1308
  pt: conceptPT,
@@ -1147,80 +1312,120 @@ export class ConceptService {
1147
1312
  pt: conceptSlug,
1148
1313
  articlePT: 'O', // Default to "O" if concept is missing
1149
1314
  };
1150
-
1315
+
1151
1316
  // ✅ 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
-
1317
+ const enMatch = aiResponse.match(
1318
+ new RegExp(`EN:\\s*The ${conceptEN} of (.+)`)
1319
+ );
1320
+ const ptMatch = aiResponse.match(
1321
+ new RegExp(`PT:\\s*${articlePT} ${conceptPT} de (.+)`)
1322
+ );
1323
+
1155
1324
  if (!enMatch || !ptMatch) {
1156
- console.error(`❌ Invalid AI response format for ID ${id}, retrying...`);
1325
+ console.error(
1326
+ `❌ Invalid AI response format for ID ${id}, retrying...`
1327
+ );
1157
1328
  continue;
1158
1329
  }
1159
-
1330
+
1160
1331
  newNameEN = `The ${conceptEN} of ${enMatch[1].trim()}`;
1161
1332
  newNamePT = `${articlePT} ${conceptPT} de ${ptMatch[1].trim()}`;
1162
-
1163
- console.log(`🎨 Extracted Names: EN - "${newNameEN}", PT - "${newNamePT}"`);
1164
-
1333
+
1334
+ console.log(
1335
+ `🎨 Extracted Names: EN - "${newNameEN}", PT - "${newNamePT}"`
1336
+ );
1337
+
1165
1338
  // ✅ Check if the new name is already in use in D1 before updating
1166
1339
  const existingEntry = await db
1167
1340
  .select({ id: conceptsData.id })
1168
1341
  .from(conceptsData)
1169
1342
  .where(eq(conceptsData.name, newNameEN))
1170
1343
  .execute();
1171
-
1344
+
1172
1345
  if (existingEntry.length > 0 || recentDuplicates.has(newNameEN)) {
1173
1346
  console.warn(`⚠️ Duplicate detected: "${newNameEN}", retrying...`);
1174
1347
  continue;
1175
1348
  }
1176
-
1349
+
1177
1350
  recentDuplicates.add(newNameEN);
1178
1351
  break; // ✅ Found a valid new name
1179
1352
  }
1180
-
1353
+
1181
1354
  if (!newNameEN || !newNamePT) {
1182
- console.error(`🚨 Failed to generate a unique name for ID ${id} after ${maxAttempts} attempts.`);
1355
+ console.error(
1356
+ `🚨 Failed to generate a unique name for ID ${id} after ${maxAttempts} attempts.`
1357
+ );
1183
1358
  failedEntries++;
1184
1359
  continue;
1185
1360
  }
1186
-
1187
- console.log(`✅ Updating names: EN - "${newNameEN}", PT - "${newNamePT}"`);
1188
-
1361
+
1362
+ console.log(
1363
+ `✅ Updating names: EN - "${newNameEN}", PT - "${newNamePT}"`
1364
+ );
1365
+
1189
1366
  // ✅ Update English name using the ID
1190
1367
  await db
1191
1368
  .update(conceptsData)
1192
1369
  .set({ name: newNameEN })
1193
- .where(and(eq(conceptsData.id, id), eq(conceptsData.language, 'en-us')))
1370
+ .where(
1371
+ and(eq(conceptsData.id, id), eq(conceptsData.language, 'en-us'))
1372
+ )
1194
1373
  .execute();
1195
-
1374
+
1196
1375
  // ✅ Convert English ID to Portuguese ID by replacing `:en-us:` with `:pt-br:`
1197
1376
  const ptId = id.replace(':en-us:', ':pt-br:');
1198
-
1377
+
1199
1378
  // ✅ Update Portuguese name using the derived PT ID
1200
1379
  await db
1201
1380
  .update(conceptsData)
1202
1381
  .set({ name: newNamePT })
1203
- .where(and(eq(conceptsData.id, ptId), eq(conceptsData.language, 'pt-br')))
1382
+ .where(
1383
+ and(eq(conceptsData.id, ptId), eq(conceptsData.language, 'pt-br'))
1384
+ )
1204
1385
  .execute();
1205
-
1206
- console.log(`📝 Updated IDs: EN (${id}) -> "${newNameEN}", PT (${ptId}) -> "${newNamePT}"`);
1207
-
1386
+
1387
+ console.log(
1388
+ `📝 Updated IDs: EN (${id}) -> "${newNameEN}", PT (${ptId}) -> "${newNamePT}"`
1389
+ );
1390
+
1208
1391
  updatedEntries++;
1209
1392
  }
1210
1393
  }
1211
-
1394
+
1212
1395
  const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(2);
1213
-
1396
+
1214
1397
  console.log(`🎉 [COMPLETE] Regeneration finished in ${elapsedTime}s.`);
1215
1398
  console.log(`✅ Successfully updated: ${updatedEntries}`);
1216
1399
  console.log(`⚠️ Skipped (already unique): ${skippedEntries}`);
1217
1400
  console.log(`🚨 Failed attempts: ${failedEntries}`);
1218
-
1401
+
1402
+ const remainingDuplicates = await db
1403
+ .select({ count: sql<number>`COUNT(*)` }) // ✅ Count only duplicates
1404
+ .from(conceptsData)
1405
+ .where(
1406
+ and(
1407
+ eq(conceptsData.conceptSlug, conceptSlug), // ✅ Filter by concept
1408
+ sql`name IN (
1409
+ SELECT name FROM concepts_data
1410
+ WHERE concept_slug = ${conceptSlug}
1411
+ GROUP BY name
1412
+ HAVING COUNT(*) > 1
1413
+ )`
1414
+ )
1415
+ )
1416
+ .execute()
1417
+ .then((res) => res[0]?.count || 0);
1418
+
1419
+ console.log(
1420
+ `🔄 Remaining duplicates for ${conceptSlug}: ${remainingDuplicates}`
1421
+ );
1422
+
1219
1423
  return {
1220
1424
  message: `Duplicate names processed and regenerated for ${conceptSlug}.`,
1221
1425
  updated: updatedEntries,
1222
1426
  skipped: skippedEntries,
1223
1427
  failed: failedEntries,
1428
+ remainingDuplicates,
1224
1429
  elapsedTime,
1225
1430
  };
1226
1431
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.225",
3
+ "version": "0.0.227",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -146,18 +146,15 @@ export const buildLLMMessages = (env: BackendBindings) => ({
146
146
  generateConceptContent: ({
147
147
  name,
148
148
  description,
149
- poem,
150
149
  combination,
151
150
  conceptSlug,
152
151
  }: {
153
152
  name: string;
154
153
  description: string;
155
- poem: string[];
156
154
  conceptSlug: Concept;
157
155
  combination: string;
158
156
  }): ChatMessages => {
159
157
  const zodiacCombination = mapConceptToPlanets(conceptSlug, combination);
160
- const formattedPoem = poem.map((line) => `${line}`).join('\n');
161
158
 
162
159
  return [
163
160
  {
@@ -171,8 +168,6 @@ export const buildLLMMessages = (env: BackendBindings) => ({
171
168
  - Combination: ${zodiacCombination}
172
169
  - Name: ${name}
173
170
  - Description: ${description}
174
- - Poem:
175
- ${formattedPoem}
176
171
 
177
172
  Generate additional content to elaborate on this concept for frontend display.
178
173
  `,
@@ -246,7 +241,11 @@ export const buildLLMMessages = (env: BackendBindings) => ({
246
241
  ];
247
242
  },
248
243
 
249
- translateENConceptName: ({ englishName }: { englishName: string }): ChatMessages => [
244
+ translateENConceptName: ({
245
+ englishName,
246
+ }: {
247
+ englishName: string;
248
+ }): ChatMessages => [
250
249
  {
251
250
  role: 'system',
252
251
  content: `