Skip to content

Commit 39afce3

Browse files
authored
Merge pull request #17 from DaleStudy/revert/pr-15
revert: PR #15 (GraphQL ๋ฐฐ์น˜ ์กฐํšŒ + OpenAI ๋ฐฐ์น˜ ๋ถ„์„) ์›๋ณต
2 parents a430e96 + 1c12f7d commit 39afce3

File tree

3 files changed

+49
-171
lines changed

3 files changed

+49
-171
lines changed

โ€Žhandlers/learning-status.jsโ€Ž

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
fetchCohortUserSolutions,
1111
fetchPRSubmissions,
1212
} from "../utils/learningData.js";
13-
import { generateBatchApproachAnalysis } from "../utils/openai.js";
13+
import { generateApproachAnalysis } from "../utils/openai.js";
1414
import {
1515
formatLearningStatusComment,
1616
upsertLearningStatusComment,
@@ -105,10 +105,9 @@ export async function postLearningStatus(
105105
`[learningStatus] PR #${prNumber}: analyzing ${submissions.length} submission(s) for ${username}`
106106
);
107107

108-
// 4. ์ œ์ถœ ํŒŒ์ผ ์ฝ”๋“œ ๋‹ค์šด๋กœ๋“œ (NํšŒ fetch)
108+
// 4. ์ œ์ถœ ํŒŒ์ผ๋ณ„ AI ๋ถ„์„
109109
const submissionResults = [];
110-
const batchItems = [];
111-
const batchIndices = [];
110+
const totalUsage = { prompt_tokens: 0, completion_tokens: 0 };
112111

113112
for (const submission of submissions) {
114113
const problemInfo = categories[submission.problemName];
@@ -127,6 +126,7 @@ export async function postLearningStatus(
127126
}
128127

129128
try {
129+
// ํŒŒ์ผ ์›๋ณธ ๋‚ด์šฉ ๊ฐ€์ ธ์˜ค๊ธฐ
130130
const rawResponse = await fetch(submission.rawUrl);
131131
if (!rawResponse.ok) {
132132
throw new Error(`Failed to fetch raw file: ${rawResponse.status} ${rawResponse.statusText}`);
@@ -140,18 +140,27 @@ export async function postLearningStatus(
140140
);
141141
}
142142

143-
const idx = submissionResults.length;
143+
const analysis = await generateApproachAnalysis(
144+
fileContent,
145+
submission.problemName,
146+
problemInfo,
147+
openaiApiKey
148+
);
149+
150+
if (analysis.usage) {
151+
totalUsage.prompt_tokens += analysis.usage.prompt_tokens ?? 0;
152+
totalUsage.completion_tokens += analysis.usage.completion_tokens ?? 0;
153+
}
154+
144155
submissionResults.push({
145156
problemName: submission.problemName,
146157
difficulty: problemInfo.difficulty,
147-
matches: null,
148-
explanation: "",
158+
matches: analysis.matches,
159+
explanation: analysis.explanation,
149160
});
150-
batchItems.push({ problemName: submission.problemName, fileContent, problemInfo });
151-
batchIndices.push(idx);
152161
} catch (error) {
153162
console.error(
154-
`[learningStatus] Failed to fetch "${submission.problemName}": ${error.message}`
163+
`[learningStatus] Failed to analyze "${submission.problemName}": ${error.message}`
155164
);
156165
submissionResults.push({
157166
problemName: submission.problemName,
@@ -162,33 +171,13 @@ export async function postLearningStatus(
162171
}
163172
}
164173

165-
// 5. AI ์ผ๊ด„ ๋ถ„์„ (1ํšŒ OpenAI ํ˜ธ์ถœ๋กœ ๋ชจ๋“  ์ œ์ถœ ํŒŒ์ผ ๋ถ„์„)
166-
let totalUsage = null;
167-
if (batchItems.length > 0) {
168-
try {
169-
console.log(
170-
`[learningStatus] PR #${prNumber}: batch analyzing ${batchItems.length} file(s) via OpenAI`
171-
);
172-
const { results: batchResults, usage } = await generateBatchApproachAnalysis(batchItems, openaiApiKey);
173-
totalUsage = usage;
174-
for (let i = 0; i < batchResults.length; i++) {
175-
submissionResults[batchIndices[i]].matches = batchResults[i].matches;
176-
submissionResults[batchIndices[i]].explanation = batchResults[i].explanation;
177-
}
178-
} catch (error) {
179-
console.error(
180-
`[learningStatus] Batch analysis failed: ${error.message}`
181-
);
182-
}
183-
}
184-
185-
const hasUsage = totalUsage != null;
174+
const hasUsage = totalUsage.prompt_tokens > 0 || totalUsage.completion_tokens > 0;
186175

187-
// 6. ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์ง„ํ–‰๋„ ๊ณ„์‚ฐ
176+
// 5. ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์ง„ํ–‰๋„ ๊ณ„์‚ฐ
188177
const totalProblems = Object.keys(categories).length;
189178
const categoryProgress = buildCategoryProgress(categories, solvedProblems);
190179

191-
// 7. ๋Œ“๊ธ€ ๋ณธ๋ฌธ ํฌ๋งท
180+
// 6. ๋Œ“๊ธ€ ๋ณธ๋ฌธ ํฌ๋งท
192181
const commentBody = formatLearningStatusComment(
193182
username,
194183
submissionResults,
@@ -197,7 +186,7 @@ export async function postLearningStatus(
197186
categoryProgress
198187
);
199188

200-
// 8. ๋Œ“๊ธ€ ์ƒ์„ฑ ๋˜๋Š” ์—…๋ฐ์ดํŠธ
189+
// 7. ๋Œ“๊ธ€ ์ƒ์„ฑ ๋˜๋Š” ์—…๋ฐ์ดํŠธ
201190
await upsertLearningStatusComment(
202191
repoOwner,
203192
repoName,
@@ -207,7 +196,7 @@ export async function postLearningStatus(
207196
hasUsage ? totalUsage : null
208197
);
209198

210-
// 9. ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
199+
// 8. ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
211200
const matchedCount = submissionResults.filter((r) => r.matches === true).length;
212201
return { analyzed: submissionResults.length, matched: matchedCount };
213202
}

โ€Žutils/learningData.jsโ€Ž

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,16 @@ async function fetchActiveCohortProjectId(repoOwner, repoName, appToken) {
8080
}
8181

8282
/**
83-
* ๊ธฐ์ˆ˜ ํ”„๋กœ์ ํŠธ์˜ ์•„์ดํ…œ์„ ํŽ˜์ด์ง€๋„ค์ด์…˜ํ•˜๋ฉฐ ํ•ด๋‹น ์œ ์ €๊ฐ€ ๋จธ์ง€ํ•œ PR์˜
84-
* ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ GraphQL๋กœ ํ•œ ๋ฒˆ์— ์กฐํšŒํ•˜์—ฌ ํ’€์ดํ•œ ๋ฌธ์ œ ์ด๋ฆ„ ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
85-
*
86-
* PR๋ณ„ REST ํ˜ธ์ถœ ์—†์ด GraphQL ์‘๋‹ต์— files๋ฅผ ํฌํ•จ์‹œ์ผœ subrequest๋ฅผ ์ ˆ์•ฝํ•œ๋‹ค.
83+
* ๊ธฐ์ˆ˜ ํ”„๋กœ์ ํŠธ์—์„œ ํ•ด๋‹น ์œ ์ €๊ฐ€ ๋จธ์ง€ํ•œ PR ๋ฒˆํ˜ธ ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
84+
* ํ”„๋กœ์ ํŠธ ์•„์ดํ…œ์„ ํŽ˜์ด์ง€๋„ค์ด์…˜ํ•˜๋ฉฐ author.login์œผ๋กœ ํ•„ํ„ฐ๋งํ•œ๋‹ค.
8785
*
8886
* @param {string} projectId
8987
* @param {string} username
9088
* @param {string} appToken
91-
* @returns {Promise<string[]>}
89+
* @returns {Promise<number[]>}
9290
*/
93-
async function fetchCohortSolvedFromProject(projectId, username, appToken) {
94-
const usernamePattern = new RegExp(
95-
`^([^/]+)/${escapeRegExp(username)}\\.[^/]+$`
96-
);
97-
const problemNames = new Set();
91+
async function fetchUserMergedPRsInProject(projectId, username, appToken) {
92+
const prNumbers = [];
9893
let cursor = null;
9994

10095
while (true) {
@@ -108,11 +103,9 @@ async function fetchCohortSolvedFromProject(projectId, username, appToken) {
108103
nodes {
109104
content {
110105
... on PullRequest {
106+
number
111107
state
112108
author { login }
113-
files(first: 100) {
114-
nodes { path }
115-
}
116109
}
117110
}
118111
}
@@ -131,20 +124,15 @@ async function fetchCohortSolvedFromProject(projectId, username, appToken) {
131124
pr?.state === "MERGED" &&
132125
pr?.author?.login?.toLowerCase() === username.toLowerCase()
133126
) {
134-
for (const file of pr.files?.nodes || []) {
135-
const match = file.path.match(usernamePattern);
136-
if (match) {
137-
problemNames.add(match[1]);
138-
}
139-
}
127+
prNumbers.push(pr.number);
140128
}
141129
}
142130

143131
if (!pageInfo.hasNextPage) break;
144132
cursor = pageInfo.endCursor;
145133
}
146134

147-
return Array.from(problemNames);
135+
return prNumbers;
148136
}
149137

150138
/**
@@ -177,17 +165,31 @@ export async function fetchCohortUserSolutions(
177165
return fetchUserSolutions(repoOwner, repoName, username, appToken);
178166
}
179167

180-
const problems = await fetchCohortSolvedFromProject(
168+
const prNumbers = await fetchUserMergedPRsInProject(
181169
projectId,
182170
username,
183171
appToken
184172
);
185173

186174
console.log(
187-
`[fetchCohortUserSolutions] ${username} solved ${problems.length} problems in current cohort`
175+
`[fetchCohortUserSolutions] ${username} has ${prNumbers.length} merged PRs in current cohort`
188176
);
189177

190-
return problems;
178+
const problemNames = new Set();
179+
for (const prNumber of prNumbers) {
180+
const submissions = await fetchPRSubmissions(
181+
repoOwner,
182+
repoName,
183+
prNumber,
184+
username,
185+
appToken
186+
);
187+
for (const { problemName } of submissions) {
188+
problemNames.add(problemName);
189+
}
190+
}
191+
192+
return Array.from(problemNames);
191193
}
192194

193195
/**

โ€Žutils/openai.jsโ€Ž

Lines changed: 0 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -258,119 +258,6 @@ ${truncatedContent}
258258
};
259259
}
260260

261-
/**
262-
* ์—ฌ๋Ÿฌ ์†”๋ฃจ์…˜ ํŒŒ์ผ์˜ ์ ‘๊ทผ๋ฒ• ์ผ์น˜ ์—ฌ๋ถ€๋ฅผ ํ•œ ๋ฒˆ์˜ API ํ˜ธ์ถœ๋กœ ์ผ๊ด„ ๋ถ„์„.
263-
* subrequest ์ˆ˜๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ํŒŒ์ผ๋‹น ๊ฐœ๋ณ„ ํ˜ธ์ถœ ๋Œ€์‹  ๋ฐฐ์น˜๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.
264-
*
265-
* @param {Array<{problemName: string, fileContent: string, problemInfo: object}>} items
266-
* @param {string} apiKey - OpenAI API ํ‚ค
267-
* @returns {Promise<{results: Array<{matches: boolean, explanation: string}>, usage: object|null}>}
268-
*/
269-
export async function generateBatchApproachAnalysis(items, apiKey) {
270-
if (items.length === 0) return { results: [], usage: null };
271-
272-
// ๋‹จ๊ฑด์ด๋ฉด ๊ธฐ์กด ํ•จ์ˆ˜ ์œ„์ž„
273-
if (items.length === 1) {
274-
const { fileContent, problemName, problemInfo } = items[0];
275-
const result = await generateApproachAnalysis(fileContent, problemName, problemInfo, apiKey);
276-
return {
277-
results: [{ matches: result.matches, explanation: result.explanation }],
278-
usage: result.usage ?? null,
279-
};
280-
}
281-
282-
const systemPrompt = `You are an algorithm analysis expert. You will receive multiple problems. For each one, determine if the submitted code matches the intended approach.
283-
284-
Respond with a JSON object containing a "results" array with exactly ${items.length} entries, in the same order as the input:
285-
{
286-
"results": [
287-
{ "matches": true, "explanation": "ํ•œ๊ตญ์–ด 1๋ฌธ์žฅ, 80์ž ์ด๋‚ด" },
288-
...
289-
]
290-
}
291-
292-
Rules:
293-
- matches=true if the core data structure or algorithm matches the intended approach
294-
- matches=false if brute force was used when an optimized approach was intended
295-
- Keep each explanation to 1 sentence in Korean, 80 characters or fewer
296-
- You MUST return exactly ${items.length} results`;
297-
298-
const MAX_BATCH_FILE_SIZE = 5000;
299-
300-
const problemSections = items.map(({ problemName, fileContent, problemInfo }, i) => {
301-
const truncated = fileContent.slice(0, MAX_BATCH_FILE_SIZE);
302-
return `## ๋ฌธ์ œ ${i + 1}: ${problemName}
303-
- ๋‚œ์ด๋„: ${problemInfo.difficulty}
304-
- ์นดํ…Œ๊ณ ๋ฆฌ: ${(problemInfo.categories || []).join(", ")}
305-
- ์˜๋„๋œ ์ ‘๊ทผ๋ฒ•: ${problemInfo.intended_approach}
306-
307-
\`\`\`
308-
${truncated}
309-
\`\`\``;
310-
});
311-
312-
const userPrompt = problemSections.join("\n\n") +
313-
`\n\n์œ„ ${items.length}๊ฐœ ์ฝ”๋“œ๊ฐ€ ๊ฐ๊ฐ ์˜๋„๋œ ์ ‘๊ทผ๋ฒ•๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.`;
314-
315-
const response = await fetch("https://api.openai.com/v1/chat/completions", {
316-
method: "POST",
317-
headers: {
318-
Authorization: `Bearer ${apiKey}`,
319-
"Content-Type": "application/json",
320-
},
321-
body: JSON.stringify({
322-
model: "gpt-4.1-nano",
323-
messages: [
324-
{ role: "system", content: systemPrompt },
325-
{ role: "user", content: userPrompt },
326-
],
327-
response_format: { type: "json_object" },
328-
max_tokens: 200 * items.length,
329-
temperature: 0.2,
330-
}),
331-
});
332-
333-
if (!response.ok) {
334-
const error = await response.text();
335-
throw new Error(`OpenAI batch API error: ${error}`);
336-
}
337-
338-
const data = await response.json();
339-
const content = data.choices[0]?.message?.content;
340-
341-
if (!content) {
342-
throw new Error("Empty response from OpenAI batch analysis");
343-
}
344-
345-
let parsed;
346-
try {
347-
parsed = JSON.parse(content);
348-
} catch {
349-
throw new Error(`OpenAI returned invalid JSON: ${content.slice(0, 200)}`);
350-
}
351-
352-
const rawResults = parsed.results;
353-
if (!Array.isArray(rawResults)) {
354-
throw new Error(`OpenAI did not return a results array`);
355-
}
356-
357-
if (rawResults.length !== items.length) {
358-
console.warn(
359-
`[generateBatchApproachAnalysis] Expected ${items.length} results, got ${rawResults.length}`
360-
);
361-
}
362-
363-
const results = items.map((_, i) => {
364-
const r = rawResults[i];
365-
return {
366-
matches: r?.matches === true,
367-
explanation: typeof r?.explanation === "string" ? r.explanation : "",
368-
};
369-
});
370-
371-
return { results, usage: data.usage ?? null };
372-
}
373-
374261
/**
375262
* ์†”๋ฃจ์…˜์˜ ์‹œ๊ฐ„/๊ณต๊ฐ„ ๋ณต์žก๋„ ๋ถ„์„.
376263
* ์‚ฌ์šฉ์ž๊ฐ€ ์ฝ”๋“œ ์–ด๋”˜๊ฐ€์— ์ž์œ  ํฌ๋งท์œผ๋กœ ๋‚จ๊ธด TC/SC ์ฃผ์„์„ ํ•จ๊ป˜ ์ถ”์ถœํ•˜์—ฌ

0 commit comments

Comments
ย (0)