@mathrunet/masamune_firebase_workflow_marketing 3.8.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/CHANGELOG.md +361 -0
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/assets/fonts/NotoSans-Bold.ttf +1439 -0
- package/assets/fonts/NotoSans-Regular.ttf +1439 -0
- package/assets/fonts/NotoSans.ttf +0 -0
- package/assets/fonts/NotoSansJP-Bold.ttf +0 -0
- package/assets/fonts/NotoSansJP-Regular.ttf +0 -0
- package/assets/fonts/NotoSansKR-Bold.ttf +1439 -0
- package/assets/fonts/NotoSansKR-Regular.ttf +1439 -0
- package/assets/fonts/NotoSansKR.ttf +0 -0
- package/assets/fonts/NotoSansSC-Bold.ttf +1439 -0
- package/assets/fonts/NotoSansSC-Regular.ttf +1439 -0
- package/assets/fonts/NotoSansSC.ttf +0 -0
- package/dist/clients/app_store_client.d.ts +54 -0
- package/dist/clients/app_store_client.js +280 -0
- package/dist/clients/app_store_client.js.map +1 -0
- package/dist/clients/firebase_analytics_client.d.ts +67 -0
- package/dist/clients/firebase_analytics_client.js +411 -0
- package/dist/clients/firebase_analytics_client.js.map +1 -0
- package/dist/clients/github_content_client.d.ts +83 -0
- package/dist/clients/github_content_client.js +443 -0
- package/dist/clients/github_content_client.js.map +1 -0
- package/dist/clients/google_play_client.d.ts +49 -0
- package/dist/clients/google_play_client.js +275 -0
- package/dist/clients/google_play_client.js.map +1 -0
- package/dist/functions/analyze_github_init.d.ts +37 -0
- package/dist/functions/analyze_github_init.js +299 -0
- package/dist/functions/analyze_github_init.js.map +1 -0
- package/dist/functions/analyze_github_process.d.ts +25 -0
- package/dist/functions/analyze_github_process.js +268 -0
- package/dist/functions/analyze_github_process.js.map +1 -0
- package/dist/functions/analyze_github_summary.d.ts +32 -0
- package/dist/functions/analyze_github_summary.js +270 -0
- package/dist/functions/analyze_github_summary.js.map +1 -0
- package/dist/functions/analyze_market_research.d.ts +40 -0
- package/dist/functions/analyze_market_research.js +346 -0
- package/dist/functions/analyze_market_research.js.map +1 -0
- package/dist/functions/analyze_marketing_data.d.ts +96 -0
- package/dist/functions/analyze_marketing_data.js +990 -0
- package/dist/functions/analyze_marketing_data.js.map +1 -0
- package/dist/functions/collect_from_app_store.d.ts +25 -0
- package/dist/functions/collect_from_app_store.js +148 -0
- package/dist/functions/collect_from_app_store.js.map +1 -0
- package/dist/functions/collect_from_firebase_analytics.d.ts +25 -0
- package/dist/functions/collect_from_firebase_analytics.js +142 -0
- package/dist/functions/collect_from_firebase_analytics.js.map +1 -0
- package/dist/functions/collect_from_google_play_console.d.ts +25 -0
- package/dist/functions/collect_from_google_play_console.js +142 -0
- package/dist/functions/collect_from_google_play_console.js.map +1 -0
- package/dist/functions/generate_marketing_markdown.d.ts +89 -0
- package/dist/functions/generate_marketing_markdown.js +653 -0
- package/dist/functions/generate_marketing_markdown.js.map +1 -0
- package/dist/functions/generate_marketing_pdf.d.ts +25 -0
- package/dist/functions/generate_marketing_pdf.js +175 -0
- package/dist/functions/generate_marketing_pdf.js.map +1 -0
- package/dist/functions/research_market.d.ts +56 -0
- package/dist/functions/research_market.js +434 -0
- package/dist/functions/research_market.js.map +1 -0
- package/dist/functions.d.ts +571 -0
- package/dist/functions.js +648 -0
- package/dist/functions.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/locales/de_DE.d.ts +5 -0
- package/dist/locales/de_DE.js +111 -0
- package/dist/locales/de_DE.js.map +1 -0
- package/dist/locales/en.d.ts +5 -0
- package/dist/locales/en.js +111 -0
- package/dist/locales/en.js.map +1 -0
- package/dist/locales/es_ES.d.ts +5 -0
- package/dist/locales/es_ES.js +111 -0
- package/dist/locales/es_ES.js.map +1 -0
- package/dist/locales/fr_FR.d.ts +5 -0
- package/dist/locales/fr_FR.js +111 -0
- package/dist/locales/fr_FR.js.map +1 -0
- package/dist/locales/id_ID.d.ts +5 -0
- package/dist/locales/id_ID.js +111 -0
- package/dist/locales/id_ID.js.map +1 -0
- package/dist/locales/index.d.ts +59 -0
- package/dist/locales/index.js +155 -0
- package/dist/locales/index.js.map +1 -0
- package/dist/locales/ja.d.ts +5 -0
- package/dist/locales/ja.js +111 -0
- package/dist/locales/ja.js.map +1 -0
- package/dist/locales/ko_KR.d.ts +5 -0
- package/dist/locales/ko_KR.js +111 -0
- package/dist/locales/ko_KR.js.map +1 -0
- package/dist/locales/pt_PT.d.ts +5 -0
- package/dist/locales/pt_PT.js +111 -0
- package/dist/locales/pt_PT.js.map +1 -0
- package/dist/locales/ru_RU.d.ts +5 -0
- package/dist/locales/ru_RU.js +111 -0
- package/dist/locales/ru_RU.js.map +1 -0
- package/dist/locales/types.d.ts +105 -0
- package/dist/locales/types.js +8 -0
- package/dist/locales/types.js.map +1 -0
- package/dist/locales/zh_CN.d.ts +5 -0
- package/dist/locales/zh_CN.js +111 -0
- package/dist/locales/zh_CN.js.map +1 -0
- package/dist/models/app_store_data.d.ts +87 -0
- package/dist/models/app_store_data.js +8 -0
- package/dist/models/app_store_data.js.map +1 -0
- package/dist/models/chart_data.d.ts +104 -0
- package/dist/models/chart_data.js +8 -0
- package/dist/models/chart_data.js.map +1 -0
- package/dist/models/common.d.ts +42 -0
- package/dist/models/common.js +8 -0
- package/dist/models/common.js.map +1 -0
- package/dist/models/firebase_analytics_data.d.ts +134 -0
- package/dist/models/firebase_analytics_data.js +8 -0
- package/dist/models/firebase_analytics_data.js.map +1 -0
- package/dist/models/github_analysis_data.d.ts +266 -0
- package/dist/models/github_analysis_data.js +101 -0
- package/dist/models/github_analysis_data.js.map +1 -0
- package/dist/models/google_play_data.d.ts +115 -0
- package/dist/models/google_play_data.js +8 -0
- package/dist/models/google_play_data.js.map +1 -0
- package/dist/models/index.d.ts +14 -0
- package/dist/models/index.js +36 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/market_research_data.d.ts +259 -0
- package/dist/models/market_research_data.js +8 -0
- package/dist/models/market_research_data.js.map +1 -0
- package/dist/models/marketing_analysis_data.d.ts +200 -0
- package/dist/models/marketing_analysis_data.js +8 -0
- package/dist/models/marketing_analysis_data.js.map +1 -0
- package/dist/models/pdf_data.d.ts +112 -0
- package/dist/models/pdf_data.js +8 -0
- package/dist/models/pdf_data.js.map +1 -0
- package/dist/services/chart_service.d.ts +54 -0
- package/dist/services/chart_service.js +436 -0
- package/dist/services/chart_service.js.map +1 -0
- package/dist/services/github_analysis_service.d.ts +73 -0
- package/dist/services/github_analysis_service.js +488 -0
- package/dist/services/github_analysis_service.js.map +1 -0
- package/dist/services/pdf_service.d.ts +112 -0
- package/dist/services/pdf_service.js +1397 -0
- package/dist/services/pdf_service.js.map +1 -0
- package/dist/utils/github_analysis_storage.d.ts +88 -0
- package/dist/utils/github_analysis_storage.js +154 -0
- package/dist/utils/github_analysis_storage.js.map +1 -0
- package/package.json +64 -0
- package/scripts/cleanup_test_data.ts +304 -0
- package/scripts/list_test_data.ts +85 -0
|
@@ -0,0 +1,990 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AnalyzeMarketingData = void 0;
|
|
4
|
+
const masamune_firebase_workflow_1 = require("@mathrunet/masamune_firebase_workflow");
|
|
5
|
+
const genai_1 = require("@google/genai");
|
|
6
|
+
const locales_1 = require("../locales");
|
|
7
|
+
/**
|
|
8
|
+
* A function for analyzing marketing data using AI (Gemini).
|
|
9
|
+
*
|
|
10
|
+
* AIを使用してマーケティングデータを解析するためのFunction。
|
|
11
|
+
*/
|
|
12
|
+
class AnalyzeMarketingData extends masamune_firebase_workflow_1.WorkflowProcessFunctionBase {
|
|
13
|
+
/**
|
|
14
|
+
* The ID of the function.
|
|
15
|
+
*
|
|
16
|
+
* 関数のID。
|
|
17
|
+
*/
|
|
18
|
+
id = "analyze_marketing_data";
|
|
19
|
+
/**
|
|
20
|
+
* Translations for the current locale.
|
|
21
|
+
*/
|
|
22
|
+
translations = (0, locales_1.getTranslations)("en");
|
|
23
|
+
/**
|
|
24
|
+
* The process of the function.
|
|
25
|
+
*
|
|
26
|
+
* @param context
|
|
27
|
+
* The context of the function.
|
|
28
|
+
*
|
|
29
|
+
* @returns
|
|
30
|
+
* The action of the function.
|
|
31
|
+
*/
|
|
32
|
+
async process(context) {
|
|
33
|
+
const action = context.action;
|
|
34
|
+
const task = context.task;
|
|
35
|
+
// 1. task.results から各データを取得
|
|
36
|
+
const googlePlayConsole = task.results?.googlePlayConsole;
|
|
37
|
+
const appStore = task.results?.appStore;
|
|
38
|
+
const firebaseAnalytics = task.results?.firebaseAnalytics;
|
|
39
|
+
const githubRepository = task.results?.githubRepository;
|
|
40
|
+
// 市場調査データを取得(あれば)
|
|
41
|
+
const marketResearchData = task.results?.marketResearchData;
|
|
42
|
+
const marketResearch = task.results?.marketResearch;
|
|
43
|
+
// 市場調査データが有効かどうかをチェック
|
|
44
|
+
const hasValidMarketResearchData = marketResearchData && !("error" in marketResearchData);
|
|
45
|
+
const hasValidMarketResearch = marketResearch && !("error" in marketResearch);
|
|
46
|
+
// 2. いずれのデータも無ければ空データを返却
|
|
47
|
+
if (!googlePlayConsole && !appStore && !firebaseAnalytics) {
|
|
48
|
+
console.log("AnalyzeMarketingData: No marketing data found in task.results");
|
|
49
|
+
return {
|
|
50
|
+
...action,
|
|
51
|
+
results: {
|
|
52
|
+
marketingAnalytics: {},
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// 3. 環境変数からプロジェクト情報を取得
|
|
57
|
+
const projectId = process.env.GCLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCP_PROJECT_ID;
|
|
58
|
+
const region = process.env.GCLOUD_REGION || "us-central1";
|
|
59
|
+
const modelName = process.env.GEMINI_MODEL || "gemini-2.5-flash";
|
|
60
|
+
const inputPrice = process.env.MODEL_INPUT_PRICE || 0.0000003;
|
|
61
|
+
const outputPrice = process.env.MODEL_OUTPUT_PRICE || 0.0000025;
|
|
62
|
+
if (!projectId) {
|
|
63
|
+
console.error("AnalyzeMarketingData: No GCP project ID found");
|
|
64
|
+
return {
|
|
65
|
+
...action,
|
|
66
|
+
results: {
|
|
67
|
+
marketingAnalytics: {
|
|
68
|
+
error: "No GCP project ID configured",
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
// 4. Localeを取得して翻訳を初期化
|
|
75
|
+
const locale = typeof action.locale === "object"
|
|
76
|
+
? action.locale["@language"]
|
|
77
|
+
: action.locale;
|
|
78
|
+
this.translations = (0, locales_1.getTranslations)(locale);
|
|
79
|
+
// 5. GoogleGenAI を初期化(VertexAI モード)
|
|
80
|
+
const genai = new genai_1.GoogleGenAI({
|
|
81
|
+
vertexai: true,
|
|
82
|
+
project: projectId,
|
|
83
|
+
location: region,
|
|
84
|
+
});
|
|
85
|
+
const combinedData = {
|
|
86
|
+
googlePlayConsole,
|
|
87
|
+
appStore,
|
|
88
|
+
firebaseAnalytics,
|
|
89
|
+
githubRepository,
|
|
90
|
+
marketResearchData: hasValidMarketResearchData ? marketResearchData : undefined,
|
|
91
|
+
marketResearch: hasValidMarketResearch ? marketResearch : undefined,
|
|
92
|
+
};
|
|
93
|
+
// 市場調査データが統合されるかどうかのフラグ(Firestoreはundefinedを許可しないため、必ずbooleanにする)
|
|
94
|
+
const marketDataIntegrated = !!(hasValidMarketResearchData || hasValidMarketResearch);
|
|
95
|
+
// レビューデータを抽出
|
|
96
|
+
const reviews = [
|
|
97
|
+
...(googlePlayConsole?.recentReviews || []),
|
|
98
|
+
...(appStore?.recentReviews || []),
|
|
99
|
+
];
|
|
100
|
+
// 価格を数値に変換(環境変数は文字列の可能性があるため)
|
|
101
|
+
const inputPriceNum = Number(inputPrice);
|
|
102
|
+
const outputPriceNum = Number(outputPrice);
|
|
103
|
+
// 5. 各解析を並列実行
|
|
104
|
+
const [overallResult, suggestionsResult, trendResult, reviewResult, githubImprovementsResult, competitivePositioningResult, marketOpportunityResult,] = await Promise.all([
|
|
105
|
+
this.generateOverallAnalysis(genai, combinedData, modelName),
|
|
106
|
+
this.generateImprovementSuggestions(genai, combinedData, modelName),
|
|
107
|
+
this.generateTrendAnalysis(genai, combinedData, modelName),
|
|
108
|
+
reviews.length > 0
|
|
109
|
+
? this.analyzeReviews(genai, reviews, modelName)
|
|
110
|
+
: Promise.resolve({
|
|
111
|
+
data: {
|
|
112
|
+
sentiment: { positive: 0, neutral: 0, negative: 0 },
|
|
113
|
+
commonThemes: [],
|
|
114
|
+
actionableInsights: [],
|
|
115
|
+
},
|
|
116
|
+
inputTokens: 0,
|
|
117
|
+
outputTokens: 0,
|
|
118
|
+
}),
|
|
119
|
+
// GitHub改善提案(GitHubデータがある場合のみ)
|
|
120
|
+
githubRepository && !("error" in githubRepository)
|
|
121
|
+
? this.generateGitHubImprovements(genai, combinedData, modelName)
|
|
122
|
+
: Promise.resolve(null),
|
|
123
|
+
// 競合ポジショニング分析(市場調査データがある場合のみ)
|
|
124
|
+
marketDataIntegrated
|
|
125
|
+
? this.generateCompetitivePositioning(genai, combinedData, modelName)
|
|
126
|
+
: Promise.resolve(null),
|
|
127
|
+
// 市場機会優先度分析(市場調査データがある場合のみ)
|
|
128
|
+
marketDataIntegrated
|
|
129
|
+
? this.generateMarketOpportunityPriority(genai, combinedData, modelName)
|
|
130
|
+
: Promise.resolve(null),
|
|
131
|
+
]);
|
|
132
|
+
// 6. トークン使用量を集計してコストを計算
|
|
133
|
+
const totalInputTokens = overallResult.inputTokens +
|
|
134
|
+
suggestionsResult.inputTokens +
|
|
135
|
+
trendResult.inputTokens +
|
|
136
|
+
reviewResult.inputTokens +
|
|
137
|
+
(githubImprovementsResult?.inputTokens ?? 0) +
|
|
138
|
+
(competitivePositioningResult?.inputTokens ?? 0) +
|
|
139
|
+
(marketOpportunityResult?.inputTokens ?? 0);
|
|
140
|
+
const totalOutputTokens = overallResult.outputTokens +
|
|
141
|
+
suggestionsResult.outputTokens +
|
|
142
|
+
trendResult.outputTokens +
|
|
143
|
+
reviewResult.outputTokens +
|
|
144
|
+
(githubImprovementsResult?.outputTokens ?? 0) +
|
|
145
|
+
(competitivePositioningResult?.outputTokens ?? 0) +
|
|
146
|
+
(marketOpportunityResult?.outputTokens ?? 0);
|
|
147
|
+
// コスト計算(1トークンあたりのドル × トークン数)
|
|
148
|
+
const aiCost = totalInputTokens * inputPriceNum + totalOutputTokens * outputPriceNum;
|
|
149
|
+
// 7. 結果を marketingAnalytics キーで返却
|
|
150
|
+
return {
|
|
151
|
+
...action,
|
|
152
|
+
usage: (action.usage ?? 0) + aiCost,
|
|
153
|
+
results: {
|
|
154
|
+
marketingAnalytics: {
|
|
155
|
+
overallAnalysis: overallResult.data,
|
|
156
|
+
improvementSuggestions: suggestionsResult.data,
|
|
157
|
+
trendAnalysis: trendResult.data,
|
|
158
|
+
reviewAnalysis: reviewResult.data,
|
|
159
|
+
// 市場調査データがある場合のみ追加
|
|
160
|
+
...(competitivePositioningResult?.data ? { competitivePositioning: competitivePositioningResult.data } : {}),
|
|
161
|
+
...(marketOpportunityResult?.data ? { marketOpportunityPriority: marketOpportunityResult.data } : {}),
|
|
162
|
+
marketDataIntegrated,
|
|
163
|
+
generatedAt: new Date().toISOString(),
|
|
164
|
+
},
|
|
165
|
+
// GitHub改善提案(GitHubデータがある場合のみ)
|
|
166
|
+
...(githubImprovementsResult?.data ? { githubImprovements: githubImprovementsResult.data } : {}),
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.error("AnalyzeMarketingData: Failed to analyze data", error);
|
|
172
|
+
return {
|
|
173
|
+
...action,
|
|
174
|
+
results: {
|
|
175
|
+
marketingAnalytics: {
|
|
176
|
+
error: error.message,
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Generate overall analysis from marketing data.
|
|
184
|
+
*/
|
|
185
|
+
async generateOverallAnalysis(genai, data, modelName) {
|
|
186
|
+
try {
|
|
187
|
+
const prompt = this.buildOverallAnalysisPrompt(data);
|
|
188
|
+
const response = await genai.models.generateContent({
|
|
189
|
+
model: modelName,
|
|
190
|
+
contents: prompt,
|
|
191
|
+
config: {
|
|
192
|
+
responseMimeType: "application/json",
|
|
193
|
+
responseSchema: {
|
|
194
|
+
type: genai_1.Type.OBJECT,
|
|
195
|
+
properties: {
|
|
196
|
+
summary: { type: genai_1.Type.STRING },
|
|
197
|
+
highlights: {
|
|
198
|
+
type: genai_1.Type.ARRAY,
|
|
199
|
+
items: { type: genai_1.Type.STRING },
|
|
200
|
+
},
|
|
201
|
+
concerns: {
|
|
202
|
+
type: genai_1.Type.ARRAY,
|
|
203
|
+
items: { type: genai_1.Type.STRING },
|
|
204
|
+
},
|
|
205
|
+
keyMetrics: {
|
|
206
|
+
type: genai_1.Type.ARRAY,
|
|
207
|
+
items: {
|
|
208
|
+
type: genai_1.Type.OBJECT,
|
|
209
|
+
properties: {
|
|
210
|
+
metric: { type: genai_1.Type.STRING },
|
|
211
|
+
value: { type: genai_1.Type.STRING },
|
|
212
|
+
trend: { type: genai_1.Type.STRING },
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
required: ["summary", "highlights", "concerns", "keyMetrics"],
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
const text = response.text;
|
|
222
|
+
if (!text) {
|
|
223
|
+
throw new Error("No response from AI model");
|
|
224
|
+
}
|
|
225
|
+
const inputTokens = response.usageMetadata?.promptTokenCount ?? 0;
|
|
226
|
+
const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
|
|
227
|
+
return {
|
|
228
|
+
data: JSON.parse(text),
|
|
229
|
+
inputTokens,
|
|
230
|
+
outputTokens,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
console.error("Failed to generate overall analysis:", err.message);
|
|
235
|
+
return {
|
|
236
|
+
data: {
|
|
237
|
+
summary: "",
|
|
238
|
+
highlights: [],
|
|
239
|
+
concerns: [],
|
|
240
|
+
keyMetrics: [],
|
|
241
|
+
},
|
|
242
|
+
inputTokens: 0,
|
|
243
|
+
outputTokens: 0,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Generate improvement suggestions.
|
|
249
|
+
*/
|
|
250
|
+
async generateImprovementSuggestions(genai, data, modelName) {
|
|
251
|
+
try {
|
|
252
|
+
const prompt = this.buildImprovementSuggestionsPrompt(data);
|
|
253
|
+
const response = await genai.models.generateContent({
|
|
254
|
+
model: modelName,
|
|
255
|
+
contents: prompt,
|
|
256
|
+
config: {
|
|
257
|
+
responseMimeType: "application/json",
|
|
258
|
+
responseSchema: {
|
|
259
|
+
type: genai_1.Type.ARRAY,
|
|
260
|
+
items: {
|
|
261
|
+
type: genai_1.Type.OBJECT,
|
|
262
|
+
properties: {
|
|
263
|
+
title: { type: genai_1.Type.STRING },
|
|
264
|
+
description: { type: genai_1.Type.STRING },
|
|
265
|
+
priority: { type: genai_1.Type.STRING },
|
|
266
|
+
category: { type: genai_1.Type.STRING },
|
|
267
|
+
expectedImpact: { type: genai_1.Type.STRING },
|
|
268
|
+
},
|
|
269
|
+
required: ["title", "description", "priority", "category"],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
const text = response.text;
|
|
275
|
+
if (!text) {
|
|
276
|
+
throw new Error("No response from AI model");
|
|
277
|
+
}
|
|
278
|
+
const inputTokens = response.usageMetadata?.promptTokenCount ?? 0;
|
|
279
|
+
const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
|
|
280
|
+
return {
|
|
281
|
+
data: JSON.parse(text),
|
|
282
|
+
inputTokens,
|
|
283
|
+
outputTokens,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
console.error("Failed to generate improvement suggestions:", err.message);
|
|
288
|
+
return {
|
|
289
|
+
data: [],
|
|
290
|
+
inputTokens: 0,
|
|
291
|
+
outputTokens: 0,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Generate trend analysis.
|
|
297
|
+
*/
|
|
298
|
+
async generateTrendAnalysis(genai, data, modelName) {
|
|
299
|
+
try {
|
|
300
|
+
const prompt = this.buildTrendAnalysisPrompt(data);
|
|
301
|
+
const response = await genai.models.generateContent({
|
|
302
|
+
model: modelName,
|
|
303
|
+
contents: prompt,
|
|
304
|
+
config: {
|
|
305
|
+
responseMimeType: "application/json",
|
|
306
|
+
responseSchema: {
|
|
307
|
+
type: genai_1.Type.OBJECT,
|
|
308
|
+
properties: {
|
|
309
|
+
userGrowthTrend: { type: genai_1.Type.STRING },
|
|
310
|
+
engagementTrend: { type: genai_1.Type.STRING },
|
|
311
|
+
ratingTrend: { type: genai_1.Type.STRING },
|
|
312
|
+
predictions: {
|
|
313
|
+
type: genai_1.Type.ARRAY,
|
|
314
|
+
items: { type: genai_1.Type.STRING },
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
required: ["userGrowthTrend", "engagementTrend", "ratingTrend", "predictions"],
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
const text = response.text;
|
|
322
|
+
if (!text) {
|
|
323
|
+
throw new Error("No response from AI model");
|
|
324
|
+
}
|
|
325
|
+
const inputTokens = response.usageMetadata?.promptTokenCount ?? 0;
|
|
326
|
+
const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
|
|
327
|
+
return {
|
|
328
|
+
data: JSON.parse(text),
|
|
329
|
+
inputTokens,
|
|
330
|
+
outputTokens,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
console.error("Failed to generate trend analysis:", err.message);
|
|
335
|
+
return {
|
|
336
|
+
data: {
|
|
337
|
+
userGrowthTrend: "",
|
|
338
|
+
engagementTrend: "",
|
|
339
|
+
ratingTrend: "",
|
|
340
|
+
predictions: [],
|
|
341
|
+
},
|
|
342
|
+
inputTokens: 0,
|
|
343
|
+
outputTokens: 0,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Analyze reviews and extract insights.
|
|
349
|
+
*/
|
|
350
|
+
async analyzeReviews(genai, reviews, modelName) {
|
|
351
|
+
try {
|
|
352
|
+
const prompt = this.buildReviewAnalysisPrompt(reviews);
|
|
353
|
+
const response = await genai.models.generateContent({
|
|
354
|
+
model: modelName,
|
|
355
|
+
contents: prompt,
|
|
356
|
+
config: {
|
|
357
|
+
responseMimeType: "application/json",
|
|
358
|
+
responseSchema: {
|
|
359
|
+
type: genai_1.Type.OBJECT,
|
|
360
|
+
properties: {
|
|
361
|
+
sentiment: {
|
|
362
|
+
type: genai_1.Type.OBJECT,
|
|
363
|
+
properties: {
|
|
364
|
+
positive: { type: genai_1.Type.NUMBER },
|
|
365
|
+
neutral: { type: genai_1.Type.NUMBER },
|
|
366
|
+
negative: { type: genai_1.Type.NUMBER },
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
commonThemes: {
|
|
370
|
+
type: genai_1.Type.ARRAY,
|
|
371
|
+
items: { type: genai_1.Type.STRING },
|
|
372
|
+
},
|
|
373
|
+
actionableInsights: {
|
|
374
|
+
type: genai_1.Type.ARRAY,
|
|
375
|
+
items: { type: genai_1.Type.STRING },
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
required: ["sentiment", "commonThemes", "actionableInsights"],
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
const text = response.text;
|
|
383
|
+
if (!text) {
|
|
384
|
+
throw new Error("No response from AI model");
|
|
385
|
+
}
|
|
386
|
+
const inputTokens = response.usageMetadata?.promptTokenCount ?? 0;
|
|
387
|
+
const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
|
|
388
|
+
return {
|
|
389
|
+
data: JSON.parse(text),
|
|
390
|
+
inputTokens,
|
|
391
|
+
outputTokens,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
console.error("Failed to analyze reviews:", err.message);
|
|
396
|
+
return {
|
|
397
|
+
data: {
|
|
398
|
+
sentiment: { positive: 0, neutral: 0, negative: 0 },
|
|
399
|
+
commonThemes: [],
|
|
400
|
+
actionableInsights: [],
|
|
401
|
+
},
|
|
402
|
+
inputTokens: 0,
|
|
403
|
+
outputTokens: 0,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Build prompt for overall analysis.
|
|
409
|
+
*/
|
|
410
|
+
buildOverallAnalysisPrompt(data) {
|
|
411
|
+
// 市場調査データのセクションを構築
|
|
412
|
+
let marketResearchSection = "";
|
|
413
|
+
if (data.marketResearchData || data.marketResearch) {
|
|
414
|
+
marketResearchSection = `
|
|
415
|
+
## 市場調査データ
|
|
416
|
+
${data.marketResearchData ? `
|
|
417
|
+
### 市場ポテンシャル
|
|
418
|
+
${JSON.stringify(data.marketResearchData.marketPotential, null, 2)}
|
|
419
|
+
|
|
420
|
+
### 競合分析
|
|
421
|
+
${JSON.stringify(data.marketResearchData.competitorAnalysis, null, 2)}
|
|
422
|
+
` : ""}
|
|
423
|
+
${data.marketResearch ? `
|
|
424
|
+
### 需要予測
|
|
425
|
+
${JSON.stringify(data.marketResearch.demandForecast, null, 2)}
|
|
426
|
+
|
|
427
|
+
### キーインサイト
|
|
428
|
+
${JSON.stringify(data.marketResearch.keyInsights, null, 2)}
|
|
429
|
+
` : ""}
|
|
430
|
+
`;
|
|
431
|
+
}
|
|
432
|
+
return `You are an expert app marketing analyst. Analyze the following marketing data and provide a comprehensive overview.
|
|
433
|
+
|
|
434
|
+
## Marketing Data
|
|
435
|
+
|
|
436
|
+
### Google Play Data
|
|
437
|
+
${data.googlePlayConsole ? JSON.stringify(data.googlePlayConsole, null, 2) : "Not available"}
|
|
438
|
+
|
|
439
|
+
### App Store Data
|
|
440
|
+
${data.appStore ? JSON.stringify(data.appStore, null, 2) : "Not available"}
|
|
441
|
+
|
|
442
|
+
### Firebase Analytics Data
|
|
443
|
+
${data.firebaseAnalytics ? JSON.stringify(data.firebaseAnalytics, null, 2) : "Not available"}
|
|
444
|
+
${marketResearchSection}
|
|
445
|
+
## Instructions
|
|
446
|
+
1. Write a concise summary (2-3 paragraphs) analyzing the overall app performance
|
|
447
|
+
2. List 3-5 key highlights (positive points)
|
|
448
|
+
3. List 2-4 concerns or areas needing attention
|
|
449
|
+
4. Identify 4-6 key metrics with their values and trends (up/down/stable)
|
|
450
|
+
${data.marketResearchData || data.marketResearch ? "5. Integrate market research insights into your analysis, considering market potential and competitive landscape" : ""}
|
|
451
|
+
|
|
452
|
+
${this.translations.respondInLanguage}`;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Build prompt for improvement suggestions.
|
|
456
|
+
*/
|
|
457
|
+
buildImprovementSuggestionsPrompt(data) {
|
|
458
|
+
// 市場調査データのセクションを構築
|
|
459
|
+
let marketResearchSection = "";
|
|
460
|
+
if (data.marketResearchData || data.marketResearch) {
|
|
461
|
+
marketResearchSection = `
|
|
462
|
+
## 市場調査データ
|
|
463
|
+
${data.marketResearchData ? `
|
|
464
|
+
### ビジネス機会
|
|
465
|
+
${JSON.stringify(data.marketResearchData.businessOpportunities, null, 2)}
|
|
466
|
+
|
|
467
|
+
### 差別化機会
|
|
468
|
+
${JSON.stringify(data.marketResearchData.competitorAnalysis.differentiationOpportunities, null, 2)}
|
|
469
|
+
|
|
470
|
+
### 市場ギャップ
|
|
471
|
+
${JSON.stringify(data.marketResearchData.competitorAnalysis.marketGaps, null, 2)}
|
|
472
|
+
` : ""}
|
|
473
|
+
${data.marketResearch ? `
|
|
474
|
+
### 収益向上施策(市場調査ベース)
|
|
475
|
+
${JSON.stringify(data.marketResearch.revenueStrategies, null, 2)}
|
|
476
|
+
|
|
477
|
+
### 流入向上施策(市場調査ベース)
|
|
478
|
+
${JSON.stringify(data.marketResearch.trafficStrategies, null, 2)}
|
|
479
|
+
` : ""}
|
|
480
|
+
`;
|
|
481
|
+
}
|
|
482
|
+
return `You are an expert app marketing strategist. Based on the following marketing data, provide actionable improvement suggestions.
|
|
483
|
+
|
|
484
|
+
## Marketing Data
|
|
485
|
+
|
|
486
|
+
### Google Play Data
|
|
487
|
+
${data.googlePlayConsole ? JSON.stringify(data.googlePlayConsole, null, 2) : "Not available"}
|
|
488
|
+
|
|
489
|
+
### App Store Data
|
|
490
|
+
${data.appStore ? JSON.stringify(data.appStore, null, 2) : "Not available"}
|
|
491
|
+
|
|
492
|
+
### Firebase Analytics Data
|
|
493
|
+
${data.firebaseAnalytics ? JSON.stringify(data.firebaseAnalytics, null, 2) : "Not available"}
|
|
494
|
+
${marketResearchSection}
|
|
495
|
+
## Instructions
|
|
496
|
+
Provide 5-8 specific, actionable improvement suggestions. For each suggestion:
|
|
497
|
+
1. title: Brief title (under 50 characters)
|
|
498
|
+
2. description: Detailed explanation with specific steps (100-200 characters)
|
|
499
|
+
3. priority: "high", "medium", or "low"
|
|
500
|
+
4. category: One of "user_acquisition", "retention", "engagement", "monetization", "quality", "development"
|
|
501
|
+
5. expectedImpact: Expected outcome if implemented
|
|
502
|
+
${data.marketResearchData || data.marketResearch ? `
|
|
503
|
+
Important: Consider the market research data when making recommendations:
|
|
504
|
+
- Prioritize suggestions that address identified market gaps
|
|
505
|
+
- Leverage business opportunities and differentiation points
|
|
506
|
+
- Align with revenue and traffic strategies from market research
|
|
507
|
+
` : ""}
|
|
508
|
+
Focus on data-driven recommendations. ${this.translations.respondInLanguage}`;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Build prompt for trend analysis.
|
|
512
|
+
*/
|
|
513
|
+
buildTrendAnalysisPrompt(data) {
|
|
514
|
+
// 市場調査データのセクションを構築
|
|
515
|
+
let marketResearchSection = "";
|
|
516
|
+
if (data.marketResearchData || data.marketResearch) {
|
|
517
|
+
marketResearchSection = `
|
|
518
|
+
## 市場調査データ
|
|
519
|
+
${data.marketResearch ? `
|
|
520
|
+
### 需要予測
|
|
521
|
+
${JSON.stringify(data.marketResearch.demandForecast, null, 2)}
|
|
522
|
+
|
|
523
|
+
### 市場全体トレンド
|
|
524
|
+
${data.marketResearch.demandForecast.overallTrend}
|
|
525
|
+
` : ""}
|
|
526
|
+
${data.marketResearchData ? `
|
|
527
|
+
### 市場ドライバー
|
|
528
|
+
${JSON.stringify(data.marketResearchData.marketPotential.marketDrivers, null, 2)}
|
|
529
|
+
|
|
530
|
+
### 市場ポテンシャル概要
|
|
531
|
+
${data.marketResearchData.marketPotential.summary}
|
|
532
|
+
` : ""}
|
|
533
|
+
`;
|
|
534
|
+
}
|
|
535
|
+
return `You are an expert data analyst specializing in mobile app trends. Analyze the following marketing data and provide trend insights.
|
|
536
|
+
|
|
537
|
+
## Marketing Data
|
|
538
|
+
|
|
539
|
+
### Google Play Data
|
|
540
|
+
${data.googlePlayConsole ? JSON.stringify(data.googlePlayConsole, null, 2) : "Not available"}
|
|
541
|
+
|
|
542
|
+
### App Store Data
|
|
543
|
+
${data.appStore ? JSON.stringify(data.appStore, null, 2) : "Not available"}
|
|
544
|
+
|
|
545
|
+
### Firebase Analytics Data
|
|
546
|
+
${data.firebaseAnalytics ? JSON.stringify(data.firebaseAnalytics, null, 2) : "Not available"}
|
|
547
|
+
${marketResearchSection}
|
|
548
|
+
## Instructions
|
|
549
|
+
Analyze trends and provide:
|
|
550
|
+
1. userGrowthTrend: Analysis of user acquisition and growth patterns (2-3 sentences)
|
|
551
|
+
2. engagementTrend: Analysis of user engagement metrics (2-3 sentences)
|
|
552
|
+
3. ratingTrend: Analysis of app ratings and user satisfaction (2-3 sentences)
|
|
553
|
+
4. predictions: 3-5 predictions for the next period based on current trends
|
|
554
|
+
${data.marketResearchData || data.marketResearch ? "5. Consider market demand forecasts and market drivers when making predictions" : ""}
|
|
555
|
+
|
|
556
|
+
${this.translations.respondInLanguage}`;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Build prompt for review analysis.
|
|
560
|
+
*/
|
|
561
|
+
buildReviewAnalysisPrompt(reviews) {
|
|
562
|
+
const reviewsText = reviews
|
|
563
|
+
.slice(0, 50) // Limit to 50 reviews to avoid token limits
|
|
564
|
+
.map((r) => `Rating: ${r.rating}/5\nText: ${r.text}\n---`)
|
|
565
|
+
.join("\n");
|
|
566
|
+
return `You are an expert in user feedback analysis. Analyze the following app reviews and extract insights.
|
|
567
|
+
|
|
568
|
+
## Reviews
|
|
569
|
+
${reviewsText}
|
|
570
|
+
|
|
571
|
+
## Instructions
|
|
572
|
+
Analyze the reviews and provide:
|
|
573
|
+
1. sentiment: Percentage breakdown of positive, neutral, and negative reviews (must sum to 100)
|
|
574
|
+
2. commonThemes: List 3-6 recurring themes or topics mentioned by users
|
|
575
|
+
3. actionableInsights: List 3-5 specific, actionable insights based on the feedback
|
|
576
|
+
|
|
577
|
+
${this.translations.respondInLanguage}`;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Generate GitHub-aware improvement suggestions.
|
|
581
|
+
*
|
|
582
|
+
* GitHubリポジトリ分析に基づく改善提案を生成。
|
|
583
|
+
*/
|
|
584
|
+
async generateGitHubImprovements(genai, data, modelName) {
|
|
585
|
+
try {
|
|
586
|
+
const prompt = this.buildGitHubImprovementsPrompt(data);
|
|
587
|
+
const response = await genai.models.generateContent({
|
|
588
|
+
model: modelName,
|
|
589
|
+
contents: prompt,
|
|
590
|
+
config: {
|
|
591
|
+
responseMimeType: "application/json",
|
|
592
|
+
responseSchema: {
|
|
593
|
+
type: genai_1.Type.OBJECT,
|
|
594
|
+
properties: {
|
|
595
|
+
improvementSummary: { type: genai_1.Type.STRING },
|
|
596
|
+
improvements: {
|
|
597
|
+
type: genai_1.Type.ARRAY,
|
|
598
|
+
items: {
|
|
599
|
+
type: genai_1.Type.OBJECT,
|
|
600
|
+
properties: {
|
|
601
|
+
title: { type: genai_1.Type.STRING },
|
|
602
|
+
description: { type: genai_1.Type.STRING },
|
|
603
|
+
priority: { type: genai_1.Type.STRING },
|
|
604
|
+
category: { type: genai_1.Type.STRING },
|
|
605
|
+
expectedImpact: { type: genai_1.Type.STRING },
|
|
606
|
+
relatedFeature: { type: genai_1.Type.STRING },
|
|
607
|
+
codeReferences: {
|
|
608
|
+
type: genai_1.Type.ARRAY,
|
|
609
|
+
items: {
|
|
610
|
+
type: genai_1.Type.OBJECT,
|
|
611
|
+
properties: {
|
|
612
|
+
filePath: { type: genai_1.Type.STRING },
|
|
613
|
+
currentFunctionality: { type: genai_1.Type.STRING },
|
|
614
|
+
proposedChange: { type: genai_1.Type.STRING },
|
|
615
|
+
modificationType: { type: genai_1.Type.STRING },
|
|
616
|
+
},
|
|
617
|
+
required: ["filePath", "currentFunctionality", "proposedChange", "modificationType"],
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
required: ["title", "description", "priority", "category", "expectedImpact", "codeReferences"],
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
},
|
|
625
|
+
required: ["improvementSummary", "improvements"],
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
const text = response.text;
|
|
630
|
+
if (!text) {
|
|
631
|
+
throw new Error("No response from AI model");
|
|
632
|
+
}
|
|
633
|
+
const inputTokens = response.usageMetadata?.promptTokenCount ?? 0;
|
|
634
|
+
const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
|
|
635
|
+
const parsed = JSON.parse(text);
|
|
636
|
+
const githubRepo = data.githubRepository;
|
|
637
|
+
return {
|
|
638
|
+
data: {
|
|
639
|
+
repository: githubRepo.repository,
|
|
640
|
+
framework: githubRepo.framework,
|
|
641
|
+
improvements: parsed.improvements,
|
|
642
|
+
improvementSummary: parsed.improvementSummary,
|
|
643
|
+
generatedAt: new Date().toISOString(),
|
|
644
|
+
},
|
|
645
|
+
inputTokens,
|
|
646
|
+
outputTokens,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
catch (err) {
|
|
650
|
+
console.error("Failed to generate GitHub improvements:", err.message);
|
|
651
|
+
return {
|
|
652
|
+
data: {
|
|
653
|
+
repository: data.githubRepository?.repository ?? "",
|
|
654
|
+
framework: data.githubRepository?.framework ?? "",
|
|
655
|
+
improvements: [],
|
|
656
|
+
improvementSummary: "",
|
|
657
|
+
generatedAt: new Date().toISOString(),
|
|
658
|
+
},
|
|
659
|
+
inputTokens: 0,
|
|
660
|
+
outputTokens: 0,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Build prompt for GitHub-aware improvements.
|
|
666
|
+
*
|
|
667
|
+
* GitHub対応改善提案用のプロンプトを生成。
|
|
668
|
+
*/
|
|
669
|
+
buildGitHubImprovementsPrompt(data) {
|
|
670
|
+
const github = data.githubRepository;
|
|
671
|
+
// フィーチャーをテキストに変換(最大15件に制限)
|
|
672
|
+
const featuresText = github.features
|
|
673
|
+
.slice(0, 15)
|
|
674
|
+
.map((f) => `### ${f.name}\n${f.description}\nFiles: ${f.relatedFiles.join(", ")}`)
|
|
675
|
+
.join("\n\n");
|
|
676
|
+
// 全てのファイルパスを収集
|
|
677
|
+
const allFilePaths = github.features
|
|
678
|
+
.slice(0, 15)
|
|
679
|
+
.flatMap((f) => f.relatedFiles)
|
|
680
|
+
.filter((v, i, a) => a.indexOf(v) === i) // 重複排除
|
|
681
|
+
.join("\n- ");
|
|
682
|
+
return `あなたはアプリ開発とマーケティング戦略の専門家です。以下のマーケティングデータとコードベース分析に基づいて、具体的なコード改善提案を行ってください。
|
|
683
|
+
|
|
684
|
+
## アプリケーション情報
|
|
685
|
+
- Repository: ${github.repository}
|
|
686
|
+
- Framework: ${github.framework}
|
|
687
|
+
- Platforms: ${github.platforms.join(", ")}
|
|
688
|
+
|
|
689
|
+
## アプリケーション概要
|
|
690
|
+
${github.overview}
|
|
691
|
+
|
|
692
|
+
## アーキテクチャ
|
|
693
|
+
${github.architecture}
|
|
694
|
+
|
|
695
|
+
## 機能とファイル
|
|
696
|
+
${featuresText}
|
|
697
|
+
|
|
698
|
+
## 利用可能なファイルパス
|
|
699
|
+
以下のファイルパスのみをcodeReferencesで使用してください:
|
|
700
|
+
- ${allFilePaths}
|
|
701
|
+
|
|
702
|
+
## マーケティングデータ
|
|
703
|
+
|
|
704
|
+
### Google Play Console
|
|
705
|
+
${data.googlePlayConsole ? JSON.stringify(data.googlePlayConsole, null, 2) : "データなし"}
|
|
706
|
+
|
|
707
|
+
### App Store
|
|
708
|
+
${data.appStore ? JSON.stringify(data.appStore, null, 2) : "データなし"}
|
|
709
|
+
|
|
710
|
+
### Firebase Analytics
|
|
711
|
+
${data.firebaseAnalytics ? JSON.stringify(data.firebaseAnalytics, null, 2) : "データなし"}
|
|
712
|
+
|
|
713
|
+
## 指示
|
|
714
|
+
マーケティングデータとコードベース分析に基づいて、5-8個の具体的な改善提案を行ってください。
|
|
715
|
+
|
|
716
|
+
各提案には必ず以下を含めてください:
|
|
717
|
+
1. **title**: 簡潔なタイトル(50文字以内)
|
|
718
|
+
2. **description**: 詳細な説明(100-200文字)
|
|
719
|
+
3. **priority**: "high", "medium", "low" のいずれか
|
|
720
|
+
4. **category**: "user_acquisition", "retention", "engagement", "monetization", "quality", "development" のいずれか
|
|
721
|
+
5. **expectedImpact**: 期待される効果(具体的な数値目標があれば含める)
|
|
722
|
+
6. **relatedFeature**: 上記の機能名から関連するものを選択
|
|
723
|
+
7. **codeReferences**: 具体的なファイル修正の配列
|
|
724
|
+
- **filePath**: 上記「利用可能なファイルパス」から選択した実際のパス
|
|
725
|
+
- **currentFunctionality**: 現在のファイル/機能の説明
|
|
726
|
+
- **proposedChange**: 提案する具体的な変更内容
|
|
727
|
+
- **modificationType**: "add"(新規追加), "modify"(修正), "refactor"(リファクタリング), "optimize"(最適化)のいずれか
|
|
728
|
+
|
|
729
|
+
Important:
|
|
730
|
+
- codeReferences filePath must be selected from the "Available File Paths" list above
|
|
731
|
+
- Focus on code changes that improve marketing metrics (user acquisition, retention, engagement, monetization)
|
|
732
|
+
- Each improvement suggestion should be specific and actionable
|
|
733
|
+
|
|
734
|
+
${this.translations.respondInLanguage}`;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Generate competitive positioning analysis.
|
|
738
|
+
*
|
|
739
|
+
* 競合ポジショニング分析を生成。
|
|
740
|
+
*/
|
|
741
|
+
async generateCompetitivePositioning(genai, data, modelName) {
|
|
742
|
+
try {
|
|
743
|
+
const prompt = this.buildCompetitivePositioningPrompt(data);
|
|
744
|
+
const response = await genai.models.generateContent({
|
|
745
|
+
model: modelName,
|
|
746
|
+
contents: prompt,
|
|
747
|
+
config: {
|
|
748
|
+
responseMimeType: "application/json",
|
|
749
|
+
responseSchema: {
|
|
750
|
+
type: genai_1.Type.OBJECT,
|
|
751
|
+
properties: {
|
|
752
|
+
marketPosition: { type: genai_1.Type.STRING },
|
|
753
|
+
competitorComparison: {
|
|
754
|
+
type: genai_1.Type.ARRAY,
|
|
755
|
+
items: {
|
|
756
|
+
type: genai_1.Type.OBJECT,
|
|
757
|
+
properties: {
|
|
758
|
+
competitor: { type: genai_1.Type.STRING },
|
|
759
|
+
ourStrengths: {
|
|
760
|
+
type: genai_1.Type.ARRAY,
|
|
761
|
+
items: { type: genai_1.Type.STRING },
|
|
762
|
+
},
|
|
763
|
+
ourWeaknesses: {
|
|
764
|
+
type: genai_1.Type.ARRAY,
|
|
765
|
+
items: { type: genai_1.Type.STRING },
|
|
766
|
+
},
|
|
767
|
+
battleStrategy: { type: genai_1.Type.STRING },
|
|
768
|
+
},
|
|
769
|
+
required: ["competitor", "ourStrengths", "ourWeaknesses", "battleStrategy"],
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
differentiationStrategy: { type: genai_1.Type.STRING },
|
|
773
|
+
quickWins: {
|
|
774
|
+
type: genai_1.Type.ARRAY,
|
|
775
|
+
items: { type: genai_1.Type.STRING },
|
|
776
|
+
},
|
|
777
|
+
},
|
|
778
|
+
required: ["marketPosition", "competitorComparison", "differentiationStrategy", "quickWins"],
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
});
|
|
782
|
+
const text = response.text;
|
|
783
|
+
if (!text) {
|
|
784
|
+
throw new Error("No response from AI model");
|
|
785
|
+
}
|
|
786
|
+
const inputTokens = response.usageMetadata?.promptTokenCount ?? 0;
|
|
787
|
+
const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
|
|
788
|
+
return {
|
|
789
|
+
data: JSON.parse(text),
|
|
790
|
+
inputTokens,
|
|
791
|
+
outputTokens,
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
catch (err) {
|
|
795
|
+
console.error("Failed to generate competitive positioning:", err.message);
|
|
796
|
+
return {
|
|
797
|
+
data: {
|
|
798
|
+
marketPosition: "",
|
|
799
|
+
competitorComparison: [],
|
|
800
|
+
differentiationStrategy: "",
|
|
801
|
+
quickWins: [],
|
|
802
|
+
},
|
|
803
|
+
inputTokens: 0,
|
|
804
|
+
outputTokens: 0,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Build prompt for competitive positioning analysis.
|
|
810
|
+
*
|
|
811
|
+
* 競合ポジショニング分析用のプロンプトを生成。
|
|
812
|
+
*/
|
|
813
|
+
buildCompetitivePositioningPrompt(data) {
|
|
814
|
+
const competitors = data.marketResearchData?.competitorAnalysis.competitors || [];
|
|
815
|
+
const competitorsList = competitors
|
|
816
|
+
.map((c) => `- ${c.name}: ${c.description}\n 強み: ${c.strengths.join(", ")}\n 弱み: ${c.weaknesses.join(", ")}`)
|
|
817
|
+
.join("\n");
|
|
818
|
+
return `あなたは競合分析の専門家です。以下のアプリパフォーマンスデータと市場調査の競合データに基づいて、競合ポジショニング分析を行ってください。
|
|
819
|
+
|
|
820
|
+
## 当アプリのパフォーマンスデータ
|
|
821
|
+
|
|
822
|
+
### Google Play Console
|
|
823
|
+
${data.googlePlayConsole ? JSON.stringify(data.googlePlayConsole, null, 2) : "データなし"}
|
|
824
|
+
|
|
825
|
+
### App Store
|
|
826
|
+
${data.appStore ? JSON.stringify(data.appStore, null, 2) : "データなし"}
|
|
827
|
+
|
|
828
|
+
### Firebase Analytics
|
|
829
|
+
${data.firebaseAnalytics ? JSON.stringify(data.firebaseAnalytics, null, 2) : "データなし"}
|
|
830
|
+
|
|
831
|
+
## 市場調査:競合情報
|
|
832
|
+
${competitorsList || "競合データなし"}
|
|
833
|
+
|
|
834
|
+
## 市場ランドスケープ
|
|
835
|
+
${data.marketResearchData?.competitorAnalysis.marketLandscape || "情報なし"}
|
|
836
|
+
|
|
837
|
+
## 差別化機会
|
|
838
|
+
${JSON.stringify(data.marketResearchData?.competitorAnalysis.differentiationOpportunities || [], null, 2)}
|
|
839
|
+
|
|
840
|
+
## 分析指示
|
|
841
|
+
以下の観点で分析してください:
|
|
842
|
+
|
|
843
|
+
1. **marketPosition**: 現在の市場での位置づけ(2-3文)
|
|
844
|
+
- 市場シェアの推定
|
|
845
|
+
- 競合との相対的な強さ
|
|
846
|
+
|
|
847
|
+
2. **competitorComparison**: 各競合との比較(3-5社)
|
|
848
|
+
- **competitor**: 競合名
|
|
849
|
+
- **ourStrengths**: 当アプリの優位点(2-4点)
|
|
850
|
+
- **ourWeaknesses**: 当アプリの劣位点(2-4点)
|
|
851
|
+
- **battleStrategy**: この競合に対する具体的な対抗戦略(1-2文)
|
|
852
|
+
|
|
853
|
+
3. **differentiationStrategy**: 総合的な差別化戦略(3-4文)
|
|
854
|
+
- 複数の競合に対して有効な差別化ポイント
|
|
855
|
+
|
|
856
|
+
4. **quickWins**: Quick-win differentiation tactics (3-5 points)
|
|
857
|
+
- Things that can be executed in 1-2 weeks
|
|
858
|
+
|
|
859
|
+
${this.translations.respondInLanguage}`;
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Generate market opportunity priority analysis.
|
|
863
|
+
*
|
|
864
|
+
* 市場機会優先度分析を生成。
|
|
865
|
+
*/
|
|
866
|
+
async generateMarketOpportunityPriority(genai, data, modelName) {
|
|
867
|
+
try {
|
|
868
|
+
const prompt = this.buildMarketOpportunityPriorityPrompt(data);
|
|
869
|
+
const response = await genai.models.generateContent({
|
|
870
|
+
model: modelName,
|
|
871
|
+
contents: prompt,
|
|
872
|
+
config: {
|
|
873
|
+
responseMimeType: "application/json",
|
|
874
|
+
responseSchema: {
|
|
875
|
+
type: genai_1.Type.OBJECT,
|
|
876
|
+
properties: {
|
|
877
|
+
prioritizedOpportunities: {
|
|
878
|
+
type: genai_1.Type.ARRAY,
|
|
879
|
+
items: {
|
|
880
|
+
type: genai_1.Type.OBJECT,
|
|
881
|
+
properties: {
|
|
882
|
+
opportunity: { type: genai_1.Type.STRING },
|
|
883
|
+
fitScore: {
|
|
884
|
+
type: genai_1.Type.STRING,
|
|
885
|
+
enum: ["excellent", "good", "moderate", "poor"],
|
|
886
|
+
},
|
|
887
|
+
fitReason: { type: genai_1.Type.STRING },
|
|
888
|
+
requiredChanges: {
|
|
889
|
+
type: genai_1.Type.ARRAY,
|
|
890
|
+
items: { type: genai_1.Type.STRING },
|
|
891
|
+
},
|
|
892
|
+
estimatedEffort: {
|
|
893
|
+
type: genai_1.Type.STRING,
|
|
894
|
+
enum: ["low", "medium", "high"],
|
|
895
|
+
},
|
|
896
|
+
recommendedAction: { type: genai_1.Type.STRING },
|
|
897
|
+
},
|
|
898
|
+
required: ["opportunity", "fitScore", "fitReason", "requiredChanges", "estimatedEffort", "recommendedAction"],
|
|
899
|
+
},
|
|
900
|
+
},
|
|
901
|
+
strategicRecommendation: { type: genai_1.Type.STRING },
|
|
902
|
+
},
|
|
903
|
+
required: ["prioritizedOpportunities", "strategicRecommendation"],
|
|
904
|
+
},
|
|
905
|
+
},
|
|
906
|
+
});
|
|
907
|
+
const text = response.text;
|
|
908
|
+
if (!text) {
|
|
909
|
+
throw new Error("No response from AI model");
|
|
910
|
+
}
|
|
911
|
+
const inputTokens = response.usageMetadata?.promptTokenCount ?? 0;
|
|
912
|
+
const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
|
|
913
|
+
return {
|
|
914
|
+
data: JSON.parse(text),
|
|
915
|
+
inputTokens,
|
|
916
|
+
outputTokens,
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
catch (err) {
|
|
920
|
+
console.error("Failed to generate market opportunity priority:", err.message);
|
|
921
|
+
return {
|
|
922
|
+
data: {
|
|
923
|
+
prioritizedOpportunities: [],
|
|
924
|
+
strategicRecommendation: "",
|
|
925
|
+
},
|
|
926
|
+
inputTokens: 0,
|
|
927
|
+
outputTokens: 0,
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Build prompt for market opportunity priority analysis.
|
|
933
|
+
*
|
|
934
|
+
* 市場機会優先度分析用のプロンプトを生成。
|
|
935
|
+
*/
|
|
936
|
+
buildMarketOpportunityPriorityPrompt(data) {
|
|
937
|
+
const opportunities = data.marketResearchData?.businessOpportunities || [];
|
|
938
|
+
const opportunitiesList = opportunities
|
|
939
|
+
.map((o) => `- **${o.title}** (${o.type}, インパクト: ${o.potentialImpact}, 時間枠: ${o.timeframe})\n ${o.description}\n 要件: ${o.requirements.join(", ")}\n リスク: ${o.risks.join(", ")}`)
|
|
940
|
+
.join("\n\n");
|
|
941
|
+
const marketGaps = data.marketResearchData?.competitorAnalysis.marketGaps || [];
|
|
942
|
+
return `あなたは戦略コンサルタントです。以下のアプリパフォーマンスデータと市場機会データに基づいて、機会の優先順位付けを行ってください。
|
|
943
|
+
|
|
944
|
+
## 当アプリのパフォーマンスデータ
|
|
945
|
+
|
|
946
|
+
### Google Play Console
|
|
947
|
+
${data.googlePlayConsole ? JSON.stringify(data.googlePlayConsole, null, 2) : "データなし"}
|
|
948
|
+
|
|
949
|
+
### App Store
|
|
950
|
+
${data.appStore ? JSON.stringify(data.appStore, null, 2) : "データなし"}
|
|
951
|
+
|
|
952
|
+
### Firebase Analytics
|
|
953
|
+
${data.firebaseAnalytics ? JSON.stringify(data.firebaseAnalytics, null, 2) : "データなし"}
|
|
954
|
+
|
|
955
|
+
## 市場調査:ビジネス機会
|
|
956
|
+
${opportunitiesList || "機会データなし"}
|
|
957
|
+
|
|
958
|
+
## 市場調査:市場ギャップ
|
|
959
|
+
${JSON.stringify(marketGaps, null, 2)}
|
|
960
|
+
|
|
961
|
+
## 需要予測サマリー
|
|
962
|
+
${data.marketResearch?.demandForecast.summary || "情報なし"}
|
|
963
|
+
|
|
964
|
+
## 分析指示
|
|
965
|
+
各ビジネス機会と市場ギャップを、当アプリの現状と照らし合わせて優先順位付けしてください。
|
|
966
|
+
|
|
967
|
+
1. **prioritizedOpportunities**: 優先順位付けされた機会リスト(5-8件)
|
|
968
|
+
- **opportunity**: 機会名(市場調査データから)
|
|
969
|
+
- **fitScore**: 当アプリとの適合度
|
|
970
|
+
- "excellent": 既存機能・リソースで即座に対応可能
|
|
971
|
+
- "good": 小規模な追加開発で対応可能
|
|
972
|
+
- "moderate": 中規模の開発・投資が必要
|
|
973
|
+
- "poor": 大規模な方向転換が必要
|
|
974
|
+
- **fitReason**: 適合度の理由(1-2文)
|
|
975
|
+
- **requiredChanges**: 必要な変更・施策(2-4点)
|
|
976
|
+
- **estimatedEffort**: 実装工数の見積もり(low/medium/high)
|
|
977
|
+
- **recommendedAction**: 推奨アクション(1文)
|
|
978
|
+
|
|
979
|
+
2. **strategicRecommendation**: Strategic recommendations (3-4 sentences)
|
|
980
|
+
- Priority opportunities to pursue
|
|
981
|
+
- Resource allocation proposals
|
|
982
|
+
|
|
983
|
+
${this.translations.respondInLanguage}`;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
exports.AnalyzeMarketingData = AnalyzeMarketingData;
|
|
987
|
+
module.exports = (regions, options, data) => new AnalyzeMarketingData(options).build(regions);
|
|
988
|
+
// Export class for testing
|
|
989
|
+
module.exports.AnalyzeMarketingData = AnalyzeMarketingData;
|
|
990
|
+
//# sourceMappingURL=analyze_marketing_data.js.map
|