Created
June 25, 2025 16:00
-
-
Save UltiRequiem/b605b2f670d9cb913d52d2cc4df44340 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pLimit from "p-limit"; | |
export interface GitHubAnalyzerResponse { | |
success: boolean; | |
evaluation: Evaluation; | |
} | |
export interface Evaluation { | |
id: number; | |
username: string; | |
role: string; | |
overallScore: number; | |
seniorityLevel: string; | |
repositories: Repository[]; | |
summary: string; | |
strengths: string[]; | |
areasForImprovement: string[]; | |
additionalInsights?: string[]; | |
technologies: string[]; | |
categoryPromedies?: { | |
contributionPromedy: number; | |
relevancePromedy: number; | |
qualityPromedy: number; | |
starsPromedy: number; | |
forksPromedy: number; | |
lastActivityMonths: number; | |
maintainabilityPromedy?: number; | |
securityPromedy?: number; | |
testCoveragePromedy?: number; | |
documentationPromedy?: number; | |
performancePromedy?: number; | |
codeComplexityScore?: number; | |
}; | |
jobMatch: JobMatch; | |
} | |
export interface JobMatch { | |
explanation: string; | |
score: number; | |
} | |
export interface Repository { | |
name: string; | |
description: string; | |
stars: number; | |
forks: number; | |
language: string; | |
createdAt: string; | |
updatedAt: string; | |
score: number; | |
contributionScore: number; | |
relevanceScore: number; | |
qualityScore: number; | |
codeQualityMetrics: CodeQualityMetrics; | |
fullAnalysis: FullAnalysis; | |
} | |
export interface CodeQualityMetrics { | |
recencyScore: number; | |
popularityScore: number; | |
codeQualityScore: number; | |
categories: Category[]; | |
categoryInsights: CategoryInsight[]; | |
strengths: string[]; | |
weaknesses: string[]; | |
filesAnalyzed: number; | |
summary: string; | |
repoAge: string; | |
clusterScores: ClusterScores; | |
practiceRecommendations: string[]; | |
complexityIndicators: ComplexityIndicators; | |
} | |
export interface Category { | |
id: string; | |
name: string; | |
percentage: number; | |
weight: number; | |
explanation: string; | |
} | |
export interface CategoryInsight { | |
name: string; | |
score: number; | |
insight: string; | |
recommendation: string; | |
} | |
export interface ClusterScores { | |
maintainability: number; | |
security: number; | |
quality: number; | |
performance: number; | |
} | |
export interface ComplexityIndicators { | |
avgCategoryScore: number; | |
codeToTestRatio: number; | |
topCategory: TopCategory; | |
} | |
export interface TopCategory { | |
id: string; | |
name: string; | |
percentage: number; | |
weight: number; | |
explanation: string; | |
} | |
export interface FullAnalysis { | |
repository: string; | |
categories: Category2[]; | |
promedy: number; | |
summary: string; | |
filesAnalyzed: number; | |
fileSelectionData: FileSelectionData; | |
} | |
export interface Category2 { | |
id: string; | |
name: string; | |
percentage: number; | |
weight: number; | |
explanation: string; | |
} | |
export interface FileSelectionData { | |
totalFilesInRepo: number; | |
totalFilesSelected: number; | |
selectedFilePaths: SelectedFilePath[]; | |
} | |
export interface SelectedFilePath { | |
path: string; | |
importance: number; | |
reason: string; | |
} | |
export type AnalysisMode = "quick" | "exhaustive"; | |
/** | |
export const profileEvaluationSchema = z.object({ | |
username: GitHubUsername, | |
jobName: z.string().min(1, "Job name is required"), | |
jobDescription: z.string().optional(), | |
projectId: ProjectId.optional(), | |
backofficeUser: z.string().optional(), | |
}); | |
*/ | |
export interface GitHubAnalyzerRequest { | |
username: string; | |
jobName: string; | |
expectedSeniority?: string; | |
jobDescription: string; | |
projectId?: number; | |
backofficeUser?: string; | |
} | |
export interface GenericGithubAnalyzerRequest { | |
username: string; | |
} | |
export class GitHubAnalyzerClient { | |
private readonly timeout: number; | |
constructor( | |
private baseUrl: string, | |
private authToken: string, | |
timeout = 60_000, | |
) { | |
this.baseUrl = baseUrl; | |
this.authToken = authToken; | |
this.timeout = timeout; | |
} | |
private async fetchWithTimeout( | |
url: string, | |
options: RequestInit, | |
): Promise<Response> { | |
const controller = new AbortController(); | |
const timeoutId = setTimeout(() => controller.abort(), this.timeout); | |
try { | |
const response = await fetch(url, { | |
...options, | |
signal: controller.signal, | |
}); | |
return response; | |
} finally { | |
clearTimeout(timeoutId); | |
} | |
} | |
async getProfileById(id: number): Promise<GitHubAnalyzerResponse> { | |
const url = `${this.baseUrl}/analyze-profile/${id}`; | |
try { | |
const response = await this.fetchWithTimeout(url, { | |
method: "GET", | |
headers: { | |
"Content-Type": "application/json", | |
"analyzer-token": this.authToken, | |
}, | |
}); | |
if (!response.ok) { | |
throw new Error( | |
`GitHub Analyzer API error: ${response.status} ${response.statusText}`, | |
); | |
} | |
const data = await response.json(); | |
if (!data.success) { | |
throw new Error(data.code); | |
} | |
return data as GitHubAnalyzerResponse; | |
} catch (error) { | |
if (error instanceof Error && error.name === "AbortError") { | |
throw new Error("Request timeout: The analysis is taking too long"); | |
} | |
throw error; | |
} | |
} | |
async analyzeProfile( | |
analysisConfig: GitHubAnalyzerRequest, | |
): Promise<GitHubAnalyzerResponse> { | |
const url = `${this.baseUrl}/analyze-profile`; | |
try { | |
const response = await this.fetchWithTimeout(url, { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
"analyzer-token": this.authToken, | |
}, | |
body: JSON.stringify(analysisConfig), | |
}); | |
if (!response.ok) { | |
throw new Error( | |
`GitHub Analyzer API error: ${response.status} ${response.statusText}`, | |
); | |
} | |
const data = await response.json(); | |
if (!data.success) { | |
throw new Error(data.code); | |
} | |
return data as GitHubAnalyzerResponse; | |
} catch (error) { | |
if (error instanceof Error && error.name === "AbortError") { | |
throw new Error("Request timeout: The analysis is taking too long"); | |
} | |
throw error; | |
} | |
} | |
async analyzeBatch( | |
profiles: GitHubAnalyzerRequest[], | |
): Promise<GitHubAnalyzerResponse[]> { | |
const results = await Promise.all( | |
profiles.map((analyzerConfig) => this.analyzeProfile(analyzerConfig)), | |
); | |
return results; | |
} | |
async genericAnalysis(request: GenericGithubAnalyzerRequest) { | |
const url = `${this.baseUrl}/api/analyze-profile-generic`; | |
const response = await fetch(url, { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
"analyzer-token": this.authToken, | |
}, | |
body: JSON.stringify(request), | |
}); | |
if (!response.ok) { | |
console.log(response); | |
throw new Error("Failed to fetch generic analysis"); | |
} | |
const data = await response.json(); | |
return data; | |
} | |
async analyzeGenericBatch(profiles: string[]) { | |
const input: GenericGithubAnalyzerRequest[] = profiles.map((username) => ({ | |
username, | |
})); | |
const limit = pLimit(35); | |
const promises = input.map((request) => | |
limit(() => this.genericAnalysis(request)), | |
); | |
const resultsData = await Promise.all(promises); | |
return resultsData; | |
} | |
} | |
const client = new GitHubAnalyzerClient( | |
"https://code-quality-ms-production.up.railway.app", | |
"clnDUQDXoH6DnhsOYw2uy4_SOEj1GcZfynjiT-BjtSA", | |
); | |
const results = await client.analyzeGenericBatch([ | |
"https://github.com/GEnma29?tab=repositories", | |
"http://www.github.com/SantiagoRatto", | |
"https://github.com/phantom0109", | |
"https://github.com/jonathan-messina", | |
]); | |
console.log(results); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment