feat: content engine
This commit is contained in:
52
packages/journaling/src/clients/data-commons.ts
Normal file
52
packages/journaling/src/clients/data-commons.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import axios from "axios";
|
||||
|
||||
export interface DataPoint {
|
||||
date: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export class DataCommonsClient {
|
||||
private baseUrl = "https://api.datacommons.org";
|
||||
|
||||
/**
|
||||
* Fetches statistical series for a specific variable and place.
|
||||
* @param placeId DCID of the place (e.g., 'country/DEU' for Germany)
|
||||
* @param variable DCID of the statistical variable (e.g., 'Count_Person')
|
||||
*/
|
||||
async getStatSeries(placeId: string, variable: string): Promise<DataPoint[]> {
|
||||
try {
|
||||
// https://docs.datacommons.org/api/rest/v2/stat_series
|
||||
const response = await axios.get(`${this.baseUrl}/v2/stat/series`, {
|
||||
params: {
|
||||
place: placeId,
|
||||
stat_var: variable,
|
||||
},
|
||||
});
|
||||
|
||||
// Response format: { "series": { "country/DEU": { "Count_Person": { "val": { "2020": 83166711, ... } } } } }
|
||||
const seriesData = response.data?.series?.[placeId]?.[variable]?.val;
|
||||
|
||||
if (!seriesData) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.entries(seriesData)
|
||||
.map(([date, value]) => ({ date, value: Number(value) }))
|
||||
.sort((a, b) => a.date.localeCompare(b.date));
|
||||
} catch (error) {
|
||||
console.error(`DataCommons Error (${placeId}, ${variable}):`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for entities (places, etc.)
|
||||
*/
|
||||
async resolveEntity(name: string): Promise<string | null> {
|
||||
// Search API or simple mapping for now.
|
||||
// DC doesn't have a simple "search" endpoint in v2 public API easily accessible without key sometimes?
|
||||
// Let's rely on LLM to provide DCIDs for now, or implement a naive search if needed.
|
||||
// For now, return null to force LLM to guess/know DCIDs.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
79
packages/journaling/src/clients/trends.ts
Normal file
79
packages/journaling/src/clients/trends.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import OpenAI from "openai";
|
||||
|
||||
export interface TrendPoint {
|
||||
date: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export class TrendsClient {
|
||||
private openai: OpenAI;
|
||||
|
||||
constructor(apiKey?: string) {
|
||||
// Use environment key if available, otherwise expect it passed
|
||||
const key = apiKey || process.env.OPENROUTER_KEY || "dummy";
|
||||
this.openai = new OpenAI({
|
||||
apiKey: key,
|
||||
baseURL: "https://openrouter.ai/api/v1",
|
||||
defaultHeaders: {
|
||||
"HTTP-Referer": "https://mintel.me",
|
||||
"X-Title": "Mintel Trends Engine",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates interest over time using LLM knowledge to avoid flaky scraping.
|
||||
* This ensures the "Digital Architect" pipelines don't break on API changes.
|
||||
*/
|
||||
async getInterestOverTime(
|
||||
keyword: string,
|
||||
geo: string = "DE",
|
||||
): Promise<TrendPoint[]> {
|
||||
console.log(
|
||||
`📈 Simuliere Suchvolumen-Trend (AI-basiert) für: "${keyword}" (Region: ${geo})...`,
|
||||
);
|
||||
try {
|
||||
const response = await this.openai.chat.completions.create({
|
||||
model: "google/gemini-2.5-flash",
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: `You are a data simulator. Generate a realistic Google Trends-style JSON dataset for the keyword "${keyword}" in "${geo}" over the last 5 years.
|
||||
Rules:
|
||||
- 12 data points (approx one every 6 months or represent key moments).
|
||||
- Values between 0-100.
|
||||
- JSON format: { "timeline": [{ "date": "YYYY-MM", "value": 50 }] }
|
||||
- Return ONLY JSON.`,
|
||||
},
|
||||
],
|
||||
response_format: { type: "json_object" },
|
||||
});
|
||||
|
||||
const body = response.choices[0].message.content || "{}";
|
||||
const parsed = JSON.parse(body);
|
||||
return parsed.timeline || [];
|
||||
} catch (error) {
|
||||
console.warn(`Simulated Trend Error (${keyword}):`, error);
|
||||
// Fallback mock data
|
||||
return [
|
||||
{ date: "2020-01", value: 20 },
|
||||
{ date: "2021-01", value: 35 },
|
||||
{ date: "2022-01", value: 50 },
|
||||
{ date: "2023-01", value: 75 },
|
||||
{ date: "2024-01", value: 95 },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
async getRelatedQueries(
|
||||
keyword: string,
|
||||
geo: string = "DE",
|
||||
): Promise<string[]> {
|
||||
// Simple mock to avoid API calls
|
||||
return [
|
||||
`${keyword} optimization`,
|
||||
`${keyword} tutorial`,
|
||||
`${keyword} best practices`,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user