@zodic/shared 0.0.307 → 0.0.309

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.
@@ -1,4 +1,3 @@
1
-
2
1
  import { and, eq, sql } from 'drizzle-orm';
3
2
  import { inject, injectable } from 'inversify';
4
3
  import { ChatMessages, Composition, schema } from '../..';
@@ -313,7 +312,8 @@ export class ArchetypeService {
313
312
  return;
314
313
  }
315
314
 
316
- const { english: englishNames, portuguese: portugueseVariants } = this.parseArchetypeNameBlocks(response);
315
+ const { english: englishNames, portuguese: portugueseVariants } =
316
+ this.parseArchetypeNameBlocks(response);
317
317
 
318
318
  async function isEnglishNameDuplicate(name: string): Promise<boolean> {
319
319
  const result = await db
@@ -610,7 +610,12 @@ export class ArchetypeService {
610
610
  return;
611
611
  }
612
612
 
613
- const blocks = response
613
+ // Clean up the response by removing unexpected suffixes
614
+ const cleanedResponse = response
615
+ .replace(/###|---\s*###|-\s*###/g, '')
616
+ .trim();
617
+
618
+ const blocks = cleanedResponse
614
619
  .split(/Composition \d+/)
615
620
  .slice(1)
616
621
  .map((b) => b.trim());
@@ -623,13 +628,13 @@ export class ArchetypeService {
623
628
 
624
629
  console.log(`🔄 [Batch] Processing: ${combination}`);
625
630
 
626
- let en = '';
627
- let pt = '';
628
- try {
629
- en = block.split('-EN')[1].split('-PT')[0].trim();
630
- pt = block.split('-PT')[1].trim();
631
- } catch (err) {
632
- console.error(`❌ [Batch] Parsing failed for: ${combination}`, err);
631
+ const { english, portuguese } = this.parseArchetypeNameBlocks(block);
632
+
633
+ if (
634
+ english.length !== indexes.length ||
635
+ portuguese.length !== indexes.length
636
+ ) {
637
+ console.error(`❌ [Batch] Parsing failed for: ${combination}`);
633
638
  await this.context
634
639
  .drizzle()
635
640
  .insert(schema.archetypeNameDumps)
@@ -643,37 +648,27 @@ export class ArchetypeService {
643
648
  continue;
644
649
  }
645
650
 
646
- const english = en
647
- .split(/\n\d\.\s*\n?/)
648
- .filter(Boolean)
649
- .map((line) => ({
650
- name: line.match(/• Name:\s*(.+)/)?.[1]?.trim() || '',
651
- essenceLine: line.match(/• Essence:\s*(.+)/)?.[1]?.trim() || '',
652
- }));
653
-
654
- const portuguese = pt
655
- .split(/\n\d\.\s*\n?/)
656
- .filter(Boolean)
657
- .map((line) => ({
658
- masc: line.match(/• Masculino:\s*(.+)/)?.[1]?.trim() || '',
659
- fem: line.match(/• Feminino:\s*(.+)/)?.[1]?.trim() || '',
660
- essenceLine: line.match(/• Essência:\s*(.+)/)?.[1]?.trim() || '',
661
- }));
662
-
663
651
  for (const index of indexes) {
664
- if (!english[index - 1] || !portuguese[index - 1]) {
665
- await this.context.drizzle().insert(schema.archetypeNameDumps).values({
666
- id: `${combination}:${index}:${Date.now()}`,
667
- combination,
668
- rawText: block,
669
- parsedSuccessfully: 0,
670
- createdAt: Date.now(),
671
- });
672
- console.warn(`⚠️ [Batch] Skipping index ${index} for ${combination} due to missing parsed block`);
652
+ const idx = index - 1;
653
+ const englishEntry = english[idx];
654
+ const ptEntry = portuguese[idx];
655
+
656
+ if (!englishEntry || !ptEntry) {
657
+ await this.context
658
+ .drizzle()
659
+ .insert(schema.archetypeNameDumps)
660
+ .values({
661
+ id: `${combination}:${index}:${Date.now()}`,
662
+ combination,
663
+ rawText: block,
664
+ parsedSuccessfully: 0,
665
+ createdAt: Date.now(),
666
+ });
667
+ console.warn(
668
+ `⚠️ [Batch] Skipping index ${index} for ${combination} due to missing parsed block`
669
+ );
673
670
  continue;
674
671
  }
675
- const englishEntry = english[index - 1];
676
- const ptEntry = portuguese[index - 1];
677
672
 
678
673
  for (const gender of ['male', 'female']) {
679
674
  const enId = `${combination}:${gender}:${index}`;
@@ -737,11 +732,188 @@ export class ArchetypeService {
737
732
  `🎉 [Batch] Completed regenerating ${compositions.length} combinations`
738
733
  );
739
734
  }
740
-
735
+
736
+ async recycleArchetypeNameDumpsBatch(
737
+ compositions: Composition[]
738
+ ): Promise<void> {
739
+ console.log('🔁 [Recycle] Starting recycling of archetype name dumps');
740
+
741
+ if (compositions.length === 0) {
742
+ console.log('✅ [Recycle] No compositions provided for recycling');
743
+ return;
744
+ }
745
+
746
+ console.log(
747
+ `📦 [Recycle] Total compositions to recycle: ${compositions.length}`
748
+ );
749
+
750
+ // Step 1: Process each composition
751
+ const db = this.context.drizzle();
752
+ for (const comp of compositions) {
753
+ const combination = [comp.sun, comp.ascendant, comp.moon].join('-');
754
+ const indexes = comp.indexesToGenerate ?? [1, 2, 3];
755
+
756
+ console.log(`🔄 [Recycle] Processing: ${combination}`);
757
+
758
+ // Fetch all dump entries for this combination
759
+ const dumpEntries = await db
760
+ .select({
761
+ id: schema.archetypeNameDumps.id,
762
+ rawText: schema.archetypeNameDumps.rawText,
763
+ })
764
+ .from(schema.archetypeNameDumps)
765
+ .where(eq(schema.archetypeNameDumps.combination, combination))
766
+ .execute();
767
+
768
+ if (dumpEntries.length === 0) {
769
+ console.log(
770
+ `⚠️ [Recycle] No dump entries found for combination: ${combination}`
771
+ );
772
+ continue;
773
+ }
774
+
775
+ // Step 2: Process each missing index
776
+ for (const index of indexes) {
777
+ // Find a dump entry that contains the specific index
778
+ let rawText: string | null = null;
779
+ for (const entry of dumpEntries) {
780
+ if (entry.rawText.includes(`-EN ${index}.`)) {
781
+ rawText = entry.rawText;
782
+ break;
783
+ }
784
+ }
785
+
786
+ if (!rawText) {
787
+ console.log(
788
+ `⚠️ [Recycle] No dump entry contains index ${index} for combination: ${combination}`
789
+ );
790
+ continue;
791
+ }
792
+
793
+ // Clean up the raw text
794
+ const cleanedText = rawText
795
+ .replace(/###|---\s*###|-\s*###/g, '')
796
+ .trim();
797
+
798
+ // Extract the specific index sections
799
+ const enSectionMatch = cleanedText.match(
800
+ new RegExp(`-EN ${index}\\.\\s*[^-]*(?=-EN|-PT|$)`, 's')
801
+ );
802
+ const ptSectionMatch = cleanedText.match(
803
+ new RegExp(`-PT ${index}\\.\\s*[^-]*(?=-EN|-PT|$)`, 's')
804
+ );
805
+
806
+ if (!enSectionMatch || !ptSectionMatch) {
807
+ console.log(
808
+ `⚠️ [Recycle] Could not extract index ${index} sections for combination: ${combination}`
809
+ );
810
+ continue;
811
+ }
812
+
813
+ // Combine the extracted sections into a block for parsing
814
+ const block = `-EN\n${enSectionMatch[0]
815
+ .replace(/-EN\s*/, '')
816
+ .trim()}\n\n-PT\n${ptSectionMatch[0].replace(/-PT\s*/, '').trim()}`;
817
+
818
+ // Step 3: Parse the block
819
+ const { english, portuguese } = this.parseArchetypeNameBlocks(block);
820
+
821
+ if (english.length === 0 || portuguese.length === 0) {
822
+ console.error(
823
+ `❌ [Recycle] Parsing failed for index ${index} of combination: ${combination}`
824
+ );
825
+ continue;
826
+ }
827
+
828
+ const englishEntry = english[0];
829
+ const ptEntry = portuguese[0];
830
+
831
+ if (!englishEntry || !ptEntry) {
832
+ console.warn(
833
+ `⚠️ [Recycle] Skipping index ${index} for ${combination} due to missing parsed data`
834
+ );
835
+ continue;
836
+ }
837
+
838
+ // Step 4: Insert the parsed data into archetypes_data
839
+ for (const gender of ['male', 'female']) {
840
+ const enId = `${combination}:${gender}:${index}`;
841
+ const ptId = `${combination}:${gender}:${index}:pt`;
842
+ const ptName = gender === 'female' ? ptEntry.fem : ptEntry.masc;
843
+
844
+ await this.context
845
+ .drizzle()
846
+ .insert(schema.archetypesData)
847
+ .values({
848
+ id: enId,
849
+ combination,
850
+ gender,
851
+ archetypeIndex: index.toString(),
852
+ language: 'en-us',
853
+ name: englishEntry.name,
854
+ essenceLine: englishEntry.essenceLine,
855
+ status: 'idle',
856
+ })
857
+ .onConflictDoUpdate({
858
+ target: [schema.archetypesData.id],
859
+ set: {
860
+ name: englishEntry.name,
861
+ essenceLine: englishEntry.essenceLine,
862
+ updatedAt: new Date().getTime(),
863
+ },
864
+ });
865
+
866
+ await this.context
867
+ .drizzle()
868
+ .insert(schema.archetypesData)
869
+ .values({
870
+ id: ptId,
871
+ combination,
872
+ gender,
873
+ archetypeIndex: index.toString(),
874
+ language: 'pt-br',
875
+ name: ptName,
876
+ essenceLine: ptEntry.essenceLine,
877
+ status: 'idle',
878
+ })
879
+ .onConflictDoUpdate({
880
+ target: [schema.archetypesData.id],
881
+ set: {
882
+ name: ptName,
883
+ essenceLine: ptEntry.essenceLine,
884
+ updatedAt: new Date().getTime(),
885
+ },
886
+ });
887
+
888
+ await db
889
+ .delete(schema.archetypeNameDumps)
890
+ .where(
891
+ eq(
892
+ schema.archetypeNameDumps.id,
893
+ dumpEntries.find((e) => e.rawText === rawText)?.id || ''
894
+ )
895
+ );
896
+
897
+ console.log(
898
+ `✅ [Recycle] Saved archetype ${index} (${gender}) for ${combination}`
899
+ );
900
+ }
901
+ }
902
+
903
+ console.log(`🏁 [Recycle] Finished combination: ${combination}`);
904
+ }
905
+
906
+ console.log(
907
+ `🎉 [Recycle] Completed recycling for ${compositions.length} combinations`
908
+ );
909
+ }
910
+
741
911
  async fetchMissingArchetypeCompositions(): Promise<Composition[]> {
742
912
  const db = this.context.drizzle();
743
913
  const incomplete = await this.findIncompleteCombinations();
744
- console.log(`🔎 [Fetch] Retrieved ${incomplete.length} incomplete combinations`);
914
+ console.log(
915
+ `🔎 [Fetch] Retrieved ${incomplete.length} incomplete combinations`
916
+ );
745
917
  const limited = incomplete.slice(0, 400);
746
918
 
747
919
  const compositions: Composition[] = [];
@@ -776,13 +948,18 @@ export class ArchetypeService {
776
948
  moon,
777
949
  indexesToGenerate,
778
950
  });
779
- console.log(`✅ [Fetch] Will regenerate indexes [${indexesToGenerate.join(', ')}] for ${combination}`);
951
+ console.log(
952
+ `✅ [Fetch] Will regenerate indexes [${indexesToGenerate.join(
953
+ ', '
954
+ )}] for ${combination}`
955
+ );
780
956
  }
781
957
  }
782
958
 
783
- console.log(`📦 [Fetch] Total compositions to regenerate (limited to 400): ${compositions.length}`);
784
-
785
-
959
+ console.log(
960
+ `📦 [Fetch] Total compositions to regenerate (limited to 400): ${compositions.length}`
961
+ );
962
+
786
963
  return compositions;
787
964
  }
788
965
 
@@ -794,43 +971,69 @@ export class ArchetypeService {
794
971
  english: [] as { name: string; essenceLine: string }[],
795
972
  portuguese: [] as { masc: string; fem: string; essenceLine: string }[],
796
973
  };
797
-
974
+
798
975
  try {
799
- const enMatch = block.match(/-EN[\s\S]*?-PT/);
800
- const ptMatch = block.match(/-PT[\s\S]*/);
801
-
802
- const enBlock = enMatch?.[0].replace(/-EN/, '').replace(/-PT/, '').trim() ?? '';
803
- const ptBlock = ptMatch?.[0].replace(/-PT/, '').trim() ?? '';
804
-
805
- result.english = enBlock
806
- .split(/\n\d\.\s*\n?/)
976
+ // Normalize the block by replacing multiple spaces with a single space and ensuring newlines
977
+ const normalizedBlock = block
978
+ .replace(/\s+/g, ' ')
979
+ .replace(/-EN\s*/, '-EN\n')
980
+ .replace(/-PT\s*/, '\n-PT\n')
981
+ .trim();
982
+
983
+ // Split into EN and PT sections
984
+ const sections = normalizedBlock.split(/\n?-PT\n/);
985
+ const enSection = sections[0]?.replace(/-EN\n/, '').trim() ?? '';
986
+ const ptSection = sections[1]?.trim() ?? '';
987
+
988
+ // Parse English entries
989
+ result.english = enSection
990
+ .split(/\n?\d+\.\s*\n?/)
807
991
  .filter(Boolean)
808
992
  .map((entry, i) => {
809
- const name = entry.match(/• Name:\s*(.+)/)?.[1]?.trim() ?? '';
810
- const essenceLine = entry.match(/• Essence:\s*(.+)/)?.[1]?.trim() ?? '';
993
+ const nameMatch = entry.match(/• Name:\s*([^•]+)/);
994
+ const essenceMatch = entry.match(/• Essence:\s*([^•]+)/);
995
+ const name = nameMatch?.[1]?.trim() ?? '';
996
+ const essenceLine = essenceMatch?.[1]?.trim() ?? '';
811
997
  if (!name || !essenceLine) {
812
- console.warn(`⚠️ [Parse] Incomplete English entry ${i + 1}:`, entry);
998
+ console.warn(
999
+ `⚠️ [Parse] Incomplete English entry ${i + 1}:`,
1000
+ entry
1001
+ );
813
1002
  }
814
1003
  return { name, essenceLine };
815
1004
  });
816
-
817
- result.portuguese = ptBlock
818
- .split(/\n\d\.\s*\n?/)
1005
+
1006
+ // Parse Portuguese entries
1007
+ result.portuguese = ptSection
1008
+ .split(/\n?\d+\.\s*\n?/)
819
1009
  .filter(Boolean)
820
1010
  .map((entry, i) => {
821
- const masc = entry.match(/• Masculino:\s*(.+)/)?.[1]?.trim() ?? '';
822
- const fem = entry.match(/• Feminino:\s*(.+)/)?.[1]?.trim() ?? '';
823
- const essenceLine = entry.match(/• Essência:\s*(.+)/)?.[1]?.trim() ?? '';
1011
+ const mascMatch = entry.match(/• Masculino:\s*([^•]+)/);
1012
+ const femMatch = entry.match(/• Feminino:\s*([^•]+)/);
1013
+ const essenceMatch = entry.match(/• Essência:\s*([^•]+)/);
1014
+ const masc = mascMatch?.[1]?.trim() ?? '';
1015
+ const fem = femMatch?.[1]?.trim() ?? '';
1016
+ const essenceLine = essenceMatch?.[1]?.trim() ?? '';
824
1017
  if (!masc || !fem || !essenceLine) {
825
- console.warn(`⚠️ [Parse] Incomplete Portuguese entry ${i + 1}:`, entry);
1018
+ console.warn(
1019
+ `⚠️ [Parse] Incomplete Portuguese entry ${i + 1}:`,
1020
+ entry
1021
+ );
1022
+ }
1023
+ // Validate gender consistency
1024
+ if (masc.startsWith('A ') && fem.startsWith('O ')) {
1025
+ console.warn(
1026
+ `⚠️ [Parse] Gender mismatch in Portuguese entry ${i + 1}:`,
1027
+ entry
1028
+ );
1029
+ return { masc: fem, fem: masc, essenceLine }; // Swap to correct the mismatch
826
1030
  }
827
1031
  return { masc, fem, essenceLine };
828
1032
  });
829
-
830
1033
  } catch (error) {
831
1034
  console.error('❌ [Parse] Failed to parse block:', error);
832
1035
  }
833
-
1036
+
834
1037
  return result;
835
1038
  }
836
1039
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.307",
3
+ "version": "0.0.309",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -211,6 +211,14 @@ For every composition, return:
211
211
  Avoid names that sound like objects, titles, or abstract forces (e.g., The Lantern, The Eclipse, The Veil).
212
212
  Instead, use names that suggest a personified being or living archetype, such as The Masked Oracle, The Starborn Seeker, or The Dancer of the Falling Sky.
213
213
 
214
+ **Formatting Rules**:
215
+ - Each composition block must start with "Composition <number>" on its own line.
216
+ - Within each composition block, the English section must start with "-EN" on its own line, followed by the entries.
217
+ - The Portuguese section must start with "-PT" on its own line, followed by the entries.
218
+ - Each entry (e.g., "1.", "2.", "3.") must be on its own line, with its sub-items (e.g., "• Name: ...", "• Masculino: ...") on separate lines.
219
+ - Do not include any extra characters, suffixes, or separators (e.g., "###", "---", or extra newlines) at the end of the response or between composition blocks.
220
+ - Ensure that Portuguese names correctly match the gender: "Masculino" names should use masculine forms (e.g., "O Sábio"), and "Feminino" names should use feminine forms (e.g., "A Sábia").
221
+
214
222
  Do not include any commentary or explanations outside the defined output structure. Only return the output block for each composition, exactly as described:`;
215
223
 
216
224
  const compositionBlocks = compositions.map((comp, i) => {
@@ -219,7 +227,7 @@ Do not include any commentary or explanations outside the defined output structu
219
227
  const moon = influenceMap.moon[comp.moon];
220
228
  const indexes = comp.indexesToGenerate ?? [1, 2, 3];
221
229
 
222
- const influences = `\n\n### Composition ${i + 1}
230
+ const influences = `\n\nComposition ${i + 1}
223
231
 
224
232
  • First Influence – ${sun.label}: ${sun.description}
225
233
  • Second Influence – ${asc.label}: ${asc.description}
@@ -228,28 +236,28 @@ Do not include any commentary or explanations outside the defined output structu
228
236
  const enLines = indexes
229
237
  .map(
230
238
  (idx) => `${idx}.
231
- • Name: [Name that emphasizes the ${
232
- ['first', 'second', 'third'][idx - 1]
233
- } influence, while blending the other two]
234
- • Essence: [Short poetic description line in English]`
239
+ • Name: [Name that emphasizes the ${
240
+ ['first', 'second', 'third'][idx - 1]
241
+ } influence, while blending the other two]
242
+ • Essence: [Short poetic description line in English]`
235
243
  )
236
244
  .join('\n');
237
245
 
238
246
  const ptLines = indexes
239
247
  .map(
240
248
  (idx) => `${idx}.
241
- • Masculino: [Portuguese name (masc)]
242
- • Feminino: [Portuguese name (fem)]
243
- • Essência: [Short poetic description line in Portuguese]`
249
+ • Masculino: [Portuguese name (masc)]
250
+ • Feminino: [Portuguese name (fem)]
251
+ • Essência: [Short poetic description line in Portuguese]`
244
252
  )
245
253
  .join('\n');
246
254
 
247
255
  return `${influences}
248
256
 
249
- \`\n-EN
257
+ \`\n-EN
250
258
  ${enLines}
251
259
 
252
- -PT
260
+ -PT
253
261
  ${ptLines}
254
262
  \``;
255
263
  });