@zodic/shared 0.0.240 → 0.0.241

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.
@@ -7,9 +7,20 @@ import { drizzle } from 'drizzle-orm/d1';
7
7
  import { inject, injectable } from 'inversify';
8
8
  import 'reflect-metadata';
9
9
  import { v4 as uuidv4 } from 'uuid';
10
- import { schema } from '../..';
11
- import { conceptsData } from '../../db/schema';
12
- import { Concept, ControlNetConfig, Languages } from '../../types';
10
+ import { cleanText, schema } from '../..';
11
+ import {
12
+ aspectReports,
13
+ astroReports,
14
+ conceptsData,
15
+ houseReports,
16
+ } from '../../db/schema';
17
+ import {
18
+ AstrologicalReport,
19
+ AstroReportParams,
20
+ Concept,
21
+ ControlNetConfig,
22
+ Languages,
23
+ } from '../../types';
13
24
  import {
14
25
  KVConcept,
15
26
  sizes,
@@ -1993,4 +2004,249 @@ export class ConceptService {
1993
2004
 
1994
2005
  console.log(`✅ Stored duplicate keys for ${conceptSlug} in cache.`);
1995
2006
  }
2007
+
2008
+ // ? ASTRO PROCESSING
2009
+ async generateAstroReportContent(
2010
+ params: AstroReportParams,
2011
+ override: boolean = false
2012
+ ): Promise<void> {
2013
+ console.log(
2014
+ `🚀 Generating report content for ${JSON.stringify(
2015
+ params
2016
+ )}, override: ${override}`
2017
+ );
2018
+
2019
+ const db = drizzle(this.context.env.DB);
2020
+ let table, idField, whereClause;
2021
+
2022
+ // Determine table and ID
2023
+ switch (params.reportType) {
2024
+ case 'sign':
2025
+ case 'house':
2026
+ table = astroReports;
2027
+ idField =
2028
+ params.reportType === 'sign'
2029
+ ? sql`SELECT id FROM astro_reports WHERE name = ${params.pointName} AND sign = ${params.sign} AND house IS NULL`
2030
+ : sql`SELECT id FROM astro_reports WHERE name = ${params.pointName} AND house = ${params.house} AND sign IS NULL`;
2031
+ break;
2032
+ case 'signInHouse':
2033
+ table = houseReports;
2034
+ idField = sql`SELECT id FROM house_reports WHERE sign = ${params.sign} AND house = ${params.houseNumber}`;
2035
+ break;
2036
+ case 'aspect':
2037
+ table = aspectReports;
2038
+ idField = sql`SELECT id FROM aspect_reports WHERE aspecting_planet = ${params.aspectingPlanet} AND aspected_planet = ${params.aspectedPlanet} AND aspect = ${params.aspectingType}`;
2039
+ break;
2040
+ default:
2041
+ throw new Error(`Unknown report type: ${(params as any).reportType}`);
2042
+ }
2043
+
2044
+ const report = await db
2045
+ .select()
2046
+ .from(table)
2047
+ .where(sql`${idField}`)
2048
+ .get();
2049
+ if (!report) {
2050
+ throw new Error(`❌ No report found for ${JSON.stringify(params)}`);
2051
+ }
2052
+
2053
+ const id = report.id;
2054
+ const hasContent = report.enReport && report.ptReport;
2055
+
2056
+ if (!override && hasContent) {
2057
+ console.log(`⚡ Content already exists for ${id}, skipping`);
2058
+ return;
2059
+ }
2060
+
2061
+ let attempts = 0;
2062
+ const maxAttempts = 2;
2063
+
2064
+ while (attempts < maxAttempts) {
2065
+ let phase = 'generation';
2066
+ try {
2067
+ attempts++;
2068
+ console.log(`🔄 Attempt ${attempts} to generate report content...`);
2069
+
2070
+ // Use buildLLMMessages().generateAstroReportContent
2071
+ const messages = this.context
2072
+ .buildLLMMessages()
2073
+ .generateAstroReportContent({ params });
2074
+
2075
+ const response = await this.context
2076
+ .api()
2077
+ .callTogether.single(messages, {});
2078
+ if (!response) {
2079
+ throw new Error(`❌ AI returned an empty response`);
2080
+ }
2081
+
2082
+ phase = 'parsing';
2083
+
2084
+ const { enReport, ptReport } =
2085
+ await this.parseAstrologicalReportContent(
2086
+ params.reportType,
2087
+ response
2088
+ );
2089
+
2090
+ console.log(`💾 Storing report content for ${id}`);
2091
+ await db
2092
+ .update(table)
2093
+ .set({
2094
+ enReport: JSON.stringify(enReport),
2095
+ ptReport: JSON.stringify(ptReport),
2096
+ })
2097
+ .where(eq(table.id, id))
2098
+ .run();
2099
+
2100
+ console.log(`✅ Report content stored for ${id}`);
2101
+ return;
2102
+ } catch (error) {
2103
+ console.error(
2104
+ `❌ Attempt ${attempts} failed at phase: ${phase}`,
2105
+ (error as Error).message
2106
+ );
2107
+
2108
+ const failureKey = `failures:astro:${params.reportType}:${id}`;
2109
+ await this.context.kvConceptFailuresStore().put(
2110
+ failureKey,
2111
+ JSON.stringify({
2112
+ error: (error as Error).message,
2113
+ attempt: attempts,
2114
+ phase,
2115
+ params,
2116
+ timestamp: new Date().toISOString(),
2117
+ })
2118
+ );
2119
+
2120
+ if (attempts >= maxAttempts) {
2121
+ console.error(
2122
+ `🚨 All ${maxAttempts} attempts failed at phase ${phase}`
2123
+ );
2124
+ throw new Error(
2125
+ `Failed to generate content after ${maxAttempts} attempts`
2126
+ );
2127
+ }
2128
+
2129
+ console.log('🔁 Retrying...');
2130
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2131
+ }
2132
+ }
2133
+ }
2134
+
2135
+ async parseAstrologicalReportContent(
2136
+ reportType: AstroReportParams['reportType'],
2137
+ response: string
2138
+ ): Promise<{
2139
+ enReport: AstrologicalReport['sections'];
2140
+ ptReport: AstrologicalReport['sections'];
2141
+ }> {
2142
+ console.log(`📌 [START] Parsing report content for ${reportType}`);
2143
+
2144
+ try {
2145
+ const parsed = this.parseAstrologicalReport(response); // Your existing parser
2146
+
2147
+ const enReport = parsed.find((r) => r.language === 'EN');
2148
+ const ptReport = parsed.find((r) => r.language === 'PT');
2149
+
2150
+ if (!enReport || !ptReport) {
2151
+ throw new Error(`❌ Missing EN or PT report in response`);
2152
+ }
2153
+
2154
+ console.log(
2155
+ `✅ Parsed EN report sections:`,
2156
+ Object.keys(enReport.sections)
2157
+ );
2158
+ console.log(
2159
+ `✅ Parsed PT report sections:`,
2160
+ Object.keys(ptReport.sections)
2161
+ );
2162
+
2163
+ return {
2164
+ enReport: enReport.sections,
2165
+ ptReport: ptReport.sections,
2166
+ };
2167
+ } catch (error) {
2168
+ console.error(`❌ Parsing failed for ${reportType}`);
2169
+
2170
+ const failureKey = `failures:astro:parsing:${reportType}:${new Date().toISOString()}`;
2171
+ await this.context.kvConceptFailuresStore().put(
2172
+ failureKey,
2173
+ JSON.stringify({
2174
+ error: (error as Error).message,
2175
+ reportType,
2176
+ response,
2177
+ timestamp: new Date().toISOString(),
2178
+ })
2179
+ );
2180
+
2181
+ throw error;
2182
+ }
2183
+ }
2184
+
2185
+ private parseAstrologicalReport(response: string): AstrologicalReport[] {
2186
+ const reports: AstrologicalReport[] = [];
2187
+ const languageSections = response
2188
+ .split(/--\s*(EN|PT)\s*\n/)
2189
+ .filter(Boolean);
2190
+
2191
+ for (let i = 0; i < languageSections.length; i += 2) {
2192
+ const language = languageSections[i].trim();
2193
+ const content = languageSections[i + 1]?.trim();
2194
+ if (!content || !language.match(/^(EN|PT)$/)) continue;
2195
+
2196
+ const report: AstrologicalReport = {
2197
+ language,
2198
+ title: [],
2199
+ sections: {},
2200
+ };
2201
+
2202
+ const lines = content.split('\n').map((line) => line.trim());
2203
+ let currentSection = '';
2204
+
2205
+ for (const line of lines) {
2206
+ if (line.startsWith('### ') && report.title.length === 0) {
2207
+ report.title.push({
2208
+ type: 'title',
2209
+ content: cleanText(line.replace('### ', '')),
2210
+ });
2211
+ continue;
2212
+ }
2213
+
2214
+ if (line.startsWith('#### ')) {
2215
+ currentSection = cleanText(line.replace('#### ', '').trim());
2216
+ report.sections[currentSection] =
2217
+ report.sections[currentSection] || [];
2218
+ continue;
2219
+ }
2220
+
2221
+ if (!line || !currentSection) continue;
2222
+
2223
+ if (line.match(/^\d+\.\s/) || line.startsWith('- ')) {
2224
+ const listItem = line.replace(/^\d+\.\s|- /, '').trim();
2225
+ const boldMatch = listItem.match(/^\*\*(.+?)\*\*:\s*(.+)$/);
2226
+ if (boldMatch) {
2227
+ report.sections[currentSection].push({
2228
+ type: 'bullet-point',
2229
+ title: cleanText(boldMatch[1]),
2230
+ content: cleanText(boldMatch[2]),
2231
+ });
2232
+ } else {
2233
+ report.sections[currentSection].push({
2234
+ type: 'bullet-point',
2235
+ content: cleanText(listItem),
2236
+ });
2237
+ }
2238
+ continue;
2239
+ }
2240
+
2241
+ report.sections[currentSection].push({
2242
+ type: 'p',
2243
+ content: cleanText(line),
2244
+ });
2245
+ }
2246
+
2247
+ reports.push(report);
2248
+ }
2249
+
2250
+ return reports;
2251
+ }
1996
2252
  }
package/db/schema.ts CHANGED
@@ -182,64 +182,68 @@ export const astroAspects = sqliteTable(
182
182
  );
183
183
 
184
184
  export const astroReports = sqliteTable(
185
- "astro_reports",
185
+ 'astro_reports',
186
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`),
187
+ id: text('id').primaryKey(), // Unique identifier
188
+ type: text('type').notNull(), // 'planet', 'key_point', 'karmic_point', 'arabic_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
198
  },
199
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)
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
203
  ]
204
204
  );
205
205
 
206
206
  export const houseReports = sqliteTable(
207
- "house_reports",
207
+ 'house_reports',
208
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`),
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
218
  },
219
219
  (t) => [
220
- index("house_reports_sign_house_idx").on(t.sign, t.house), // Optimized index for sign + house lookups
220
+ index('house_reports_sign_house_idx').on(t.sign, t.house), // Optimized index for sign + house lookups
221
221
  ]
222
222
  );
223
223
 
224
224
  export const aspectReports = sqliteTable(
225
- "aspect_reports",
225
+ 'aspect_reports',
226
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`),
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
237
  },
238
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
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(
243
+ t.aspectingPlanet,
244
+ t.aspectedPlanet,
245
+ t.aspect
246
+ ), // Optimized for aspect lookups
243
247
  ]
244
248
  );
245
249
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.240",
3
+ "version": "0.0.241",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -1,3 +1,5 @@
1
+ import { AspectType, PointSlug, ZodiacSign } from "./legacy";
2
+
1
3
  export type Provider = 'google' | 'facebook';
2
4
 
3
5
  export type ProviderInfo = {
@@ -31,6 +33,44 @@ export type AstroEntity = {
31
33
  house: number;
32
34
  };
33
35
 
36
+ export interface ContentItem {
37
+ type: "title" | "subtitle" | "p" | "bullet-point";
38
+ title?: string;
39
+ content: string;
40
+ }
41
+
42
+ export interface AstrologicalReport {
43
+ language: string;
44
+ title: ContentItem[];
45
+ sections: Record<string, ContentItem[]>;
46
+ }
47
+
48
+
49
+ export type AstroReportParams =
50
+ | {
51
+ reportType: "sign";
52
+ type: "planet" | "key_point" | "karmic_point" | "arabic_part";
53
+ pointName: PointSlug;
54
+ sign: ZodiacSign;
55
+ }
56
+ | {
57
+ reportType: "house";
58
+ type: "planet" | "karmic_point"; // Only planets and karmic points for house reports
59
+ pointName: PointSlug;
60
+ house: number;
61
+ }
62
+ | {
63
+ reportType: "signInHouse";
64
+ sign: ZodiacSign;
65
+ houseNumber: number;
66
+ }
67
+ | {
68
+ reportType: "aspect";
69
+ aspectingPlanet: PointSlug;
70
+ aspectedPlanet: PointSlug;
71
+ aspectingType: AspectType;
72
+ };
73
+
34
74
  export type NatalChartInterpretation = {
35
75
  planets: Planet[];
36
76
  houses: House[];
@@ -3,18 +3,18 @@ import { BackendBindings } from './cloudflare';
3
3
  import { ControlNetConfig, Gender } from './generic';
4
4
 
5
5
  export const zodiacSigns = [
6
- "aries",
7
- "taurus",
8
- "gemini",
9
- "cancer",
10
- "leo",
11
- "virgo",
12
- "libra",
13
- "scorpio",
14
- "sagittarius",
15
- "capricorn",
16
- "aquarius",
17
- "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
18
  ] as const;
19
19
  export type ZodiacSignSlug = (typeof zodiacSigns)[number];
20
20
 
@@ -22,55 +22,61 @@ export type ZodiacSignSlug = (typeof zodiacSigns)[number];
22
22
  export const houses = Array.from({ length: 12 }, (_, i) => i + 1);
23
23
 
24
24
  // Aspects
25
- export const aspects = ["conjunction", "opposition", "trine", "square", "sextile"] as const;
25
+ export const aspects = [
26
+ 'conjunction',
27
+ 'opposition',
28
+ 'trine',
29
+ 'square',
30
+ 'sextile',
31
+ ] as const;
26
32
  export type AspectType = (typeof aspects)[number];
27
33
 
28
34
  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",
35
+ sun: 'planet',
36
+ moon: 'planet',
37
+ mercury: 'planet',
38
+ venus: 'planet',
39
+ mars: 'planet',
40
+ jupiter: 'planet',
41
+ saturn: 'planet',
42
+ uranus: 'planet',
43
+ neptune: 'planet',
44
+ pluto: 'planet',
45
+ ascendant: 'key_point',
46
+ descendant: 'key_point',
47
+ midheaven: 'key_point',
48
+ imum_coeli: 'key_point',
49
+ north_node: 'karmic_point',
50
+ south_node: 'karmic_point',
51
+ chiron: 'karmic_point',
52
+ lilith: 'arabic_part',
53
+ vertex: 'arabic_part',
54
+ antivertex: 'arabic_part',
55
+ pars_fortuna: 'arabic_part',
56
+ pars_spiritus: 'arabic_part',
57
+ pars_victoria: 'arabic_part',
58
+ pars_amoris: 'arabic_part',
59
+ pars_eros: 'arabic_part',
60
+ pars_fortitudo: 'arabic_part',
61
+ pars_necessitatis: 'arabic_part',
56
62
  };
57
63
 
58
64
  // Subset of 14 points for aspects
59
65
  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",
66
+ 'sun',
67
+ 'moon',
68
+ 'mercury',
69
+ 'venus',
70
+ 'mars',
71
+ 'jupiter',
72
+ 'saturn',
73
+ 'uranus',
74
+ 'neptune',
75
+ 'pluto',
76
+ 'ascendant',
77
+ 'descendant',
78
+ 'midheaven',
79
+ 'imum_coeli',
74
80
  ];
75
81
 
76
82
  export type PointSlug = keyof typeof pointNameMap;
@@ -1,9 +1,14 @@
1
- import { PointSlug, ZodiacSign, zodiacSignMap } from '../../types/scopes/legacy';
1
+ import {
2
+ AspectType,
3
+ PointSlug,
4
+ ZodiacSign,
5
+ zodiacSignMap,
6
+ } from '../../types/scopes/legacy';
2
7
  import { pointNameMap } from './pointNameMap';
3
8
 
4
9
  // Interfaces for each function's parameters
5
10
  interface PlanetOrPointParams {
6
- type: 'planet' | 'key_point' | 'karmic_point' | 'hermetic_part';
11
+ type: 'planet' | 'key_point' | 'karmic_point' | 'arabic_part';
7
12
  sign: ZodiacSign;
8
13
  pointName: PointSlug;
9
14
  }
@@ -16,13 +21,36 @@ interface HouseParams {
16
21
  interface AspectParams {
17
22
  aspectingPlanet: PointSlug;
18
23
  aspectedPlanet: PointSlug;
19
- aspectingType: string; // e.g., "conjunct", "square", "trine"
24
+ aspectingType: AspectType; // e.g., "conjunct", "square", "trine"
25
+ }
26
+
27
+ // Parameters for each prompt type
28
+ interface PlanetOrPointParams {
29
+ type: 'planet' | 'key_point' | 'karmic_point' | 'arabic_part';
30
+ sign: ZodiacSign;
31
+ pointName: PointSlug;
32
+ }
33
+
34
+ interface HousePlacementParams {
35
+ type: 'planet' | 'key_point' | 'karmic_point' | 'arabic_part';
36
+ house: number; // e.g., "1", "2", etc.
37
+ pointName: PointSlug;
38
+ }
39
+
40
+ interface HouseParams {
41
+ houseNumber: number;
42
+ sign: ZodiacSign;
43
+ }
44
+
45
+ interface AspectParams {
46
+ aspectingPlanet: PointSlug;
47
+ aspectedPlanet: PointSlug;
48
+ aspectingType: AspectType;
20
49
  }
21
50
 
22
- // Modular astroPrompts object
23
51
  export const astroPrompts = {
24
52
  planetOrPoint: ({ type, sign, pointName }: PlanetOrPointParams): string => {
25
- const validTypes = ['planet', 'key_point', 'karmic_point', 'hermetic_part'];
53
+ const validTypes = ['planet', 'key_point', 'karmic_point', 'arabic_part'];
26
54
  if (!validTypes.includes(type)) {
27
55
  throw new Error(
28
56
  `Invalid type for planetOrPoint: ${type}. Must be one of ${validTypes.join(
@@ -48,6 +76,37 @@ export const astroPrompts = {
48
76
  `.trim();
49
77
  },
50
78
 
79
+ housePlacement: ({
80
+ type,
81
+ house,
82
+ pointName,
83
+ }: HousePlacementParams): string => {
84
+ const validTypes = ['planet', 'key_point', 'karmic_point', 'arabic_part'];
85
+ if (!validTypes.includes(type)) {
86
+ throw new Error(
87
+ `Invalid type for housePlacement: ${type}. Must be one of ${validTypes.join(
88
+ ', '
89
+ )}`
90
+ );
91
+ }
92
+
93
+ const formattedPointName = pointNameMap[pointName] || pointName;
94
+ return `
95
+ Make me a report for the astrological placement: ${formattedPointName} in House ${house}.
96
+ The response should include an English version and a Portuguese version.
97
+ Use "###" for titles and "####" for subtitles.
98
+ The response format should be exactly like this:
99
+
100
+ -- EN
101
+
102
+ [English Report - structured with headings, sections, and detailed explanations]
103
+
104
+ -- PT
105
+
106
+ [Portuguese Report - structured exactly the same as the English version]
107
+ `.trim();
108
+ },
109
+
51
110
  house: ({ houseNumber, sign }: HouseParams): string => {
52
111
  return `
53
112
  Make me a report for the astrological placement: House ${houseNumber} in ${zodiacSignMap[sign]}.
@@ -88,4 +147,3 @@ export const astroPrompts = {
88
147
  `.trim();
89
148
  },
90
149
  };
91
-
@@ -1,4 +1,11 @@
1
- import { BackendBindings, ChatMessages, Concept, Crown } from '../types';
1
+ import {
2
+ AstroReportParams,
3
+ BackendBindings,
4
+ ChatMessages,
5
+ Concept,
6
+ Crown,
7
+ } from '../types';
8
+ import { astroPrompts } from './astroPrompts';
2
9
  import { conceptPrompts } from './conceptPrompts';
3
10
  import { mapConceptToPlanets } from './mapConceptToPlanets';
4
11
 
@@ -280,4 +287,55 @@ export const buildLLMMessages = (env: BackendBindings) => ({
280
287
  content: `Translate this concept name into Brazilian Portuguese: "${englishName}"`,
281
288
  },
282
289
  ],
290
+
291
+ generateAstroReportContent: ({
292
+ params,
293
+ }: {
294
+ params: AstroReportParams;
295
+ }): ChatMessages => {
296
+ let prompt: string;
297
+
298
+ switch (params.reportType) {
299
+ case 'sign':
300
+ prompt = astroPrompts.planetOrPoint({
301
+ type: params.type,
302
+ sign: params.sign,
303
+ pointName: params.pointName,
304
+ });
305
+ break;
306
+
307
+ case 'house':
308
+ prompt = astroPrompts.housePlacement({
309
+ type: params.type,
310
+ house: params.house,
311
+ pointName: params.pointName,
312
+ });
313
+ break;
314
+
315
+ case 'signInHouse':
316
+ prompt = astroPrompts.house({
317
+ houseNumber: params.houseNumber,
318
+ sign: params.sign,
319
+ });
320
+ break;
321
+
322
+ case 'aspect':
323
+ prompt = astroPrompts.aspect({
324
+ aspectingPlanet: params.aspectingPlanet,
325
+ aspectedPlanet: params.aspectedPlanet,
326
+ aspectingType: params.aspectingType,
327
+ });
328
+ break;
329
+
330
+ default:
331
+ throw new Error(`Unknown report type: ${(params as any).reportType}`);
332
+ }
333
+
334
+ return [
335
+ {
336
+ role: 'user',
337
+ content: prompt,
338
+ },
339
+ ];
340
+ },
283
341
  });
package/utils/index.ts CHANGED
@@ -129,3 +129,11 @@ export function chunkArray<T>(array: T[], size: number): T[][] {
129
129
  }
130
130
  return result;
131
131
  }
132
+
133
+ // Utility function to clean text
134
+ export function cleanText(text: string): string {
135
+ return text.replace(/\"/g, ''); // Replace \" with "
136
+ // Add more replacements here if needed, e.g.:
137
+ // .replace(/\\'/g, "'") // Replace \' with '
138
+ // .replace(/[^\w\s.,!?":'-]/g, "") // Remove all but common punctuation
139
+ }