@zodic/shared 0.0.249 → 0.0.250

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.
@@ -10,6 +10,7 @@ import { v4 as uuidv4 } from 'uuid';
10
10
  import { cleanText, schema } from '../..';
11
11
  import {
12
12
  aspectReports,
13
+ astroDescriptionTemplates,
13
14
  astroFeatureReports,
14
15
  astroReports,
15
16
  conceptsData,
@@ -18,8 +19,11 @@ import {
18
19
  import {
19
20
  AstrologicalReport,
20
21
  AstroReportParams,
22
+ AstroReportRow,
23
+ ChatMessages,
21
24
  Concept,
22
25
  ControlNetConfig,
26
+ DescriptionTemplateRow,
23
27
  Languages,
24
28
  } from '../../types';
25
29
  import {
@@ -27,6 +31,7 @@ import {
27
31
  sizes,
28
32
  StructuredConceptContent,
29
33
  } from '../../types/scopes/legacy';
34
+ import { astroPrompts } from '../../utils/astroPrompts';
30
35
  import { leonardoInitImages } from '../../utils/initImages';
31
36
  import { buildConceptKVKey } from '../../utils/KVKeysBuilders';
32
37
  import { AppContext } from '../base/AppContext';
@@ -2006,132 +2011,105 @@ export class ConceptService {
2006
2011
  console.log(`✅ Stored duplicate keys for ${conceptSlug} in cache.`);
2007
2012
  }
2008
2013
 
2009
- // ? ASTRO PROCESSING
2010
2014
  async generateAstroReportContent(
2011
- params: AstroReportParams,
2015
+ params: AstroReportParams | { entityType: string; name: string; override?: boolean },
2012
2016
  override: boolean = false
2013
2017
  ): Promise<void> {
2014
- console.log(
2015
- `🚀 Generating report content for ${JSON.stringify(
2016
- params
2017
- )}, override: ${override}`
2018
- );
2018
+ console.log(`🚀 Generating content for ${JSON.stringify(params)}, override: ${override}`);
2019
2019
 
2020
2020
  const db = drizzle(this.context.env.DB);
2021
2021
  let table, whereClause;
2022
2022
 
2023
- // Log the params for debugging
2024
- console.log(
2025
- `🔍 Received params: reportType=${params.reportType}, featureType=${
2026
- (params as any).featureType || 'N/A'
2027
- }, subtype=${(params as any).subtype || 'N/A'}`
2028
- );
2029
-
2030
- // Determine table and where clause
2031
- switch (params.reportType) {
2032
- case 'sign':
2033
- console.log(
2034
- `🗂️ Handling sign report: type=${params.type}, pointName=${params.pointName}, sign=${params.sign}`
2035
- );
2036
- table = astroReports;
2037
- whereClause = and(
2038
- eq(astroReports.name, params.pointName),
2039
- eq(astroReports.sign, params.sign),
2040
- isNull(astroReports.house)
2041
- );
2042
- break;
2043
- case 'house':
2044
- console.log(
2045
- `🗂️ Handling house report: type=${params.type}, pointName=${params.pointName}, house=${params.house}`
2046
- );
2047
- table = astroReports;
2048
- whereClause = and(
2049
- eq(astroReports.name, params.pointName),
2050
- eq(astroReports.house, params.house),
2051
- isNull(astroReports.sign)
2052
- );
2053
- break;
2054
- case 'signInHouse':
2055
- console.log(
2056
- `🗂️ Handling signInHouse report: sign=${params.sign}, houseNumber=${params.houseNumber}`
2057
- );
2058
- table = houseReports;
2059
- whereClause = and(
2060
- eq(houseReports.sign, params.sign),
2061
- eq(houseReports.house, params.houseNumber)
2062
- );
2063
- break;
2064
- case 'aspect':
2065
- console.log(
2066
- `🗂️ Handling aspect report: aspectingPlanet=${params.aspectingPlanet}, aspectedPlanet=${params.aspectedPlanet}, aspectingType=${params.aspectingType}`
2067
- );
2068
- table = aspectReports;
2069
- whereClause = and(
2070
- eq(aspectReports.aspectingPlanet, params.aspectingPlanet),
2071
- eq(aspectReports.aspectedPlanet, params.aspectedPlanet),
2072
- eq(aspectReports.aspect, params.aspectingType)
2073
- );
2074
- break;
2075
- case 'feature':
2076
- console.log(
2077
- `🗂️ Handling feature report: featureType=${
2078
- params.featureType
2079
- }, ${JSON.stringify(params)}`
2080
- );
2081
- table = astroFeatureReports;
2082
- let nameValue: string;
2083
- if (params.featureType === 'element') {
2084
- if (!('subtype' in params))
2085
- throw new Error('Missing subtype for element feature report');
2086
- switch (params.subtype) {
2087
- case 'balanced':
2088
- nameValue = 'balanced';
2089
- break;
2090
- case 'pure':
2091
- if (!('dominantElement' in params))
2092
- throw new Error(
2093
- 'Missing dominantElement for pure element report'
2094
- );
2095
- nameValue = params.dominantElement;
2096
- break;
2097
- case 'preponderant_lacking':
2098
- if (
2099
- !('dominantElement' in params) ||
2100
- !('lackingElement' in params)
2101
- )
2102
- throw new Error(
2103
- 'Missing dominantElement or lackingElement for preponderant_lacking element report'
2104
- );
2105
- nameValue = `${params.dominantElement}-${params.lackingElement}`;
2106
- break;
2107
- default:
2108
- throw new Error(`Unknown element subtype: ${params}`);
2023
+ let report: AstroReportRow | DescriptionTemplateRow;
2024
+ const isReport = "reportType" in params;
2025
+
2026
+ if (isReport) {
2027
+ // Handle existing report types (sign, house, signInHouse, aspect, feature)
2028
+ switch (params.reportType) {
2029
+ case "sign":
2030
+ console.log(`🗂️ Handling sign report: type=${params.type}, pointName=${params.pointName}, sign=${params.sign}`);
2031
+ table = astroReports;
2032
+ whereClause = and(
2033
+ eq(astroReports.name, params.pointName),
2034
+ eq(astroReports.sign, params.sign),
2035
+ isNull(astroReports.house)
2036
+ );
2037
+ break;
2038
+ case "house":
2039
+ console.log(`🗂️ Handling house report: type=${params.type}, pointName=${params.pointName}, house=${params.house}`);
2040
+ table = astroReports;
2041
+ whereClause = and(
2042
+ eq(astroReports.name, params.pointName),
2043
+ eq(astroReports.house, params.house),
2044
+ isNull(astroReports.sign)
2045
+ );
2046
+ break;
2047
+ case "signInHouse":
2048
+ console.log(`🗂️ Handling signInHouse report: sign=${params.sign}, houseNumber=${params.houseNumber}`);
2049
+ table = houseReports;
2050
+ whereClause = and(
2051
+ eq(houseReports.sign, params.sign),
2052
+ eq(houseReports.house, params.houseNumber)
2053
+ );
2054
+ break;
2055
+ case "aspect":
2056
+ console.log(`🗂️ Handling aspect report: aspectingPlanet=${params.aspectingPlanet}, aspectedPlanet=${params.aspectedPlanet}, aspectingType=${params.aspectingType}`);
2057
+ table = aspectReports;
2058
+ whereClause = and(
2059
+ eq(aspectReports.aspectingPlanet, params.aspectingPlanet),
2060
+ eq(aspectReports.aspectedPlanet, params.aspectedPlanet),
2061
+ eq(aspectReports.aspect, params.aspectingType)
2062
+ );
2063
+ break;
2064
+ case "feature":
2065
+ console.log(`🗂️ Handling feature report: featureType=${params.featureType}, ${JSON.stringify(params)}`);
2066
+ table = astroFeatureReports;
2067
+ let nameValue: string;
2068
+ if (params.featureType === "element") {
2069
+ if (!("subtype" in params)) throw new Error("Missing subtype for element feature report");
2070
+ switch (params.subtype) {
2071
+ case "balanced":
2072
+ nameValue = "balanced";
2073
+ break;
2074
+ case "pure":
2075
+ if (!("dominantElement" in params)) throw new Error("Missing dominantElement for pure element report");
2076
+ nameValue = params.dominantElement;
2077
+ break;
2078
+ case "preponderant_lacking":
2079
+ if (!("dominantElement" in params) || !("lackingElement" in params)) throw new Error("Missing dominantElement or lackingElement for preponderant_lacking element report");
2080
+ nameValue = `${params.dominantElement}-${params.lackingElement}`;
2081
+ break;
2082
+ default:
2083
+ throw new Error(`Unknown element subtype: ${(params as any).subtype}`);
2084
+ }
2085
+ } else {
2086
+ if (!("name" in params)) throw new Error("Missing name for non-element feature report");
2087
+ nameValue = params.name;
2109
2088
  }
2110
- } else {
2111
- if (!('name' in params))
2112
- throw new Error('Missing name for non-element feature report');
2113
- nameValue = params.name;
2114
- }
2115
- whereClause = and(
2116
- eq(astroFeatureReports.featureType, params.featureType),
2117
- eq(astroFeatureReports.name, nameValue)
2118
- );
2119
- break;
2120
- default:
2121
- console.error(
2122
- `❌ Unknown report type: ${params}. Params: ${JSON.stringify(params)}`
2123
- );
2124
- throw new Error(`Unknown report type: ${params}`);
2125
- }
2126
-
2127
- const report = await db.select().from(table).where(whereClause).get();
2128
- if (!report) {
2129
- console.error(`❌ No report found for ${JSON.stringify(params)}`);
2130
- throw new Error(`❌ No report found for ${JSON.stringify(params)}`);
2089
+ whereClause = and(
2090
+ eq(astroFeatureReports.featureType, params.featureType),
2091
+ eq(astroFeatureReports.name, nameValue)
2092
+ );
2093
+ break;
2094
+ default:
2095
+ throw new Error(`Unknown report type: ${(params as any).reportType}`);
2096
+ }
2097
+ report = await db.select().from(table as any).where(whereClause).get() as AstroReportRow;
2098
+ } else {
2099
+ // Handle description templates
2100
+ console.log(`🗂️ Handling description template: entityType=${params.entityType}, name=${params.name}`);
2101
+ table = astroDescriptionTemplates;
2102
+ whereClause = and(
2103
+ eq(astroDescriptionTemplates.entityType, params.entityType),
2104
+ eq(astroDescriptionTemplates.name, params.name)
2105
+ );
2106
+ report = await db.select().from(table as any).where(whereClause).get() as DescriptionTemplateRow;
2131
2107
  }
2132
2108
 
2133
2109
  const id = report.id;
2134
- const hasContent = report.enReport && report.ptReport;
2110
+ const hasContent = isReport
2111
+ ? (report as AstroReportRow).enReport && (report as AstroReportRow).ptReport
2112
+ : (report as DescriptionTemplateRow).enDescription && (report as DescriptionTemplateRow).ptDescription;
2135
2113
 
2136
2114
  if (!override && hasContent) {
2137
2115
  console.log(`⚡ Content already exists for ${id}, skipping`);
@@ -2142,57 +2120,57 @@ export class ConceptService {
2142
2120
  const maxAttempts = 2;
2143
2121
 
2144
2122
  while (attempts < maxAttempts) {
2145
- let phase = 'generation';
2123
+ let phase = "generation";
2146
2124
  try {
2147
2125
  attempts++;
2148
- console.log(
2149
- `🔄 Attempt ${attempts} to generate report content for ${id}...`
2150
- );
2126
+ console.log(`🔄 Attempt ${attempts} to generate ${isReport ? "report" : "description"} content for ${id}...`);
2151
2127
 
2152
- const messages = this.context
2153
- .buildLLMMessages()
2154
- .generateAstroReportContent({ params });
2155
- console.log(`📨 Sending messages to AI: ${JSON.stringify(messages)}`);
2128
+ let messages: ChatMessages;
2129
+ if (isReport) {
2130
+ messages = this.context.buildLLMMessages().generateAstroReportContent({ params: params as AstroReportParams });
2131
+ } else {
2132
+ messages = this.generateDescriptionMessages(params);
2133
+ }
2156
2134
 
2157
- const response = await this.context
2158
- .api()
2159
- .callTogether.single(messages, {});
2135
+ console.log(`📨 Sending messages to AI: ${JSON.stringify(messages)}`);
2136
+ const response = await this.context.api().callTogether.single(messages, {});
2160
2137
  if (!response) {
2161
2138
  throw new Error(`❌ AI returned an empty response for ${id}`);
2162
2139
  }
2163
2140
 
2164
- phase = 'parsing';
2165
- console.log(
2166
- `📝 Received AI response for ${id}: ${response.slice(0, 200)}${
2167
- response.length > 200 ? '...' : ''
2168
- }`
2169
- );
2170
-
2171
- const { enReport, ptReport } =
2172
- await this.parseAstrologicalReportContent(
2173
- params.reportType,
2174
- response
2175
- );
2176
-
2177
- console.log(`💾 Storing report content for ${id}`);
2178
- await db
2179
- .update(table)
2180
- .set({
2181
- enReport: JSON.stringify(enReport),
2182
- ptReport: JSON.stringify(ptReport),
2183
- })
2184
- .where(eq(table.id, id))
2185
- .run();
2141
+ phase = "parsing";
2142
+ console.log(`📝 Received AI response for ${id}: ${response.slice(0, 200)}${response.length > 200 ? "..." : ""}`);
2143
+
2144
+ if (isReport) {
2145
+ const { enReport, ptReport } = await this.parseAstrologicalReportContent((params as AstroReportParams).reportType, response);
2146
+ console.log(`💾 Storing report content for ${id}`);
2147
+ await db
2148
+ .update(table as any)
2149
+ .set({
2150
+ enReport: JSON.stringify(enReport),
2151
+ ptReport: JSON.stringify(ptReport),
2152
+ })
2153
+ .where(eq((table as any).id, id))
2154
+ .run();
2155
+ } else {
2156
+ const { enDescription, ptDescription } = await this.parseDescriptionContent(response);
2157
+ console.log(`💾 Storing description content for ${id}`);
2158
+ await db
2159
+ .update(table as any)
2160
+ .set({
2161
+ enDescription,
2162
+ ptDescription,
2163
+ })
2164
+ .where(eq(astroDescriptionTemplates.id, id))
2165
+ .run();
2166
+ }
2186
2167
 
2187
- console.log(`✅ Report content stored for ${id}`);
2168
+ console.log(`✅ ${isReport ? "Report" : "Description"} content stored for ${id}`);
2188
2169
  return;
2189
2170
  } catch (error) {
2190
- console.error(
2191
- `❌ Attempt ${attempts} failed at phase: ${phase} for ${id}`,
2192
- (error as Error).message
2193
- );
2171
+ console.error(`❌ Attempt ${attempts} failed at phase: ${phase} for ${id}`, (error as Error).message);
2194
2172
 
2195
- const failureKey = `failures:astro:${params.reportType}:${id}`;
2173
+ const failureKey = `failures:astro:${isReport ? (params as AstroReportParams).reportType : "description"}:${id}`;
2196
2174
  await this.context.kvConceptFailuresStore().put(
2197
2175
  failureKey,
2198
2176
  JSON.stringify({
@@ -2205,20 +2183,78 @@ export class ConceptService {
2205
2183
  );
2206
2184
 
2207
2185
  if (attempts >= maxAttempts) {
2208
- console.error(
2209
- `🚨 All ${maxAttempts} attempts failed at phase ${phase} for ${id}`
2210
- );
2211
- throw new Error(
2212
- `Failed to generate content for ${id} after ${maxAttempts} attempts`
2213
- );
2186
+ console.error(`🚨 All ${maxAttempts} attempts failed at phase ${phase} for ${id}`);
2187
+ throw new Error(`Failed to generate ${isReport ? "report" : "description"} for ${id} after ${maxAttempts} attempts`);
2214
2188
  }
2215
2189
 
2216
- console.log('🔁 Retrying...');
2190
+ console.log("🔁 Retrying...");
2217
2191
  await new Promise((resolve) => setTimeout(resolve, 2000));
2218
2192
  }
2219
2193
  }
2220
2194
  }
2221
2195
 
2196
+ private generateDescriptionMessages({
2197
+ entityType,
2198
+ name,
2199
+ isGeneric,
2200
+ }: {
2201
+ entityType: string;
2202
+ name: string;
2203
+ isGeneric?: boolean;
2204
+ }): ChatMessages {
2205
+ const prompt = astroPrompts.makeIntroDescription({
2206
+ entityType,
2207
+ name,
2208
+ isGeneric,
2209
+ });
2210
+ return [{ role: 'user', content: prompt }];
2211
+ }
2212
+
2213
+ async parseDescriptionContent(
2214
+ response: string
2215
+ ): Promise<{ enDescription: string; ptDescription: string }> {
2216
+ console.log(`📌 [START] Parsing description content`);
2217
+
2218
+ try {
2219
+ const enMatch = response.match(/-- EN\s*([\s\S]+?)-- PT/);
2220
+ const ptMatch = response.match(/-- PT\s*([\s\S]+)/);
2221
+
2222
+ if (!enMatch || !ptMatch) {
2223
+ throw new Error(`❌ Missing EN or PT description in response`);
2224
+ }
2225
+
2226
+ const enDescription = enMatch[1].trim();
2227
+ const ptDescription = ptMatch[1].trim();
2228
+
2229
+ console.log(
2230
+ `✅ Parsed EN description: ${enDescription.slice(0, 100)}${
2231
+ enDescription.length > 100 ? '...' : ''
2232
+ }`
2233
+ );
2234
+ console.log(
2235
+ `✅ Parsed PT description: ${ptDescription.slice(0, 100)}${
2236
+ ptDescription.length > 100 ? '...' : ''
2237
+ }`
2238
+ );
2239
+
2240
+ return { enDescription, ptDescription };
2241
+ } catch (error) {
2242
+ console.error(`❌ Parsing failed for description`);
2243
+
2244
+ const failureKey = `failures:astro:parsing:description:${new Date().toISOString()}`;
2245
+ await this.context.kvConceptFailuresStore().put(
2246
+ failureKey,
2247
+ JSON.stringify({
2248
+ error: (error as Error).message,
2249
+ response,
2250
+ timestamp: new Date().toISOString(),
2251
+ })
2252
+ );
2253
+
2254
+ throw error;
2255
+ }
2256
+ }
2257
+
2222
2258
  async parseAstrologicalReportContent(
2223
2259
  reportType: AstroReportParams['reportType'],
2224
2260
  response: string
package/db/schema.ts CHANGED
@@ -189,8 +189,10 @@ export const astroReports = sqliteTable(
189
189
  name: text('name').notNull(), // Slug (e.g., 'sun', 'pars_fortuna')
190
190
  sign: text('sign'), // Slug (e.g., 'aries', 'taurus') - nullable for house-only reports
191
191
  house: integer('house'), // Nullable, only for applicable placements
192
- enDescription: text('en_description'), // General explanation of the placement
193
- ptDescription: text('pt_description'),
192
+ descriptionTemplateId: text('description_template_id').references(
193
+ () => astroDescriptionTemplates.id,
194
+ { onDelete: 'set null' }
195
+ ), // NEW: Foreign key to astro_description_templates.id
194
196
  enReport: text('en_report'), // Full report content in English
195
197
  ptReport: text('pt_report'), // Full report content in Portuguese
196
198
  createdAt: integer('created_at').default(sql`CURRENT_TIMESTAMP`),
@@ -209,8 +211,10 @@ export const houseReports = sqliteTable(
209
211
  id: text('id').primaryKey(), // Unique identifier
210
212
  sign: text('sign').notNull(), // Lowercase slug (e.g., 'aries', 'taurus')
211
213
  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
+ descriptionTemplateId: text('description_template_id').references(
215
+ () => astroDescriptionTemplates.id,
216
+ { onDelete: 'set null' }
217
+ ), // NEW: Foreign key to astro_description_templates.id
214
218
  enReport: text('en_report'), // Full report content in English
215
219
  ptReport: text('pt_report'), // Full report content in Portuguese
216
220
  createdAt: integer('created_at').default(sql`CURRENT_TIMESTAMP`),
@@ -218,6 +222,7 @@ export const houseReports = sqliteTable(
218
222
  },
219
223
  (t) => [
220
224
  index('house_reports_sign_house_idx').on(t.sign, t.house), // Optimized index for sign + house lookups
225
+ index('house_reports_description_template_idx').on(t.descriptionTemplateId), // NEW: Index for foreign key performance
221
226
  ]
222
227
  );
223
228
 
@@ -228,8 +233,10 @@ export const aspectReports = sqliteTable(
228
233
  aspectingPlanet: text('aspecting_planet').notNull(), // Planet/point initiating the aspect (e.g., 'sun', 'mars')
229
234
  aspectedPlanet: text('aspected_planet').notNull(), // Planet/point receiving the aspect (e.g., 'moon', 'venus')
230
235
  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'),
236
+ descriptionTemplateId: text('description_template_id').references(
237
+ () => astroDescriptionTemplates.id,
238
+ { onDelete: 'set null' }
239
+ ), // NEW: Foreign key to astro_description_templates.id
233
240
  enReport: text('en_report'), // Full report content in English
234
241
  ptReport: text('pt_report'), // Full report content in Portuguese
235
242
  createdAt: integer('created_at').default(sql`CURRENT_TIMESTAMP`),
@@ -239,11 +246,8 @@ export const aspectReports = sqliteTable(
239
246
  index('aspect_reports_aspecting_idx').on(t.aspectingPlanet),
240
247
  index('aspect_reports_aspected_idx').on(t.aspectedPlanet),
241
248
  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
249
+ index('aspect_reports_combined_idx').on(t.aspectingPlanet, t.aspectedPlanet, t.aspect),
250
+ index('aspect_reports_description_template_idx').on(t.descriptionTemplateId), // NEW: Index for foreign key performance
247
251
  ]
248
252
  );
249
253
 
@@ -251,17 +255,38 @@ export const astroFeatureReports = sqliteTable(
251
255
  'astro_feature_reports',
252
256
  {
253
257
  id: text('id').primaryKey(), // Unique identifier
254
- featureType: text('feature_type').notNull(), // e.g., "moon_phase", "element", "mode", "hemisphere_east_west", "hemisphere_north_south", "dominant_sign"
255
- name: text('name').notNull(), // e.g., "last-quarter-moon", "fire", "cardinal", "east-west", "north-south", "capricorn"
256
- enDescription: text('en_description'), // Introductory text in English
257
- ptDescription: text('pt_description'), // Introductory text in Portuguese
258
+ featureType: text('feature_type').notNull(), // e.g., "moon_phase", "element", "mode", "hemisphere_east_west", "hemisphere_north_south"
259
+ name: text('name').notNull(), // e.g., "last-quarter-moon", "fire", "cardinal", "east", "north"
260
+ descriptionTemplateId: text('description_template_id').references(
261
+ () => astroDescriptionTemplates.id,
262
+ { onDelete: 'set null' }
263
+ ), // NEW: Foreign key to astro_description_templates.id
258
264
  enReport: text('en_report'), // Full report content in English (JSON string)
259
265
  ptReport: text('pt_report'), // Full report content in Portuguese (JSON string)
260
266
  createdAt: integer('created_at').default(sql`CURRENT_TIMESTAMP`),
261
267
  updatedAt: integer('updated_at').default(sql`CURRENT_TIMESTAMP`),
262
268
  },
263
269
  (t) => [
264
- index('astro_feature_reports_type_name_idx').on(t.featureType, t.name), // Optimized for lookups by type and name
270
+ index('astro_feature_reports_type_name_idx').on(t.featureType, t.name),
271
+ index('astro_feature_reports_description_template_idx').on(t.descriptionTemplateId), // NEW: Index for foreign key performance
272
+ ]
273
+ );
274
+
275
+ export const astroDescriptionTemplates = sqliteTable(
276
+ 'astro_description_templates',
277
+ {
278
+ id: text('id').primaryKey(), // Unique identifier
279
+ entityType: text('entity_type').notNull(), // e.g., "planet", "key_point", "karmic_point", "arabic_part", "house", "aspect", "moon_phase", "element", "mode", "hemisphere_east_west", "hemisphere_north_south"
280
+ name: text('name').notNull(), // Slug (e.g., "last-quarter-moon", "fire", "cardinal", "east", "house_1")
281
+ enName: text('en_name'), // Optional human-readable name in English (e.g., "Last Quarter Moon", "Fire")
282
+ ptName: text('pt_name'), // Optional human-readable name in Portuguese (e.g., "Lua Minguante", "Fogo")
283
+ enDescription: text('en_description').notNull(), // Shared 150–180 word intro in English
284
+ ptDescription: text('pt_description').notNull(), // Shared 150–180 word intro in Portuguese
285
+ createdAt: integer('created_at').default(sql`CURRENT_TIMESTAMP`),
286
+ updatedAt: integer('updated_at').default(sql`CURRENT_TIMESTAMP`),
287
+ },
288
+ (t) => [
289
+ index('astro_description_templates_type_name_idx').on(t.entityType, t.name), // Optimized for lookups by type and slug
265
290
  ]
266
291
  );
267
292
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.249",
3
+ "version": "0.0.250",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -45,6 +45,31 @@ export interface AstrologicalReport {
45
45
  sections: Record<string, ContentItem[]>;
46
46
  }
47
47
 
48
+ export interface AstroReportRow {
49
+ id: string;
50
+ type?: string; // For astroReports
51
+ name?: string;
52
+ sign?: string;
53
+ house?: number;
54
+ featureType?: string; // For astroFeatureReports
55
+ enReport: string | null;
56
+ ptReport: string | null;
57
+ enDescription?: string | null;
58
+ ptDescription?: string | null;
59
+ createdAt: number | null;
60
+ updatedAt: number | null;
61
+ }
62
+
63
+ export interface DescriptionTemplateRow {
64
+ id: string;
65
+ entityType: string;
66
+ name: string;
67
+ enDescription: string | null;
68
+ ptDescription: string | null;
69
+ createdAt: number | null;
70
+ updatedAt: number | null;
71
+ }
72
+
48
73
  export type AstroReportParams =
49
74
  | {
50
75
  reportType: 'sign';
@@ -49,6 +49,12 @@ interface AspectParams {
49
49
  aspectingType: AspectType;
50
50
  }
51
51
 
52
+ interface DescriptionParams {
53
+ entityType: string;
54
+ name: string;
55
+ isGeneric?: boolean; // Flag for generic descriptions (aspects, moon phases, elements, modes, hemispheres)
56
+ }
57
+
52
58
  export const astroPrompts = {
53
59
  planetOrPoint: ({ type, sign, pointName }: PlanetOrPointParams): string => {
54
60
  const validTypes = ['planet', 'key_point', 'karmic_point', 'arabic_part'];
@@ -224,4 +230,24 @@ export const astroPrompts = {
224
230
  throw new Error(`Unknown feature type in params: ${params}`);
225
231
  }
226
232
  },
233
+
234
+ makeIntroDescription: ({ entityType, name, isGeneric }: DescriptionParams): string => {
235
+ const baseFormat = `
236
+ Make me an introductory, explanatory description of 150 to 180 words about the astrological ${entityType}.
237
+ The result must have an English version and a Portuguese version.
238
+ The result format must be exactly like the following:
239
+ -- EN
240
+ [Single block of plain text with English version]
241
+ -- PT
242
+ [Single block of plain text with Portuguese version]
243
+ `.trim();
244
+
245
+ if (isGeneric) {
246
+ return `
247
+ ${baseFormat.replace("astrological", "astrological concept representing").replace("about the astrological", "about what the")}
248
+ `.trim();
249
+ } else {
250
+ return `${baseFormat} ${name ? `named ${name}.` : ""}`.trim();
251
+ }
252
+ },
227
253
  };