@zodic/shared 0.0.239 → 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
  }
@@ -0,0 +1,47 @@
1
+ CREATE TABLE `aspect_reports` (
2
+ `id` text PRIMARY KEY NOT NULL,
3
+ `aspecting_planet` text NOT NULL,
4
+ `aspected_planet` text NOT NULL,
5
+ `aspect` text NOT NULL,
6
+ `en_description` text,
7
+ `pt_description` text,
8
+ `en_report` text,
9
+ `pt_report` text,
10
+ `created_at` integer DEFAULT CURRENT_TIMESTAMP,
11
+ `updated_at` integer DEFAULT CURRENT_TIMESTAMP
12
+ );
13
+ --> statement-breakpoint
14
+ CREATE INDEX `aspect_reports_aspecting_idx` ON `aspect_reports` (`aspecting_planet`);--> statement-breakpoint
15
+ CREATE INDEX `aspect_reports_aspected_idx` ON `aspect_reports` (`aspected_planet`);--> statement-breakpoint
16
+ CREATE INDEX `aspect_reports_aspect_idx` ON `aspect_reports` (`aspect`);--> statement-breakpoint
17
+ CREATE INDEX `aspect_reports_combined_idx` ON `aspect_reports` (`aspecting_planet`,`aspected_planet`,`aspect`);--> statement-breakpoint
18
+ CREATE TABLE `astro_reports` (
19
+ `id` text PRIMARY KEY NOT NULL,
20
+ `type` text NOT NULL,
21
+ `name` text NOT NULL,
22
+ `sign` text,
23
+ `house` integer,
24
+ `en_description` text,
25
+ `pt_description` text,
26
+ `en_report` text,
27
+ `pt_report` text,
28
+ `created_at` integer DEFAULT CURRENT_TIMESTAMP,
29
+ `updated_at` integer DEFAULT CURRENT_TIMESTAMP
30
+ );
31
+ --> statement-breakpoint
32
+ CREATE INDEX `astro_reports_type_idx` ON `astro_reports` (`type`);--> statement-breakpoint
33
+ CREATE INDEX `astro_reports_name_sign_idx` ON `astro_reports` (`name`,`sign`);--> statement-breakpoint
34
+ CREATE INDEX `astro_reports_name_house_idx` ON `astro_reports` (`name`,`house`);--> statement-breakpoint
35
+ CREATE TABLE `house_reports` (
36
+ `id` text PRIMARY KEY NOT NULL,
37
+ `sign` text NOT NULL,
38
+ `house` integer NOT NULL,
39
+ `en_description` text,
40
+ `pt_description` text,
41
+ `en_report` text,
42
+ `pt_report` text,
43
+ `created_at` integer DEFAULT CURRENT_TIMESTAMP,
44
+ `updated_at` integer DEFAULT CURRENT_TIMESTAMP
45
+ );
46
+ --> statement-breakpoint
47
+ CREATE INDEX `house_reports_sign_house_idx` ON `house_reports` (`sign`,`house`);