diff --git a/app/projects/components/project-overview.tsx b/app/projects/components/project-overview.tsx
index df05917e..47e49c9e 100644
--- a/app/projects/components/project-overview.tsx
+++ b/app/projects/components/project-overview.tsx
@@ -244,6 +244,30 @@ export function ProjectOverview({ onViewQuestions, projectId, orgId }: ProjectOv
)}
+ {/* Vendor Eligibility */}
+ {project.eligibility && project.eligibility.length > 0 && (
+
+
+
+
+ Vendor Eligibility Requirements
+
+
+
+
+ {project.eligibility.map((requirement: string, index: number) => (
+ -
+
+
+ {requirement}
+
+
+ ))}
+
+
+
+ )}
+
{/* Consolidated Project Summary */}
diff --git a/lib/interfaces/ai-service.ts b/lib/interfaces/ai-service.ts
index 88af81f6..9f2034df 100644
--- a/lib/interfaces/ai-service.ts
+++ b/lib/interfaces/ai-service.ts
@@ -13,6 +13,11 @@ export interface IAIQuestionExtractor {
* Generate a summary of the RFP document
*/
generateSummary(content: string, documentName: string): Promise;
+
+ /**
+ * Extract vendor eligibility requirements as bullet points
+ */
+ extractEligibility(content: string, documentName: string): Promise;
}
/**
diff --git a/lib/project-service.ts b/lib/project-service.ts
index efdb8bfa..5fc1d634 100644
--- a/lib/project-service.ts
+++ b/lib/project-service.ts
@@ -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) {
console.log(`Saving answers for project ${projectId}. Total answers: ${Object.keys(answers).length}`);
diff --git a/lib/services/openai-question-extractor.ts b/lib/services/openai-question-extractor.ts
index e2826461..7854efae 100644
--- a/lib/services/openai-question-extractor.ts
+++ b/lib/services/openai-question-extractor.ts
@@ -60,6 +60,49 @@ export class OpenAIQuestionExtractor implements IAIQuestionExtractor {
}
}
+ /**
+ * Extract vendor eligibility requirements from RFP document
+ */
+ async extractEligibility(content: string, documentName: string): Promise {
+ 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
*/
diff --git a/lib/services/question-extraction-service.ts b/lib/services/question-extraction-service.ts
index 6c7b30e0..920a867a 100644
--- a/lib/services/question-extraction-service.ts
+++ b/lib/services/question-extraction-service.ts
@@ -13,16 +13,18 @@ export class QuestionExtractionService implements IQuestionExtractionService {
*/
async processDocument(request: ExtractQuestionsRequest): Promise {
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 {
+ 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 {
+ 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
*/
diff --git a/lib/validators/extract-questions.ts b/lib/validators/extract-questions.ts
index 22869fcb..c886fe42 100644
--- a/lib/validators/extract-questions.ts
+++ b/lib/validators/extract-questions.ts
@@ -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
diff --git a/prisma/migrations/20250813184727_add_project_eligibility/migration.sql b/prisma/migrations/20250813184727_add_project_eligibility/migration.sql
new file mode 100644
index 00000000..d3aec581
--- /dev/null
+++ b/prisma/migrations/20250813184727_add_project_eligibility/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "projects" ADD COLUMN "eligibility" TEXT[];
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 341586d0..4650a741 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -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[]
diff --git a/types/project.ts b/types/project.ts
index e3bdcb48..b4c2b72e 100644
--- a/types/project.ts
+++ b/types/project.ts
@@ -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;