now we alos extract key eligibility required for the vendor

This commit is contained in:
Zhaoqi Li
2025-08-13 11:55:26 -07:00
parent 91df641eee
commit d76058e7b3
9 changed files with 160 additions and 5 deletions
@@ -244,6 +244,30 @@ export function ProjectOverview({ onViewQuestions, projectId, orgId }: ProjectOv
</Card>
)}
{/* Vendor Eligibility */}
{project.eligibility && project.eligibility.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Users className="h-5 w-5" />
Vendor Eligibility Requirements
</CardTitle>
</CardHeader>
<CardContent>
<ul className="space-y-2">
{project.eligibility.map((requirement: string, index: number) => (
<li key={index} className="flex items-start gap-3">
<div className="h-1.5 w-1.5 rounded-full bg-primary mt-2.5 flex-shrink-0" />
<span className="text-muted-foreground leading-relaxed">
{requirement}
</span>
</li>
))}
</ul>
</CardContent>
</Card>
)}
{/* Consolidated Project Summary */}
<Card>
<CardContent>
+5
View File
@@ -13,6 +13,11 @@ export interface IAIQuestionExtractor {
* Generate a summary of the RFP document
*/
generateSummary(content: string, documentName: string): Promise<string>;
/**
* Extract vendor eligibility requirements as bullet points
*/
extractEligibility(content: string, documentName: string): Promise<string[]>;
}
/**
+17
View File
@@ -25,6 +25,7 @@ export const projectService = {
name: true,
description: true,
summary: true,
eligibility: true,
createdAt: true,
updatedAt: true,
organizationId: true
@@ -42,6 +43,7 @@ export const projectService = {
name: true,
description: true,
summary: true,
eligibility: true,
createdAt: true,
updatedAt: true,
organizationId: true,
@@ -68,6 +70,7 @@ export const projectService = {
name: true,
description: true,
summary: true,
eligibility: true,
createdAt: true,
updatedAt: true,
organizationId: true,
@@ -293,6 +296,20 @@ export const projectService = {
}
},
// Eligibility operations
async saveEligibility(projectId: string, eligibility: string[]) {
try {
await db.project.update({
where: { id: projectId },
data: { eligibility },
});
console.log(`Successfully saved eligibility for project ${projectId}`);
} catch (error) {
console.error(`Error saving eligibility for project ${projectId}:`, error);
throw error;
}
},
// Answer operations
async saveAnswers(projectId: string, answers: Record<string, { text: string; sources?: AnswerSource[] }>) {
console.log(`Saving answers for project ${projectId}. Total answers: ${Object.keys(answers).length}`);
+79
View File
@@ -60,6 +60,49 @@ export class OpenAIQuestionExtractor implements IAIQuestionExtractor {
}
}
/**
* Extract vendor eligibility requirements from RFP document
*/
async extractEligibility(content: string, documentName: string): Promise<string[]> {
try {
const systemPrompt = this.getEligibilitySystemPrompt();
const response = await this.client.chat.completions.create({
model: this.config.model,
response_format: { type: "json_object" },
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: this.formatUserPrompt(content, documentName) }
],
temperature: 0.1, // Low temperature for precise extraction
max_tokens: 1000, // Allow for comprehensive eligibility lists
});
const assistantMessage = response.choices[0]?.message?.content;
if (!assistantMessage) {
throw new AIServiceError('Empty response from OpenAI for eligibility extraction');
}
// Parse and validate the JSON response
const rawData = JSON.parse(assistantMessage);
// Expect format: { "eligibility": ["requirement 1", "requirement 2", ...] }
if (!rawData.eligibility || !Array.isArray(rawData.eligibility)) {
throw new AIServiceError('Invalid eligibility format from AI service');
}
return rawData.eligibility.filter((item: any) => typeof item === 'string' && item.trim().length > 0);
} catch (error) {
if (error instanceof SyntaxError) {
throw new AIServiceError('Invalid JSON response from AI service for eligibility extraction');
}
if (error instanceof AIServiceError) {
throw error;
}
throw new AIServiceError(`Eligibility extraction failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Extract structured questions from document content
*/
@@ -122,6 +165,42 @@ Do not include section numbers, question lists, or administrative details like s
`.trim();
}
/**
* Get the system prompt for vendor eligibility extraction
*/
private getEligibilitySystemPrompt(): string {
return `
You are an expert at analyzing RFP (Request for Proposal) documents and extracting vendor eligibility requirements.
Your task is to read through the RFP document and identify all key eligibility criteria that vendors must meet to qualify for this proposal. Focus on extracting:
1. Minimum experience requirements (years in business, project experience)
2. Technical qualifications and certifications
3. Financial requirements (bonding, insurance, revenue thresholds)
4. Geographic restrictions or preferences
5. Industry-specific licenses or accreditations
6. Staff qualifications and expertise requirements
7. Past performance criteria
8. Legal and compliance requirements
9. Size classifications (small business, minority-owned, etc.)
10. Any other mandatory qualifications mentioned
Format your response as a JSON object with an "eligibility" array containing clear, concise bullet points. Each requirement should be a standalone statement that a vendor can easily evaluate against their own qualifications.
Example format:
{
"eligibility": [
"Minimum 5 years of experience in software development",
"Must hold current ISO 27001 certification",
"Annual revenue of at least $10 million",
"Licensed to operate in the State of California"
]
}
Focus only on mandatory requirements, not preferences. If no clear eligibility criteria are found, return an empty array.
`.trim();
}
/**
* Get the system prompt for question extraction
*/
+30 -5
View File
@@ -13,16 +13,18 @@ export class QuestionExtractionService implements IQuestionExtractionService {
*/
async processDocument(request: ExtractQuestionsRequest): Promise<ExtractQuestionsResponse> {
try {
// Stage 1: Extract questions and generate summary using AI (parallel execution)
const [extractedQuestions, summary] = await Promise.all([
// Stage 1: Extract questions, generate summary, and extract eligibility using AI (parallel execution)
const [extractedQuestions, summary, eligibility] = await Promise.all([
this.extractQuestions(request.content, request.documentName),
this.generateSummary(request.content, request.documentName)
this.generateSummary(request.content, request.documentName),
this.extractEligibility(request.content, request.documentName)
]);
// Stage 2: Save to database (questions and summary)
// Stage 2: Save to database (questions, summary, and eligibility)
await Promise.all([
this.saveQuestions(request.projectId, extractedQuestions.sections),
this.saveSummary(request.projectId, summary)
this.saveSummary(request.projectId, summary),
this.saveEligibility(request.projectId, eligibility)
]);
// Stage 3: Return structured response
@@ -32,6 +34,7 @@ export class QuestionExtractionService implements IQuestionExtractionService {
sections: extractedQuestions.sections,
extractedAt: new Date().toISOString(),
summary, // Include summary in response
eligibility, // Include eligibility in response
};
return response;
@@ -65,6 +68,17 @@ export class QuestionExtractionService implements IQuestionExtractionService {
}
}
/**
* Extract eligibility requirements from content using AI
*/
private async extractEligibility(content: string, documentName: string): Promise<string[]> {
try {
return await openAIQuestionExtractor.extractEligibility(content, documentName);
} catch (error) {
throw new AIServiceError(`AI eligibility extraction failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Save extracted questions to storage
*/
@@ -87,6 +101,17 @@ export class QuestionExtractionService implements IQuestionExtractionService {
}
}
/**
* Save extracted eligibility requirements to storage
*/
async saveEligibility(projectId: string, eligibility: string[]): Promise<void> {
try {
await projectService.saveEligibility(projectId, eligibility);
} catch (error) {
throw new DatabaseError(`Failed to save eligibility: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Get extraction statistics
*/
+1
View File
@@ -31,6 +31,7 @@ export const ExtractQuestionsResponseSchema = z.object({
sections: z.array(SectionSchema),
extractedAt: z.string(),
summary: z.string().optional(), // RFP summary generated by AI
eligibility: z.array(z.string()).optional(), // Vendor eligibility requirements as bullet points
});
// Type exports
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "projects" ADD COLUMN "eligibility" TEXT[];
+1
View File
@@ -72,6 +72,7 @@ model Project {
name String
description String?
summary String? // RFP summary generated from uploaded documents
eligibility String[] // Vendor eligibility requirements as bullet points
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
questions Question[]
+1
View File
@@ -3,6 +3,7 @@ export interface Project {
name: string;
description?: string;
summary?: string; // RFP summary generated from uploaded documents
eligibility?: string[]; // Vendor eligibility requirements as bullet points
createdAt: string;
progress?: number;
status?: string;