@zodic/shared 0.0.236 → 0.0.238

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.
@@ -550,22 +550,20 @@ export class ConceptService {
550
550
  structuredContentEN: StructuredConceptContent;
551
551
  structuredContentPT: StructuredConceptContent;
552
552
  }> {
553
- console.log(
554
- `📌 [START] Parsing structured content for ${conceptSlug} from ChatGPT response.`
555
- );
553
+ console.log(`📌 [START] Parsing structured content for ${conceptSlug}`);
556
554
 
557
555
  // ✅ Step 1: Clean artifacts before processing
558
556
  let cleanedResponse = response
559
557
  .replace(/[*#•]/g, '') // Remove bullet points, bold markers, headings
560
558
  .replace(/---+/g, '') // Remove dividers (like "---")
561
559
  .replace(/\n\s*\n/g, '\n\n') // Normalize multiple line breaks
562
- .replace(/\s+EN:\s*/g, '\nEN: ') // Ensure consistent "EN:" formatting
563
- .replace(/\s+PT:\s*/g, '\nPT: ') // Ensure consistent "PT:" formatting
560
+ .replace(/\s+EN:\s*/g, '\nEN: ') // Normalize "EN:"
561
+ .replace(/\s+PT:\s*/g, '\nPT: ') // Normalize "PT:"
564
562
  .trim();
565
563
 
566
564
  console.log(
567
565
  '🔹 [CLEANED RESPONSE]:',
568
- cleanedResponse.slice(0, 500) + '...'
566
+ cleanedResponse.slice(0, 3000) + '...'
569
567
  );
570
568
 
571
569
  try {
@@ -677,12 +675,13 @@ export class ConceptService {
677
675
  }
678
676
 
679
677
  // ✅ Step 3: Detect if AI interleaved EN/PT sections
680
- const interleavedMatches = cleanedResponse.match(
681
- /(EN:\s*[\s\S]+?PT:\s*[\s\S]+?)(?=EN:|$)/
682
- );
683
678
  let extractedEN = '';
684
679
  let extractedPT = '';
685
680
 
681
+ const interleavedMatches = cleanedResponse.match(
682
+ /(EN:\s*[\s\S]+?PT:\s*[\s\S]+?)(?=EN:|$)/g
683
+ );
684
+
686
685
  if (interleavedMatches) {
687
686
  console.warn(
688
687
  '⚠️ Detected interleaved EN/PT sections. Parsing accordingly.'
@@ -706,21 +705,21 @@ export class ConceptService {
706
705
  }
707
706
 
708
707
  console.log(
709
- '✅ [MATCH SUCCESS] Extracted English Content:',
710
- extractedEN.slice(0, 500) + '...'
708
+ '✅ Extracted English Content:',
709
+ extractedEN.slice(0, 1000) + '...'
711
710
  );
712
711
  console.log(
713
- '✅ [MATCH SUCCESS] Extracted Portuguese Content:',
714
- extractedPT.slice(0, 500) + '...'
712
+ '✅ Extracted Portuguese Content:',
713
+ extractedPT.slice(0, 1000) + '...'
715
714
  );
716
715
 
717
716
  // ✅ Step 4: Extract structured sections dynamically
718
717
  function extractSections(text: string, sectionTitles: string[]) {
719
718
  return sectionTitles.map((title) => {
720
- console.log(`🔍 [PROCESSING] Extracting section: "${title}"`);
719
+ console.log(`🔍 Extracting section: "${title}"`);
721
720
 
722
721
  const regex = new RegExp(
723
- `\\d+\\.\\s*${title}:\\s*([\\s\\S]+?)(?=\\n\\d+\\.\\s*[A-Z]|$)`
722
+ `(?:\\d+\\.\\s*)?${title}\\s*:?\\s*([\\s\\S]+?)(?=\\n(?:\\d+\\.\\s*)?[A-Z]|$)`
724
723
  );
725
724
  const match = text.match(regex);
726
725
 
@@ -734,10 +733,7 @@ export class ConceptService {
734
733
  .map((p) => p.trim())
735
734
  .filter((p) => p.length > 0);
736
735
 
737
- console.log(
738
- `✅ [EXTRACTED] "${title}" - Parsed Content:`,
739
- paragraphs
740
- );
736
+ console.log(`✅ Extracted "${title}" - Parsed Content:`, paragraphs);
741
737
  return { type: 'section', title, content: paragraphs };
742
738
  });
743
739
  }
@@ -746,17 +742,15 @@ export class ConceptService {
746
742
  const structuredContentPT = extractSections(extractedPT, sectionsPT);
747
743
 
748
744
  console.log(
749
- '🎯 [FINAL RESULT] Parsed English Content:',
745
+ '🎯 Parsed English Content:',
750
746
  JSON.stringify(structuredContentEN, null, 2)
751
747
  );
752
748
  console.log(
753
- '🎯 [FINAL RESULT] Parsed Portuguese Content:',
749
+ '🎯 Parsed Portuguese Content:',
754
750
  JSON.stringify(structuredContentPT, null, 2)
755
751
  );
756
752
 
757
- console.log(
758
- '✅ [COMPLETE] Structured content parsing finished successfully.'
759
- );
753
+ console.log('✅ Structured content parsing finished successfully.');
760
754
 
761
755
  return {
762
756
  structuredContentEN,
@@ -784,6 +778,247 @@ export class ConceptService {
784
778
  }
785
779
  }
786
780
 
781
+ // async parseStructuredContent(
782
+ // conceptSlug: Concept,
783
+ // response: string
784
+ // ): Promise<{
785
+ // structuredContentEN: StructuredConceptContent;
786
+ // structuredContentPT: StructuredConceptContent;
787
+ // }> {
788
+ // console.log(
789
+ // `📌 [START] Parsing structured content for ${conceptSlug} from ChatGPT response.`
790
+ // );
791
+
792
+ // // ✅ Step 1: Clean artifacts before processing
793
+ // let cleanedResponse = response
794
+ // .replace(/[*#•]/g, '') // Remove bullet points, bold markers, headings
795
+ // .replace(/---+/g, '') // Remove dividers (like "---")
796
+ // .replace(/\n\s*\n/g, '\n\n') // Normalize multiple line breaks
797
+ // .replace(/\s+EN:\s*/g, '\nEN: ') // Ensure consistent "EN:" formatting
798
+ // .replace(/\s+PT:\s*/g, '\nPT: ') // Ensure consistent "PT:" formatting
799
+ // .trim();
800
+
801
+ // console.log(
802
+ // '🔹 [CLEANED RESPONSE]:',
803
+ // cleanedResponse.slice(0, 500) + '...'
804
+ // );
805
+
806
+ // try {
807
+ // // ✅ Step 2: Define Section Titles Per Concept
808
+ // const conceptSections: Record<string, { en: string[]; pt: string[] }> = {
809
+ // crown: {
810
+ // en: [
811
+ // 'Core Identity',
812
+ // 'Strengths and Challenges',
813
+ // 'Path to Fulfillment',
814
+ // 'Emotional Depth',
815
+ // 'Vision and Aspirations',
816
+ // ],
817
+ // pt: [
818
+ // 'Identidade Essencial',
819
+ // 'Forças e Desafios',
820
+ // 'Caminho para a Plenitude',
821
+ // 'Profundidade Emocional',
822
+ // 'Visão e Aspirações',
823
+ // ],
824
+ // },
825
+ // scepter: {
826
+ // en: [
827
+ // 'The Power of Expression',
828
+ // 'The Dance of Action and Reaction',
829
+ // 'Navigating Challenges',
830
+ // 'The Mirror of Relations',
831
+ // 'Impact on the World',
832
+ // ],
833
+ // pt: [
834
+ // 'O Poder da Expressão',
835
+ // 'A Dança da Ação e Reação',
836
+ // 'Navegando Desafios',
837
+ // 'O Espelho das Relações',
838
+ // 'Impacto no Mundo',
839
+ // ],
840
+ // },
841
+ // amulet: {
842
+ // en: [
843
+ // 'The Shield of Protection',
844
+ // 'The Strength Within',
845
+ // 'Nurturing Growth',
846
+ // 'The Cycle of Renewal',
847
+ // 'The Anchor of Wisdom',
848
+ // ],
849
+ // pt: [
850
+ // 'O Escudo da Proteção',
851
+ // 'A Força Interior',
852
+ // 'Cultivando o Crescimento',
853
+ // 'O Ciclo da Renovação',
854
+ // 'A Âncora da Sabedoria',
855
+ // ],
856
+ // },
857
+ // ring: {
858
+ // en: [
859
+ // 'Magnetic Pull',
860
+ // 'Hidden Desires',
861
+ // 'The Fated Dance',
862
+ // 'Unveiling the Shadows',
863
+ // 'Your Love Power',
864
+ // ],
865
+ // pt: [
866
+ // 'Força Magnética',
867
+ // 'Desejos Ocultos',
868
+ // 'A Dança do Destino',
869
+ // 'Revelando as Sombras',
870
+ // 'Seu Poder no Amor',
871
+ // ],
872
+ // },
873
+ // lantern: {
874
+ // en: [
875
+ // 'The Call to Awakening',
876
+ // 'Walking Through Shadows',
877
+ // 'The Inner Flame',
878
+ // 'Revelations and Truths',
879
+ // 'Becoming the Light',
880
+ // ],
881
+ // pt: [
882
+ // 'O Chamado para o Despertar',
883
+ // 'Caminhando Pelas Sombras',
884
+ // 'A Chama Interior',
885
+ // 'Revelações e Verdades',
886
+ // 'Tornando-se a Luz',
887
+ // ],
888
+ // },
889
+ // orb: {
890
+ // en: [
891
+ // 'The Path Unfolding',
892
+ // 'A Calling Beyond the Self',
893
+ // 'Turning Points of Fate',
894
+ // 'Mastering the Journey',
895
+ // 'Legacy and Impact',
896
+ // ],
897
+ // pt: [
898
+ // 'O Caminho que se Revela',
899
+ // 'Um Chamado Além de Si Mesmo',
900
+ // 'Pontos de Virada do Destino',
901
+ // 'Dominando a Jornada',
902
+ // 'Legado e Impacto',
903
+ // ],
904
+ // },
905
+ // };
906
+
907
+ // const sectionsEN = conceptSections[conceptSlug]?.en;
908
+ // const sectionsPT = conceptSections[conceptSlug]?.pt;
909
+
910
+ // if (!sectionsEN || !sectionsPT) {
911
+ // throw new Error(`Unknown concept: ${conceptSlug}`);
912
+ // }
913
+
914
+ // // ✅ Step 3: Detect if AI interleaved EN/PT sections
915
+ // const interleavedMatches = cleanedResponse.match(
916
+ // /(EN:\s*[\s\S]+?PT:\s*[\s\S]+?)(?=EN:|$)/
917
+ // );
918
+ // let extractedEN = '';
919
+ // let extractedPT = '';
920
+
921
+ // if (interleavedMatches) {
922
+ // console.warn(
923
+ // '⚠️ Detected interleaved EN/PT sections. Parsing accordingly.'
924
+ // );
925
+ // extractedEN = interleavedMatches
926
+ // .map(
927
+ // (pair) => pair.match(/EN:\s*([\s\S]+?)\s*PT:/)?.[1]?.trim() || ''
928
+ // )
929
+ // .join('\n\n');
930
+ // extractedPT = interleavedMatches
931
+ // .map((pair) => pair.match(/PT:\s*([\s\S]+)/)?.[1]?.trim() || '')
932
+ // .join('\n\n');
933
+ // } else {
934
+ // console.log(
935
+ // '✅ Standard format detected (EN block followed by PT block).'
936
+ // );
937
+ // extractedEN =
938
+ // cleanedResponse.match(/EN:\s*([\s\S]+?)\s*PT:/)?.[1]?.trim() || '';
939
+ // extractedPT =
940
+ // cleanedResponse.match(/PT:\s*([\s\S]+)/)?.[1]?.trim() || '';
941
+ // }
942
+
943
+ // console.log(
944
+ // '✅ [MATCH SUCCESS] Extracted English Content:',
945
+ // extractedEN.slice(0, 500) + '...'
946
+ // );
947
+ // console.log(
948
+ // '✅ [MATCH SUCCESS] Extracted Portuguese Content:',
949
+ // extractedPT.slice(0, 500) + '...'
950
+ // );
951
+
952
+ // // ✅ Step 4: Extract structured sections dynamically
953
+ // function extractSections(text: string, sectionTitles: string[]) {
954
+ // return sectionTitles.map((title) => {
955
+ // console.log(`🔍 [PROCESSING] Extracting section: "${title}"`);
956
+
957
+ // const regex = new RegExp(
958
+ // `\\d+\\.\\s*${title}:\\s*([\\s\\S]+?)(?=\\n\\d+\\.\\s*[A-Z]|$)`
959
+ // );
960
+ // const match = text.match(regex);
961
+
962
+ // if (!match) {
963
+ // throw new Error(`❌ Missing section: "${title}"`);
964
+ // }
965
+
966
+ // const paragraphs = match[1]
967
+ // .trim()
968
+ // .split(/\n{2,}/)
969
+ // .map((p) => p.trim())
970
+ // .filter((p) => p.length > 0);
971
+
972
+ // console.log(
973
+ // `✅ [EXTRACTED] "${title}" - Parsed Content:`,
974
+ // paragraphs
975
+ // );
976
+ // return { type: 'section', title, content: paragraphs };
977
+ // });
978
+ // }
979
+
980
+ // const structuredContentEN = extractSections(extractedEN, sectionsEN);
981
+ // const structuredContentPT = extractSections(extractedPT, sectionsPT);
982
+
983
+ // console.log(
984
+ // '🎯 [FINAL RESULT] Parsed English Content:',
985
+ // JSON.stringify(structuredContentEN, null, 2)
986
+ // );
987
+ // console.log(
988
+ // '🎯 [FINAL RESULT] Parsed Portuguese Content:',
989
+ // JSON.stringify(structuredContentPT, null, 2)
990
+ // );
991
+
992
+ // console.log(
993
+ // '✅ [COMPLETE] Structured content parsing finished successfully.'
994
+ // );
995
+
996
+ // return {
997
+ // structuredContentEN,
998
+ // structuredContentPT,
999
+ // };
1000
+ // } catch (error) {
1001
+ // console.error(`❌ Parsing failed for ${conceptSlug}. Logging failure.`);
1002
+
1003
+ // // ✅ Store failure in KV for debugging
1004
+ // const kvFailuresStore = this.context.kvConceptFailuresStore();
1005
+ // const failureKey = `failures:content:parsing:${conceptSlug}:${new Date().toISOString()}`;
1006
+
1007
+ // await kvFailuresStore.put(
1008
+ // failureKey,
1009
+ // JSON.stringify({
1010
+ // error: (error as Error).message,
1011
+ // conceptSlug,
1012
+ // cleanedResponse,
1013
+ // timestamp: new Date().toISOString(),
1014
+ // })
1015
+ // );
1016
+
1017
+ // console.error(`🚨 Failure logged in KV: ${failureKey}`);
1018
+ // throw error;
1019
+ // }
1020
+ // }
1021
+
787
1022
  // private parseStructuredContent(
788
1023
  // conceptSlug: Concept,
789
1024
  // response: string
package/db/schema.ts CHANGED
@@ -181,6 +181,68 @@ export const astroAspects = sqliteTable(
181
181
  ]
182
182
  );
183
183
 
184
+ export const astroReports = sqliteTable(
185
+ "astro_reports",
186
+ {
187
+ id: text("id").primaryKey(), // Unique identifier
188
+ type: text("type").notNull(), // 'planet', 'key_point', 'karmic_point', 'hermetic_part'
189
+ name: text("name").notNull(), // Slug (e.g., 'sun', 'pars_fortuna')
190
+ sign: text("sign"), // Slug (e.g., 'aries', 'taurus') - nullable for house-only reports
191
+ house: integer("house"), // Nullable, only for applicable placements
192
+ enDescription: text("en_description"), // General explanation of the placement
193
+ ptDescription: text("pt_description"),
194
+ enReport: text("en_report"), // Full report content in English
195
+ ptReport: text("pt_report"), // Full report content in Portuguese
196
+ createdAt: integer("created_at").default(sql`CURRENT_TIMESTAMP`),
197
+ updatedAt: integer("updated_at").default(sql`CURRENT_TIMESTAMP`),
198
+ },
199
+ (t) => [
200
+ index("astro_reports_type_idx").on(t.type),
201
+ index("astro_reports_name_sign_idx").on(t.name, t.sign), // Most critical index (name + sign)
202
+ index("astro_reports_name_house_idx").on(t.name, t.house), // Most critical index (name + house)
203
+ ]
204
+ );
205
+
206
+ export const houseReports = sqliteTable(
207
+ "house_reports",
208
+ {
209
+ id: text("id").primaryKey(), // Unique identifier
210
+ sign: text("sign").notNull(), // Lowercase slug (e.g., 'aries', 'taurus')
211
+ house: integer("house").notNull(), // House placement (1-12)
212
+ enDescription: text("en_description"), // General explanation of the sign in a house
213
+ ptDescription: text("pt_description"),
214
+ enReport: text("en_report"), // Full report content in English
215
+ ptReport: text("pt_report"), // Full report content in Portuguese
216
+ createdAt: integer("created_at").default(sql`CURRENT_TIMESTAMP`),
217
+ updatedAt: integer("updated_at").default(sql`CURRENT_TIMESTAMP`),
218
+ },
219
+ (t) => [
220
+ index("house_reports_sign_house_idx").on(t.sign, t.house), // Optimized index for sign + house lookups
221
+ ]
222
+ );
223
+
224
+ export const aspectReports = sqliteTable(
225
+ "aspect_reports",
226
+ {
227
+ id: text("id").primaryKey(), // Unique identifier
228
+ aspectingPlanet: text("aspecting_planet").notNull(), // Planet/point initiating the aspect (e.g., 'sun', 'mars')
229
+ aspectedPlanet: text("aspected_planet").notNull(), // Planet/point receiving the aspect (e.g., 'moon', 'venus')
230
+ aspect: text("aspect").notNull(), // Aspect type ('conjunction', 'trine', 'square', etc.)
231
+ enDescription: text("en_description"), // General explanation of the aspect
232
+ ptDescription: text("pt_description"),
233
+ enReport: text("en_report"), // Full report content in English
234
+ ptReport: text("pt_report"), // Full report content in Portuguese
235
+ createdAt: integer("created_at").default(sql`CURRENT_TIMESTAMP`),
236
+ updatedAt: integer("updated_at").default(sql`CURRENT_TIMESTAMP`),
237
+ },
238
+ (t) => [
239
+ index("aspect_reports_aspecting_idx").on(t.aspectingPlanet),
240
+ index("aspect_reports_aspected_idx").on(t.aspectedPlanet),
241
+ index("aspect_reports_aspect_idx").on(t.aspect),
242
+ index("aspect_reports_combined_idx").on(t.aspectingPlanet, t.aspectedPlanet, t.aspect), // Optimized for aspect lookups
243
+ ]
244
+ );
245
+
184
246
  export const concepts = sqliteTable(
185
247
  'concepts',
186
248
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.236",
3
+ "version": "0.0.238",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -1,21 +1,109 @@
1
+ import { pointNameMap } from '../../utils/astroPrompts/pointNameMap';
1
2
  import { BackendBindings } from './cloudflare';
2
3
  import { ControlNetConfig, Gender } from './generic';
3
4
 
4
5
  export const zodiacSigns = [
5
- 'Aries',
6
- 'Taurus',
7
- 'Gemini',
8
- 'Cancer',
9
- 'Leo',
10
- 'Virgo',
11
- 'Libra',
12
- 'Scorpio',
13
- 'Sagittarius',
14
- 'Capricorn',
15
- 'Aquarius',
16
- 'Pisces',
6
+ "aries",
7
+ "taurus",
8
+ "gemini",
9
+ "cancer",
10
+ "leo",
11
+ "virgo",
12
+ "libra",
13
+ "scorpio",
14
+ "sagittarius",
15
+ "capricorn",
16
+ "aquarius",
17
+ "pisces",
18
+ ] as const;
19
+ export type ZodiacSignSlug = (typeof zodiacSigns)[number];
20
+
21
+ // Houses
22
+ export const houses = Array.from({ length: 12 }, (_, i) => i + 1);
23
+
24
+ // Aspects
25
+ const aspects = ["conjunction", "opposition", "trine", "square", "sextile"] as const;
26
+ export type AspectType = (typeof aspects)[number];
27
+
28
+ export const pointTypes: Record<PointSlug, string> = {
29
+ sun: "planet",
30
+ moon: "planet",
31
+ mercury: "planet",
32
+ venus: "planet",
33
+ mars: "planet",
34
+ jupiter: "planet",
35
+ saturn: "planet",
36
+ uranus: "planet",
37
+ neptune: "planet",
38
+ pluto: "planet",
39
+ ascendant: "key_point",
40
+ descendant: "key_point",
41
+ midheaven: "key_point",
42
+ imum_coeli: "key_point",
43
+ north_node: "karmic_point",
44
+ south_node: "karmic_point",
45
+ chiron: "karmic_point",
46
+ lilith: "hermetic_part",
47
+ vertex: "hermetic_part",
48
+ antivertex: "hermetic_part",
49
+ pars_fortuna: "hermetic_part",
50
+ pars_spiritus: "hermetic_part",
51
+ pars_victoria: "hermetic_part",
52
+ pars_amoris: "hermetic_part",
53
+ pars_eros: "hermetic_part",
54
+ pars_fortitudo: "hermetic_part",
55
+ pars_necessitatis: "hermetic_part",
56
+ };
57
+
58
+ // Subset of 14 points for aspects
59
+ export const aspectPoints: PointSlug[] = [
60
+ "sun",
61
+ "moon",
62
+ "mercury",
63
+ "venus",
64
+ "mars",
65
+ "jupiter",
66
+ "saturn",
67
+ "uranus",
68
+ "neptune",
69
+ "pluto",
70
+ "ascendant",
71
+ "descendant",
72
+ "midheaven",
73
+ "imum_coeli",
17
74
  ];
18
75
 
76
+ export type PointSlug = keyof typeof pointNameMap;
77
+
78
+ export type ZodiacSign =
79
+ | 'aries'
80
+ | 'taurus'
81
+ | 'gemini'
82
+ | 'cancer'
83
+ | 'leo'
84
+ | 'virgo'
85
+ | 'libra'
86
+ | 'scorpio'
87
+ | 'sagittarius'
88
+ | 'capricorn'
89
+ | 'aquarius'
90
+ | 'pisces';
91
+
92
+ export const zodiacSignMap: Record<ZodiacSign, string> = {
93
+ aries: 'Aries',
94
+ taurus: 'Taurus',
95
+ gemini: 'Gemini',
96
+ cancer: 'Cancer',
97
+ leo: 'Leo',
98
+ virgo: 'Virgo',
99
+ libra: 'Libra',
100
+ scorpio: 'Scorpio',
101
+ sagittarius: 'Sagittarius',
102
+ capricorn: 'Capricorn',
103
+ aquarius: 'Aquarius',
104
+ pisces: 'Pisces',
105
+ };
106
+
19
107
  export interface ChatGPTOptions {
20
108
  model?: string;
21
109
  options?: Record<string, any>;
@@ -0,0 +1,91 @@
1
+ import { PointSlug, ZodiacSign, zodiacSignMap } from '../../types/scopes/legacy';
2
+ import { pointNameMap } from './pointNameMap';
3
+
4
+ // Interfaces for each function's parameters
5
+ interface PlanetOrPointParams {
6
+ type: 'planet' | 'key_point' | 'karmic_point' | 'hermetic_part';
7
+ sign: ZodiacSign;
8
+ pointName: PointSlug;
9
+ }
10
+
11
+ interface HouseParams {
12
+ houseNumber: number; // e.g., "1", "2", etc.
13
+ sign: ZodiacSign;
14
+ }
15
+
16
+ interface AspectParams {
17
+ aspectingPlanet: PointSlug;
18
+ aspectedPlanet: PointSlug;
19
+ aspectingType: string; // e.g., "conjunct", "square", "trine"
20
+ }
21
+
22
+ // Modular astroPrompts object
23
+ export const astroPrompts = {
24
+ planetOrPoint: ({ type, sign, pointName }: PlanetOrPointParams): string => {
25
+ const validTypes = ['planet', 'key_point', 'karmic_point', 'hermetic_part'];
26
+ if (!validTypes.includes(type)) {
27
+ throw new Error(
28
+ `Invalid type for planetOrPoint: ${type}. Must be one of ${validTypes.join(
29
+ ', '
30
+ )}`
31
+ );
32
+ }
33
+
34
+ const formattedPointName = pointNameMap[pointName] || pointName;
35
+ return `
36
+ Make me a report for the astrological placement: ${formattedPointName} in ${zodiacSignMap[sign]}.
37
+ The response should include an English version and a Portuguese version.
38
+ Use "###" for titles and "####" for subtitles.
39
+ The response format should be exactly like this:
40
+
41
+ -- EN
42
+
43
+ [English Report - structured with headings, sections, and detailed explanations]
44
+
45
+ -- PT
46
+
47
+ [Portuguese Report - structured exactly the same as the English version]
48
+ `.trim();
49
+ },
50
+
51
+ house: ({ houseNumber, sign }: HouseParams): string => {
52
+ return `
53
+ Make me a report for the astrological placement: House ${houseNumber} in ${zodiacSignMap[sign]}.
54
+ The response should include an English version and a Portuguese version.
55
+ Use "###" for titles and "####" for subtitles.
56
+ The response format should be exactly like this:
57
+
58
+ -- EN
59
+
60
+ [English Report - structured with headings, sections, and detailed explanations]
61
+
62
+ -- PT
63
+
64
+ [Portuguese Report - structured exactly the same as the English version]
65
+ `.trim();
66
+ },
67
+
68
+ aspect: ({
69
+ aspectingPlanet,
70
+ aspectedPlanet,
71
+ aspectingType,
72
+ }: AspectParams): string => {
73
+ return `
74
+ Make me a report for the astrological aspect: ${
75
+ pointNameMap[aspectingPlanet] || aspectingPlanet
76
+ } ${aspectingType} ${pointNameMap[aspectedPlanet] || aspectedPlanet}.
77
+ The response should include an English version and a Portuguese version.
78
+ Use "###" for titles and "####" for subtitles.
79
+ The response format should be exactly like this:
80
+
81
+ -- EN
82
+
83
+ [English Report - structured with headings, sections, and detailed explanations]
84
+
85
+ -- PT
86
+
87
+ [Portuguese Report - structured exactly the same as the English version]
88
+ `.trim();
89
+ },
90
+ };
91
+
@@ -0,0 +1,29 @@
1
+ export const pointNameMap: Record<string, string> = {
2
+ sun: "Sun",
3
+ moon: "Moon",
4
+ mercury: "Mercury",
5
+ venus: "Venus",
6
+ mars: "Mars",
7
+ jupiter: "Jupiter",
8
+ saturn: "Saturn",
9
+ uranus: "Uranus",
10
+ neptune: "Neptune",
11
+ pluto: "Pluto",
12
+ ascendant: "Ascendant",
13
+ descendant: "Descendant",
14
+ midheaven: "Midheaven",
15
+ imum_coeli: "Imum Coeli",
16
+ north_node: "North Node",
17
+ south_node: "South Node",
18
+ chiron: "Chiron",
19
+ lilith: "Lilith",
20
+ vertex: "Vertex",
21
+ antivertex: "Antivertex",
22
+ pars_fortuna: "Pars Fortuna",
23
+ pars_spiritus: "Pars Spiritus",
24
+ pars_victoria: "Pars Victoria",
25
+ pars_amoris: "Pars Amoris",
26
+ pars_eros: "Pars Eros",
27
+ pars_fortitudo: "Pars Fortitudo",
28
+ pars_necessitatis: "Pars Necessitatis",
29
+ };
package/utils/index.ts CHANGED
@@ -101,7 +101,9 @@ export const verifyToken = async (
101
101
  }
102
102
  };
103
103
 
104
- export const providers: (c: AuthCtx) => Record<Provider, ProviderInfo> = (c) => ({
104
+ export const providers: (c: AuthCtx) => Record<Provider, ProviderInfo> = (
105
+ c
106
+ ) => ({
105
107
  google: {
106
108
  serverUrl: 'https://accounts.google.com/.well-known/openid-configuration', // ✅ Correct Discovery URL
107
109
  clientId: c.env.GOOGLE_OAUTH_CLIENT_ID!,
@@ -120,3 +122,10 @@ export function normalizeName(name: string): string {
120
122
  return name.toLowerCase().replace(/\s+/g, '-');
121
123
  }
122
124
 
125
+ export function chunkArray<T>(array: T[], size: number): T[][] {
126
+ const result = [];
127
+ for (let i = 0; i < array.length; i += size) {
128
+ result.push(array.slice(i, i + size));
129
+ }
130
+ return result;
131
+ }