mirror of
https://github.com/run-llama/auto_rfp.git
synced 2026-06-30 21:57:56 -04:00
now we alos extract key eligibility required for the vendor
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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[];
|
||||
@@ -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[]
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user