pnpfucius 2.0.0
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.
- package/README.md +396 -0
- package/bin/claude-predict.js +5 -0
- package/bin/pnpfucius.js +8 -0
- package/package.json +71 -0
- package/src/agent.js +1037 -0
- package/src/ai/index.js +6 -0
- package/src/ai/market-generator.js +186 -0
- package/src/ai/resolver.js +172 -0
- package/src/ai/scorer.js +184 -0
- package/src/analytics/aggregator.js +198 -0
- package/src/cli.js +948 -0
- package/src/collateral/privacy-tokens.js +183 -0
- package/src/config.js +128 -0
- package/src/daemon/index.js +321 -0
- package/src/daemon/lifecycle.js +168 -0
- package/src/daemon/scheduler.js +252 -0
- package/src/events/emitter.js +147 -0
- package/src/helius/client.js +221 -0
- package/src/helius/transaction-tracker.js +192 -0
- package/src/helius/webhooks.js +233 -0
- package/src/index.js +139 -0
- package/src/monitoring/news-monitor.js +262 -0
- package/src/monitoring/news-scorer.js +236 -0
- package/src/predict/agent.js +291 -0
- package/src/predict/prompts.js +69 -0
- package/src/predict/slash-commands.js +361 -0
- package/src/predict/tools/analytics-tools.js +83 -0
- package/src/predict/tools/bash-tool.js +87 -0
- package/src/predict/tools/file-tools.js +140 -0
- package/src/predict/tools/index.js +120 -0
- package/src/predict/tools/market-tools.js +851 -0
- package/src/predict/tools/news-tools.js +130 -0
- package/src/predict/ui/renderer.js +215 -0
- package/src/predict/ui/welcome.js +146 -0
- package/src/privacy-markets.js +194 -0
- package/src/storage/market-store.js +418 -0
- package/src/utils/spinner.js +172 -0
package/src/ai/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// AI module exports
|
|
2
|
+
// Claude API-powered market generation, resolution, and scoring
|
|
3
|
+
|
|
4
|
+
export { AIMarketGenerator, createMarketGenerator } from './market-generator.js';
|
|
5
|
+
export { AIResolver, createResolver } from './resolver.js';
|
|
6
|
+
export { AIScorer, createScorer, quickScore } from './scorer.js';
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// AI-powered market generation using Claude API
|
|
2
|
+
// Transforms news headlines into privacy-themed prediction markets
|
|
3
|
+
|
|
4
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
5
|
+
import { getConfig } from '../config.js';
|
|
6
|
+
|
|
7
|
+
const MARKET_GENERATION_PROMPT = `You are an expert at creating prediction market questions for a privacy-focused prediction market on Solana.
|
|
8
|
+
|
|
9
|
+
Given news headlines or topics, generate relevant YES/NO prediction market questions that:
|
|
10
|
+
1. Are clearly verifiable with a definitive outcome
|
|
11
|
+
2. Focus on privacy, regulation, zero-knowledge proofs, encryption, data protection, or surveillance
|
|
12
|
+
3. Have appropriate timeframes (30-365 days typically)
|
|
13
|
+
4. Are interesting enough to attract betting activity
|
|
14
|
+
5. Are NOT already obviously true or false
|
|
15
|
+
|
|
16
|
+
Categories to consider:
|
|
17
|
+
- regulation: Government policies, GDPR, privacy laws, sanctions
|
|
18
|
+
- technology: ZK protocols, encryption standards, privacy tools, Solana privacy features
|
|
19
|
+
- adoption: User growth, TVL milestones, enterprise adoption
|
|
20
|
+
- events: Breaches, scandals, conference announcements, court cases
|
|
21
|
+
|
|
22
|
+
Respond with valid JSON only, no markdown formatting:
|
|
23
|
+
{
|
|
24
|
+
"question": "The yes/no question ending with ?",
|
|
25
|
+
"category": "regulation|technology|adoption|events",
|
|
26
|
+
"categoryName": "Human readable category name",
|
|
27
|
+
"suggestedDurationDays": 30-365,
|
|
28
|
+
"suggestedLiquidityUSDC": 1000-10000,
|
|
29
|
+
"urgency": "breaking|timely|evergreen",
|
|
30
|
+
"reasoning": "Brief explanation of why this is a good market"
|
|
31
|
+
}`;
|
|
32
|
+
|
|
33
|
+
export class AIMarketGenerator {
|
|
34
|
+
constructor(apiKey = null) {
|
|
35
|
+
const config = getConfig();
|
|
36
|
+
this.apiKey = apiKey || config.anthropicApiKey;
|
|
37
|
+
|
|
38
|
+
if (!this.apiKey) {
|
|
39
|
+
throw new Error('ANTHROPIC_API_KEY is required for AI market generation');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.client = new Anthropic({ apiKey: this.apiKey });
|
|
43
|
+
this.model = 'claude-sonnet-4-20250514';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Generate a market question from a news headline
|
|
48
|
+
*/
|
|
49
|
+
async generateFromNews(newsItem) {
|
|
50
|
+
const { title, summary, source, link } = newsItem;
|
|
51
|
+
|
|
52
|
+
const userPrompt = `Generate a prediction market question based on this news:
|
|
53
|
+
|
|
54
|
+
Title: ${title}
|
|
55
|
+
${summary ? `Summary: ${summary}` : ''}
|
|
56
|
+
${source ? `Source: ${source}` : ''}
|
|
57
|
+
|
|
58
|
+
Create a compelling, verifiable YES/NO question that privacy-focused traders would want to bet on.`;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const response = await this.client.messages.create({
|
|
62
|
+
model: this.model,
|
|
63
|
+
max_tokens: 500,
|
|
64
|
+
messages: [
|
|
65
|
+
{ role: 'user', content: MARKET_GENERATION_PROMPT + '\n\n' + userPrompt }
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const content = response.content[0].text;
|
|
70
|
+
const market = JSON.parse(content);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
...market,
|
|
74
|
+
sourceNews: { title, source, link },
|
|
75
|
+
generatedAt: Date.now()
|
|
76
|
+
};
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error instanceof SyntaxError) {
|
|
79
|
+
throw new Error(`Failed to parse AI response as JSON: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Generate multiple markets from a batch of news items
|
|
87
|
+
*/
|
|
88
|
+
async generateBatchFromNews(newsItems, options = {}) {
|
|
89
|
+
const { maxMarkets = 5, minScore = 50 } = options;
|
|
90
|
+
const results = [];
|
|
91
|
+
|
|
92
|
+
for (const item of newsItems.slice(0, maxMarkets)) {
|
|
93
|
+
try {
|
|
94
|
+
// Skip low-relevance items if score provided
|
|
95
|
+
if (item.relevanceScore !== undefined && item.relevanceScore < minScore) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const market = await this.generateFromNews(item);
|
|
100
|
+
results.push({ success: true, market, newsItem: item });
|
|
101
|
+
} catch (error) {
|
|
102
|
+
results.push({ success: false, error: error.message, newsItem: item });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return results;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Generate a market from a topic/theme (not news-based)
|
|
111
|
+
*/
|
|
112
|
+
async generateFromTopic(topic, category = null) {
|
|
113
|
+
const userPrompt = `Generate a prediction market question about: ${topic}
|
|
114
|
+
${category ? `Focus on the "${category}" category.` : ''}
|
|
115
|
+
|
|
116
|
+
Create a compelling, verifiable YES/NO question that privacy-focused traders would want to bet on.`;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const response = await this.client.messages.create({
|
|
120
|
+
model: this.model,
|
|
121
|
+
max_tokens: 500,
|
|
122
|
+
messages: [
|
|
123
|
+
{ role: 'user', content: MARKET_GENERATION_PROMPT + '\n\n' + userPrompt }
|
|
124
|
+
]
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const content = response.content[0].text;
|
|
128
|
+
const market = JSON.parse(content);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
...market,
|
|
132
|
+
sourceTopic: topic,
|
|
133
|
+
generatedAt: Date.now()
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (error instanceof SyntaxError) {
|
|
137
|
+
throw new Error(`Failed to parse AI response as JSON: ${error.message}`);
|
|
138
|
+
}
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Generate multiple diverse markets
|
|
145
|
+
*/
|
|
146
|
+
async generateDiverseMarkets(count = 5) {
|
|
147
|
+
const topics = [
|
|
148
|
+
'GDPR enforcement trends in 2026',
|
|
149
|
+
'Zero-knowledge proof adoption on Solana',
|
|
150
|
+
'Privacy coin regulations in the US',
|
|
151
|
+
'Enterprise adoption of confidential computing',
|
|
152
|
+
'End-to-end encryption legislation',
|
|
153
|
+
'Decentralized identity adoption',
|
|
154
|
+
'Privacy-preserving AI developments',
|
|
155
|
+
'Tornado Cash legal developments',
|
|
156
|
+
'Light Protocol and Solana privacy',
|
|
157
|
+
'Data breach trends and regulations'
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
const categories = ['regulation', 'technology', 'adoption', 'events'];
|
|
161
|
+
const results = [];
|
|
162
|
+
|
|
163
|
+
for (let i = 0; i < count; i++) {
|
|
164
|
+
const topic = topics[i % topics.length];
|
|
165
|
+
const category = categories[i % categories.length];
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const market = await this.generateFromTopic(topic, category);
|
|
169
|
+
results.push({ success: true, market });
|
|
170
|
+
} catch (error) {
|
|
171
|
+
results.push({ success: false, error: error.message, topic });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return results;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Factory function
|
|
181
|
+
*/
|
|
182
|
+
export function createMarketGenerator(apiKey = null) {
|
|
183
|
+
return new AIMarketGenerator(apiKey);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default AIMarketGenerator;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// AI-powered market resolution helper using Claude API
|
|
2
|
+
// Analyzes whether market conditions have been met
|
|
3
|
+
|
|
4
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
5
|
+
import { getConfig } from '../config.js';
|
|
6
|
+
|
|
7
|
+
const RESOLUTION_PROMPT = `You are an expert at determining whether prediction market conditions have been met.
|
|
8
|
+
|
|
9
|
+
Given a prediction market question and the current date, analyze whether:
|
|
10
|
+
1. The condition can be definitively resolved (enough time has passed, event occurred, etc.)
|
|
11
|
+
2. If resolvable, whether the outcome is YES or NO
|
|
12
|
+
3. Your confidence level in the assessment
|
|
13
|
+
|
|
14
|
+
Consider:
|
|
15
|
+
- Time-bound conditions (has the deadline passed?)
|
|
16
|
+
- Verifiable events (did the specific event occur?)
|
|
17
|
+
- Measurable thresholds (was the metric achieved?)
|
|
18
|
+
|
|
19
|
+
Be conservative - if uncertain, indicate the market cannot be resolved yet.
|
|
20
|
+
|
|
21
|
+
Respond with valid JSON only, no markdown formatting:
|
|
22
|
+
{
|
|
23
|
+
"canResolve": true/false,
|
|
24
|
+
"outcome": "yes"|"no"|"unknown",
|
|
25
|
+
"confidence": 0.0-1.0,
|
|
26
|
+
"reasoning": "Detailed explanation of your assessment",
|
|
27
|
+
"sources": ["Any sources or facts you're basing this on"],
|
|
28
|
+
"suggestedAction": "resolve_yes"|"resolve_no"|"wait"|"needs_oracle"
|
|
29
|
+
}`;
|
|
30
|
+
|
|
31
|
+
export class AIResolver {
|
|
32
|
+
constructor(apiKey = null) {
|
|
33
|
+
const config = getConfig();
|
|
34
|
+
this.apiKey = apiKey || config.anthropicApiKey;
|
|
35
|
+
|
|
36
|
+
if (!this.apiKey) {
|
|
37
|
+
throw new Error('ANTHROPIC_API_KEY is required for AI resolution');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.client = new Anthropic({ apiKey: this.apiKey });
|
|
41
|
+
this.model = 'claude-sonnet-4-20250514';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Analyze if a market can be resolved
|
|
46
|
+
*/
|
|
47
|
+
async analyzeResolution(market) {
|
|
48
|
+
const { question, creationTime, endTime, durationDays } = market;
|
|
49
|
+
const now = new Date();
|
|
50
|
+
const createdAt = new Date(creationTime);
|
|
51
|
+
const endsAt = endTime ? new Date(endTime) : null;
|
|
52
|
+
|
|
53
|
+
const userPrompt = `Analyze this prediction market for resolution:
|
|
54
|
+
|
|
55
|
+
Question: ${question}
|
|
56
|
+
|
|
57
|
+
Market Details:
|
|
58
|
+
- Created: ${createdAt.toISOString().split('T')[0]}
|
|
59
|
+
- Duration: ${durationDays} days
|
|
60
|
+
${endsAt ? `- End Date: ${endsAt.toISOString().split('T')[0]}` : ''}
|
|
61
|
+
- Current Date: ${now.toISOString().split('T')[0]}
|
|
62
|
+
|
|
63
|
+
Has enough time passed? Has the condition been met or definitively failed?
|
|
64
|
+
Use your knowledge cutoff to assess current events and facts.`;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const response = await this.client.messages.create({
|
|
68
|
+
model: this.model,
|
|
69
|
+
max_tokens: 800,
|
|
70
|
+
messages: [
|
|
71
|
+
{ role: 'user', content: RESOLUTION_PROMPT + '\n\n' + userPrompt }
|
|
72
|
+
]
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const content = response.content[0].text;
|
|
76
|
+
const analysis = JSON.parse(content);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
...analysis,
|
|
80
|
+
marketAddress: market.address,
|
|
81
|
+
analyzedAt: Date.now()
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error instanceof SyntaxError) {
|
|
85
|
+
throw new Error(`Failed to parse AI response as JSON: ${error.message}`);
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Batch analyze multiple markets for resolution
|
|
93
|
+
*/
|
|
94
|
+
async analyzeMarkets(markets) {
|
|
95
|
+
const results = [];
|
|
96
|
+
|
|
97
|
+
for (const market of markets) {
|
|
98
|
+
try {
|
|
99
|
+
const analysis = await this.analyzeResolution(market);
|
|
100
|
+
results.push({
|
|
101
|
+
success: true,
|
|
102
|
+
market,
|
|
103
|
+
analysis
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
results.push({
|
|
107
|
+
success: false,
|
|
108
|
+
market,
|
|
109
|
+
error: error.message
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return results;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get markets that are ready to resolve
|
|
119
|
+
*/
|
|
120
|
+
async findResolvableMarkets(markets, options = {}) {
|
|
121
|
+
const { minConfidence = 0.8 } = options;
|
|
122
|
+
const analyses = await this.analyzeMarkets(markets);
|
|
123
|
+
|
|
124
|
+
return analyses
|
|
125
|
+
.filter(r => r.success && r.analysis.canResolve && r.analysis.confidence >= minConfidence)
|
|
126
|
+
.map(r => ({
|
|
127
|
+
market: r.market,
|
|
128
|
+
outcome: r.analysis.outcome,
|
|
129
|
+
confidence: r.analysis.confidence,
|
|
130
|
+
reasoning: r.analysis.reasoning,
|
|
131
|
+
suggestedAction: r.analysis.suggestedAction
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Generate a resolution report for a market
|
|
137
|
+
*/
|
|
138
|
+
async generateResolutionReport(market) {
|
|
139
|
+
const analysis = await this.analyzeResolution(market);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
market: {
|
|
143
|
+
address: market.address,
|
|
144
|
+
question: market.question,
|
|
145
|
+
category: market.category
|
|
146
|
+
},
|
|
147
|
+
resolution: {
|
|
148
|
+
canResolve: analysis.canResolve,
|
|
149
|
+
outcome: analysis.outcome,
|
|
150
|
+
confidence: analysis.confidence,
|
|
151
|
+
suggestedAction: analysis.suggestedAction
|
|
152
|
+
},
|
|
153
|
+
details: {
|
|
154
|
+
reasoning: analysis.reasoning,
|
|
155
|
+
sources: analysis.sources
|
|
156
|
+
},
|
|
157
|
+
meta: {
|
|
158
|
+
analyzedAt: new Date().toISOString(),
|
|
159
|
+
model: this.model
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Factory function
|
|
167
|
+
*/
|
|
168
|
+
export function createResolver(apiKey = null) {
|
|
169
|
+
return new AIResolver(apiKey);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export default AIResolver;
|
package/src/ai/scorer.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// AI-powered news relevance scoring using Claude API
|
|
2
|
+
// Scores incoming news items 0-100 on privacy relevance
|
|
3
|
+
|
|
4
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
5
|
+
import { getConfig } from '../config.js';
|
|
6
|
+
|
|
7
|
+
const SCORING_PROMPT = `You are an expert at evaluating news relevance for a privacy-focused prediction market on Solana.
|
|
8
|
+
|
|
9
|
+
Score news items from 0-100 based on their relevance to:
|
|
10
|
+
- Privacy regulations (GDPR, CCPA, federal privacy laws)
|
|
11
|
+
- Privacy technology (zero-knowledge proofs, encryption, confidential computing)
|
|
12
|
+
- Privacy tools (Tor, Signal, privacy coins, mixers like Tornado Cash)
|
|
13
|
+
- Data protection (breaches, surveillance, data rights)
|
|
14
|
+
- Blockchain privacy (Solana privacy features, Token-2022 confidential transfers)
|
|
15
|
+
- Crypto regulations affecting privacy (sanctions, KYC requirements)
|
|
16
|
+
|
|
17
|
+
Scoring guidelines:
|
|
18
|
+
- 90-100: Directly about privacy regulation/technology, major impact, breaking news
|
|
19
|
+
- 70-89: Strongly privacy-related, significant developments
|
|
20
|
+
- 50-69: Moderately relevant, tangential privacy implications
|
|
21
|
+
- 30-49: Loosely related, minor privacy angle
|
|
22
|
+
- 0-29: Not relevant or very weak connection
|
|
23
|
+
|
|
24
|
+
Also suggest if this news could generate an interesting prediction market.
|
|
25
|
+
|
|
26
|
+
Respond with valid JSON only, no markdown formatting:
|
|
27
|
+
{
|
|
28
|
+
"score": 0-100,
|
|
29
|
+
"category": "regulation"|"technology"|"adoption"|"events"|"none",
|
|
30
|
+
"urgency": "breaking"|"timely"|"evergreen",
|
|
31
|
+
"marketPotential": true/false,
|
|
32
|
+
"reasoning": "Brief explanation of the score",
|
|
33
|
+
"suggestedMarketAngle": "If marketPotential is true, a brief market idea"
|
|
34
|
+
}`;
|
|
35
|
+
|
|
36
|
+
export class AIScorer {
|
|
37
|
+
constructor(apiKey = null) {
|
|
38
|
+
const config = getConfig();
|
|
39
|
+
this.apiKey = apiKey || config.anthropicApiKey;
|
|
40
|
+
|
|
41
|
+
if (!this.apiKey) {
|
|
42
|
+
throw new Error('ANTHROPIC_API_KEY is required for AI scoring');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.client = new Anthropic({ apiKey: this.apiKey });
|
|
46
|
+
this.model = 'claude-haiku-4-20250514'; // Use Haiku for speed/cost on scoring
|
|
47
|
+
this.cache = new Map(); // Simple cache for repeated items
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Score a single news item
|
|
52
|
+
*/
|
|
53
|
+
async scoreNews(newsItem) {
|
|
54
|
+
const { title, summary, source } = newsItem;
|
|
55
|
+
|
|
56
|
+
// Check cache
|
|
57
|
+
const cacheKey = title.toLowerCase().trim();
|
|
58
|
+
if (this.cache.has(cacheKey)) {
|
|
59
|
+
return this.cache.get(cacheKey);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const userPrompt = `Score this news item for privacy relevance:
|
|
63
|
+
|
|
64
|
+
Title: ${title}
|
|
65
|
+
${summary ? `Summary: ${summary}` : ''}
|
|
66
|
+
${source ? `Source: ${source}` : ''}`;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const response = await this.client.messages.create({
|
|
70
|
+
model: this.model,
|
|
71
|
+
max_tokens: 300,
|
|
72
|
+
messages: [
|
|
73
|
+
{ role: 'user', content: SCORING_PROMPT + '\n\n' + userPrompt }
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const content = response.content[0].text;
|
|
78
|
+
const result = JSON.parse(content);
|
|
79
|
+
|
|
80
|
+
const scored = {
|
|
81
|
+
...result,
|
|
82
|
+
newsItem: { title, source },
|
|
83
|
+
scoredAt: Date.now()
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Cache result
|
|
87
|
+
this.cache.set(cacheKey, scored);
|
|
88
|
+
|
|
89
|
+
return scored;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof SyntaxError) {
|
|
92
|
+
throw new Error(`Failed to parse AI response as JSON: ${error.message}`);
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Score multiple news items in batch
|
|
100
|
+
*/
|
|
101
|
+
async scoreBatch(newsItems) {
|
|
102
|
+
const results = [];
|
|
103
|
+
|
|
104
|
+
for (const item of newsItems) {
|
|
105
|
+
try {
|
|
106
|
+
const score = await this.scoreNews(item);
|
|
107
|
+
results.push({ success: true, ...score });
|
|
108
|
+
} catch (error) {
|
|
109
|
+
results.push({
|
|
110
|
+
success: false,
|
|
111
|
+
error: error.message,
|
|
112
|
+
newsItem: item
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return results;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Filter news items by minimum score
|
|
122
|
+
*/
|
|
123
|
+
async filterByRelevance(newsItems, minScore = 50) {
|
|
124
|
+
const scored = await this.scoreBatch(newsItems);
|
|
125
|
+
|
|
126
|
+
return scored
|
|
127
|
+
.filter(r => r.success && r.score >= minScore)
|
|
128
|
+
.sort((a, b) => b.score - a.score);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get high-potential market candidates from news
|
|
133
|
+
*/
|
|
134
|
+
async findMarketCandidates(newsItems, options = {}) {
|
|
135
|
+
const { minScore = 60 } = options;
|
|
136
|
+
const scored = await this.scoreBatch(newsItems);
|
|
137
|
+
|
|
138
|
+
return scored
|
|
139
|
+
.filter(r => r.success && r.marketPotential && r.score >= minScore)
|
|
140
|
+
.sort((a, b) => b.score - a.score)
|
|
141
|
+
.map(r => ({
|
|
142
|
+
title: r.newsItem.title,
|
|
143
|
+
source: r.newsItem.source,
|
|
144
|
+
score: r.score,
|
|
145
|
+
category: r.category,
|
|
146
|
+
urgency: r.urgency,
|
|
147
|
+
marketAngle: r.suggestedMarketAngle
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Clear the score cache
|
|
153
|
+
*/
|
|
154
|
+
clearCache() {
|
|
155
|
+
this.cache.clear();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get cache stats
|
|
160
|
+
*/
|
|
161
|
+
getCacheStats() {
|
|
162
|
+
return {
|
|
163
|
+
size: this.cache.size,
|
|
164
|
+
entries: Array.from(this.cache.keys())
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Factory function
|
|
171
|
+
*/
|
|
172
|
+
export function createScorer(apiKey = null) {
|
|
173
|
+
return new AIScorer(apiKey);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Quick scoring function for single items
|
|
178
|
+
*/
|
|
179
|
+
export async function quickScore(newsItem, apiKey = null) {
|
|
180
|
+
const scorer = new AIScorer(apiKey);
|
|
181
|
+
return scorer.scoreNews(newsItem);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export default AIScorer;
|