Skip to main content
POST
/
api
/
proposals
/
{proposal_id}
/
analyze-concept
Analyze Concept
curl --request POST \
  --url https://api.example.com/api/proposals/{proposal_id}/analyze-concept \
  --header 'Content-Type: application/json' \
  --data '{
  "force": true
}'
{
  "status": "<string>",
  "message": "<string>",
  "started_at": "<string>",
  "cached": true,
  "concept_analysis": {
    "sections_needing_elaboration": [
      {
        "title": "<string>",
        "current_content": "<string>",
        "gaps": [
          {}
        ],
        "suggestions": [
          {}
        ]
      }
    ],
    "alignment_score": 123,
    "strengths": [
      {}
    ],
    "concerns": [
      {}
    ]
  },
  "completed_at": "<string>",
  "error": "<string>"
}

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:
  1. Trigger: POST to /analyze-concept returns immediately
  2. Lambda Worker: Backend invokes AnalysisWorkerFunction with analysis_type: "concept"
  3. Polling: Poll GET /concept-status for completion
  4. Result: Analysis includes sections needing elaboration and alignment assessment

Request

proposal_id
string
required
The proposal ID or code (format: PROP-YYYYMMDD-XXXX)
force
boolean
default:"false"
If true, forces a new analysis even if one already exists. Use this when the concept document has been re-uploaded.

Response

status
string
  • processing: Analysis started successfully
  • completed: Analysis already exists (cached)
message
string
User-friendly status message
started_at
string
ISO 8601 timestamp when analysis started
cached
boolean
true if returning cached results
concept_analysis
object
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:
StatusDescription
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

proposal_id
string
required
The proposal ID or code

Response

status
string
Current status: not_started, processing, completed, or failed
concept_analysis
object
Analysis results (only when completed)
completed_at
string
ISO timestamp (only when completed)
started_at
string
ISO timestamp (only when processing)
error
string
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:
  1. Review identified gaps and suggestions
  2. Select which sections to include in the refined concept document
  3. 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.

Build docs developers (and LLMs) love