Overview
Analyzes the uploaded concept document against RFP requirements and identifies sections that need elaboration. This is part of Step 2 in the proposal workflow.
Prerequisite : RFP analysis must be completed before analyzing the concept.
Workflow Pattern
Follows the same asynchronous Lambda worker pattern as RFP analysis:
Trigger : POST to /analyze-concept returns immediately
Lambda Worker : Backend invokes AnalysisWorkerFunction with analysis_type: "concept"
Polling : Poll GET /concept-status for completion
Result : Analysis includes sections needing elaboration and alignment assessment
Request
The proposal ID or code (format: PROP-YYYYMMDD-XXXX)
If true, forces a new analysis even if one already exists. Use this when the concept document has been re-uploaded.
Response
processing: Analysis started successfully
completed: Analysis already exists (cached)
User-friendly status message
ISO 8601 timestamp when analysis started
true if returning cached results
Analysis data (only present if cached)
Example Request
curl -X POST "https://api.igad-innovation.org/api/proposals/PROP-20260304-A1B2/analyze-concept" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "force": false }'
Example Response
First Call (Processing)
{
"status" : "processing" ,
"message" : "Concept analysis started. Poll /concept-status for completion." ,
"started_at" : "2026-03-04T10:35:00.000Z"
}
With Cached Result
{
"status" : "completed" ,
"concept_analysis" : {
"sections_needing_elaboration" : [
{
"title" : "Technical Architecture" ,
"current_content" : "We will build a scalable platform..." ,
"gaps" : [ "Missing technology stack details" , "No scalability metrics" ],
"suggestions" : [ "Specify database technology" , "Define expected user load" ]
}
],
"alignment_score" : 75 ,
"strengths" : [ "Clear problem statement" , "Well-defined target audience" ],
"concerns" : [ "Budget justification lacks detail" ]
},
"message" : "Concept already analyzed" ,
"cached" : true
}
Force Re-analysis
When a user re-uploads their concept document, use force: true to trigger a fresh analysis:
const reanalyzeAfterUpload = async ( proposalId : string ) => {
const response = await fetch (
`/api/proposals/ ${ proposalId } /analyze-concept` ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ force: true })
}
)
return response . json ()
}
What Force Re-analysis Clears
When force: true is provided, the endpoint removes:
REMOVE concept_analysis,
concept_analysis_completed_at,
concept_analysis_error,
concept_evaluation,
concept_document_v2,
structure_workplan_analysis,
structure_workplan_completed_at,
structure_workplan_error
SET analysis_status_concept = :not_started
Cascading Effect : Force re-analysis clears downstream artifacts (concept document, structure workplan) since they depend on the concept analysis.
Status Values
The analysis_status_concept field tracks the analysis state:
Status Description not_startedNo analysis triggered yet processingLambda worker is analyzing completedAnalysis finished successfully failedAnalysis encountered an error
Polling for Status
GET /api/proposals/{proposal_id}/concept-status Check concept analysis completion status
Polling Example
const pollConceptStatus = async ( proposalId : string ) => {
const maxAttempts = 100 // 5 minutes at 3-second intervals
let attempts = 0
const interval = setInterval ( async () => {
attempts ++
if ( attempts > maxAttempts ) {
clearInterval ( interval )
throw new Error ( 'Analysis timeout' )
}
const response = await fetch (
`/api/proposals/ ${ proposalId } /concept-status` ,
{ headers: { Authorization: `Bearer ${ token } ` } }
)
const data = await response . json ()
if ( data . status === 'completed' ) {
clearInterval ( interval )
handleConceptAnalysis ( data . concept_analysis )
} else if ( data . status === 'failed' ) {
clearInterval ( interval )
showError ( data . error )
}
}, 3000 )
}
Lambda Worker Details
Lambda Invocation Payload
lambda_client.invoke(
FunctionName = worker_function_arn,
InvocationType = "Event" , # Async
Payload = json.dumps({
"proposal_id" : proposal_code, # PROP-YYYYMMDD-XXXX format
"analysis_type" : "concept"
})
)
DynamoDB Status Management
Before invocation:
await db_client.update_item(
pk = pk,
sk = "METADATA" ,
update_expression = "SET analysis_status_concept = :status, concept_analysis_started_at = :started" ,
expression_attribute_values = {
":status" : "processing" ,
":started" : datetime.utcnow().isoformat()
}
)
After completion (in worker):
db_client.update_item_sync(
pk = pk,
sk = "METADATA" ,
update_expression = "SET analysis_status_concept = :status, concept_analysis = :analysis, concept_analysis_completed_at = :completed" ,
expression_attribute_values = {
":status" : "completed" ,
":analysis" : result,
":completed" : datetime.utcnow().isoformat()
}
)
Error Handling
Status Code 400 - Missing RFP Analysis
{
"detail" : "RFP analysis must be completed first"
}
Status Code 400 - No Concept Document
{
"detail" : "Concept document not found. Please upload a concept document or provide initial concept text."
}
Status Code 403
{
"detail" : "Access denied"
}
Status Code 404
{
"detail" : "Proposal not found"
}
Status Code 500
{
"detail" : "Concept analysis failed: Worker Lambda invocation error"
}
GET Concept Status
Description
Poll this endpoint to check concept analysis completion status.
Request
Response
Current status: not_started, processing, completed, or failed
Analysis results (only when completed) Show concept_analysis structure
sections_needing_elaboration
Array of sections that need more detail Excerpt from current concept
Recommendations for improvement
Score from 0-100 indicating alignment with RFP
List of strong points in the concept
List of concerns or risks
ISO timestamp (only when completed)
ISO timestamp (only when processing)
Error message (only when failed)
Example Response (Completed)
{
"status" : "completed" ,
"concept_analysis" : {
"sections_needing_elaboration" : [
{
"title" : "Methodology" ,
"current_content" : "We will use agile development practices..." ,
"gaps" : [
"No sprint duration specified" ,
"Missing stakeholder engagement plan"
],
"suggestions" : [
"Define sprint length (1-2 weeks recommended)" ,
"Outline monthly stakeholder review meetings"
]
},
{
"title" : "Risk Management" ,
"current_content" : "We have identified potential technical risks..." ,
"gaps" : [
"No mitigation strategies provided"
],
"suggestions" : [
"Add specific mitigation actions for each identified risk"
]
}
],
"alignment_score" : 78 ,
"strengths" : [
"Clear understanding of target beneficiaries" ,
"Realistic timeline" ,
"Strong team qualifications"
],
"concerns" : [
"Budget distribution heavily weighted toward personnel costs" ,
"Limited discussion of sustainability post-project"
]
},
"completed_at" : "2026-03-04T10:37:23.000Z"
}
Example Response (Processing)
{
"status" : "processing" ,
"started_at" : "2026-03-04T10:35:00.000Z"
}
Example Response (Failed)
{
"status" : "failed" ,
"error" : "Unable to parse concept document format"
}
Next Steps
After concept analysis completes, the user can:
Review identified gaps and suggestions
Select which sections to include in the refined concept document
Trigger concept document generation:
POST /api/proposals/{proposal_id}/generate-concept-document Generate enhanced concept document based on analysis
Best Practices
When to use force re-analysis :
User uploads a new version of their concept document
User significantly edits their initial concept text (100+ characters)
Previous analysis encountered an error and you want to retry
Always check for cached results first - if status: "completed" is returned immediately with data, skip the polling step entirely.