Skip to content

Reports improvements #682

Description

@ngoiyaeric

Intelligence executive summary should be a AI generated summary of all chats in the system at the time in context.

Place the sensor fusion prior to the strategic output.

The strategic output should be focused on the new knowledge derived from the exploration and not a replica of the sensor fusion.

Perhaps create functions to deploy the multi-agents to synthesize the report.

Based on the codebase context, you're asking about the intelligence report generation system in QCX, specifically the ReportTemplate component and the generateReportContext function. Here's the current implementation and how it relates to your requirements:

Current Implementation

The intelligence executive summary is currently generated by the generateReportContext function in lib/actions/chat.ts, which uses a single AI call to synthesize a title and 150-200 word summary from the chat messages 1 . This is called from the DownloadReportButton component when the user triggers report generation 2 .

The ReportTemplate currently structures the report as:

  1. Intelligence Executive Summary (section "00") - displays the AI-generated summary 3
  2. Executive Visualization (section "01") - displays the map snapshot 4
  3. Intelligence Assessment (section "02") - contains both sensor fusion results and strategic output mixed together 5

Addressing Your Requirements

1. AI-Generated Summary of All Chats

The current generateReportContext already generates an AI summary from chat messages 6 , but it filters to only include user messages and assistant responses. To include all chats in context, you would need to modify the filtering logic in this function.

2. Sensor Fusion Prior to Strategic Output

Currently, sensor fusion (resolution search results) and strategic output (regular responses) are interleaved in the Intelligence Assessment section based on message order 7 . To place sensor fusion prior to strategic output, you would need to restructure the filtering and rendering logic in ReportTemplate to group all resolution_search_result messages before response messages.

3. Strategic Output Focused on New Knowledge

The current writer agent generates concise responses based on search results 8 , but there's no specific logic to ensure strategic output doesn't replicate sensor fusion findings. You would need to modify the writer agent's system prompt or create a new agent specifically for strategic synthesis that explicitly avoids duplicating sensor fusion content.

4. Multi-Agent Report Synthesis

Currently, report generation uses a single generateText call in generateReportContext 9 . To deploy multi-agents for report synthesis, you could create a new orchestration function similar to the existing agent pipeline (taskManager → researcher → writer) that specifically handles report generation with specialized agents for different sections.

Notes

The current report template filters out short transition messages (under 500 characters) to reduce redundancy 10 , but this doesn't address the specific requirement of ensuring strategic output focuses on new knowledge rather than replicating sensor fusion. The system already has a sophisticated multi-agent architecture for query processing 11 , but this is not currently leveraged for report synthesis.

Wiki pages you might want to explore:

Citations

File: lib/actions/chat.ts (L25-84)

export async function generateReportContext(messages: AIMessage[]) {
  try {
    const model = await getModel()

    const promptMessages = messages
      .filter(msg => msg.role === 'user' || (msg.role === 'assistant' && msg.type === 'response'))
      .map(msg => {
        const role = msg.role === 'user' ? 'user' as const : 'assistant' as const
        const rawContent =
          typeof msg.content === 'string'
            ? msg.content
            : Array.isArray(msg.content)
              ? msg.content.map(p => (p && typeof p === 'object' && 'type' in p && p.type === 'text') ? p.text : '').join('\n')
              : JSON.stringify(msg.content)

        // Sanitize: strip huge base64 images if any are still in there
        const content = rawContent
          .replace(/data:image\/[a-zA-Z0-9.+-]+;base64,[A-Za-z0-9+/=]+/g, '[image omitted]')
          .trim()

        return { role, content }
      })
      .filter(msg => msg.content.length > 0)

    const { text } = await generateText({
      model,
      system: `You are a high-level geospatial intelligence analyst. Based on the provided conversation, generate:
      1. A professional, concise report title (max 60 characters).
      2. A 150-200 word executive summary that synthesizes the intelligence findings, observations, and spatial analysis discussed.

      Format your response as a JSON object:
      {
        "title": "The Title Here",
        "summary": "The executive summary here..."
      }
      Do not include any other text or markdown formatting in your response.`,
      messages: promptMessages as any,
    })

    try {
      return JSON.parse(text) as { title: string; summary: string }
    } catch (e) {
      console.error('Failed to parse AI response for report context', {
        error: e instanceof Error ? e.message : String(e),
        preview: text.slice(0, 200)
      })
      // Fallback
      return {
        title: 'QCX Intelligence Analysis',
        summary: 'Executive summary generation failed, but manual review of the intelligence assessment is recommended.'
      }
    }
  } catch (error) {
    console.error('Error generating report context:', error)
    return {
      title: 'QCX Intelligence Analysis',
      summary: 'Automated executive summary is currently unavailable.'
    }
  }
}

File: components/download-report-button.tsx (L56-64)

      const formData = new FormData();
      formData.append('action', 'generate_report_context');
      formData.append('messages', JSON.stringify(aiState.messages));

      const { title, summary } = await (actions as any).submit(formData);

      const finalTitle = title || 'QCX Intelligence Analysis'
      setReportTitle(finalTitle)
      setReportSummary(summary)

File: components/report-template.tsx (L50-64)

  const filteredMessages = rawFiltered.filter((message, index) => {
    // If this is a response followed by a resolution search result,
    // and the response is relatively short (likely a transition/redundant summary), filter it out.
    if (message.type === 'response' && index + 1 < rawFiltered.length) {
      const nextMessage = rawFiltered[index + 1]
      if (nextMessage.role === 'assistant' && (nextMessage.type === 'resolution_search_result' || nextMessage.type === 'response')) {
        const content = renderMessageContent(message.content)
        // Filter out short transition messages or duplicated high-level summaries
        if (content.length < 500) {
          return false
        }
      }
    }
    return true
  })

File: components/report-template.tsx (L109-123)

        {aiSummary && (
          <section className="break-inside-avoid">
            <div className="flex items-baseline gap-4 mb-8 border-b border-slate-100 pb-4">
              <span className="text-4xl font-black text-slate-200">00</span>
              <h2 className="text-2xl font-black text-[#003366] uppercase tracking-tight">Intelligence Executive Summary</h2>
            </div>
            <div className="bg-slate-50 p-10 rounded-2xl border-l-8 border-[#003366] shadow-inner">
              <div className="prose prose-slate prose-lg max-w-none text-slate-800 font-medium leading-relaxed">
                <ReactMarkdown remarkPlugins={[remarkGfm]}>
                  {aiSummary}
                </ReactMarkdown>
              </div>
            </div>
          </section>
        )}

File: components/report-template.tsx (L125-138)

        {mapSnapshot && (
          <section className="break-inside-avoid">
            <div className="flex items-baseline gap-4 mb-8 border-b border-slate-100 pb-4">
              <span className="text-4xl font-black text-slate-200">01</span>
              <h2 className="text-2xl font-black text-[#003366] uppercase tracking-tight">Executive Visualization</h2>
            </div>
            <div className="border-[12px] border-slate-50 rounded-2xl overflow-hidden shadow-2xl">
              <img src={mapSnapshot} alt="Map Snapshot" className="w-full h-auto block" crossOrigin="anonymous" />
              <div className="bg-slate-50 px-6 py-4 text-xs text-slate-500 text-center font-bold tracking-wide border-t border-slate-100">
                ORBITAL PERSPECTIVE REF: {Math.random().toString(16).substring(2, 10).toUpperCase()}
              </div>
            </div>
          </section>
        )}

File: components/report-template.tsx (L140-225)

        <section>
          <div className="flex items-baseline gap-4 mb-12 border-b border-slate-100 pb-4">
            <span className="text-4xl font-black text-slate-200">02</span>
            <h2 className="text-2xl font-black text-[#003366] uppercase tracking-tight">Intelligence Assessment</h2>
          </div>
          <div className="space-y-16">
            {filteredMessages.map((message, index) => {
              const contentString = renderMessageContent(message.content)

              if (message.type === 'input' || message.type === 'input_related') {
                let content = ''
                try {
                  const json = JSON.parse(contentString)
                  content = message.type === 'input' ? (json.input || contentString) : (json.related_query || contentString)
                } catch (e) {
                  content = contentString
                }
                return (
                  <div key={index} className="bg-[#003366] p-10 rounded-2xl shadow-xl break-inside-avoid relative overflow-hidden group">
                    <div className="absolute top-0 right-0 p-4">
                      <svg className="w-8 h-8 text-white opacity-10" fill="currentColor" viewBox="0 0 24 24"><path d="M14.017 21L14.017 18C14.017 16.8954 14.9124 16 16.017 16H19.017C19.5693 16 20.017 15.5523 20.017 15V9C20.017 8.44772 19.5693 8 19.017 8H16.017C14.9124 8 14.017 7.10457 14.017 6V3L14.017 3C14.017 2.44772 14.4647 2 15.017 2H21.017C21.5693 2 22.017 2.44772 22.017 3V15C22.017 18.3137 19.3307 21 16.017 21H14.017ZM3.017 21L3.017 18C3.017 16.8954 3.91243 16 5.017 16H8.017C8.56928 16 9.017 15.5523 9.017 15V9C9.017 8.44772 8.56928 8 8.017 8H5.017C3.91243 8 3.017 7.10457 3.017 6V3L3.017 3C3.017 2.44772 3.46472 2 4.017 2H10.017C10.5693 2 11.017 2.44772 11.017 3V15C11.017 18.3137 8.33072 21 5.017 21H3.017Z" /></svg>
                    </div>
                    <p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] mb-4">Primary Inquiry</p>
                    <p className="text-2xl text-white font-bold leading-tight tracking-tight italic">
                      {content}
                    </p>
                  </div>
                )
              } else if (message.type === 'response') {
                return (
                  <div key={index} className="prose prose-slate prose-lg max-w-none break-inside-avoid">
                    <div className="flex items-center gap-3 mb-6">
                      <div className="w-1.5 h-6 bg-emerald-500"></div>
                      <p className="text-xs font-black text-emerald-700 uppercase tracking-[0.2em] m-0">Strategic Output</p>
                    </div>
                    <div className="text-slate-700 leading-relaxed font-medium">
                      <ReactMarkdown remarkPlugins={[remarkGfm]}>
                        {contentString}
                      </ReactMarkdown>
                    </div>
                  </div>
                )
              } else if (message.type === 'resolution_search_result') {
                try {
                  const result = JSON.parse(contentString)
                  return (
                    <div key={index} className="space-y-8 bg-slate-50 p-12 rounded-3xl border border-slate-100 break-inside-avoid">
                      <div className="flex items-center justify-between">
                        <p className="text-xs font-black text-indigo-700 uppercase tracking-[0.2em]">Sensor Fusion Analysis</p>
                        <span className="px-2 py-0.5 bg-indigo-100 text-indigo-700 text-[9px] font-bold rounded">LEVEL 4 DATA</span>
                      </div>
                      {result.summary && (
                        <div className="text-slate-800 text-lg font-bold leading-snug bg-white p-8 rounded-2xl border border-slate-100 shadow-sm">
                          {result.summary}
                        </div>
                      )}
                      <div className="flex flex-col items-center gap-12">
                        {result.mapboxImage && (
                          <div className="space-y-4 w-full max-w-2xl">
                            <div className="flex items-center justify-center gap-2">
                              <div className="w-2 h-2 rounded-full bg-indigo-500"></div>
                              <p className="text-[10px] text-slate-400 font-black uppercase tracking-widest">Spectral RGB Analysis View</p>
                            </div>
                            <img src={result.mapboxImage} alt="Mapbox View" className="rounded-xl border-4 border-white shadow-xl w-full block mx-auto" crossOrigin="anonymous" />
                          </div>
                        )}
                        {result.googleImage && (
                          <div className="space-y-4 w-full max-w-2xl">
                            <div className="flex items-center justify-center gap-2">
                              <div className="w-2 h-2 rounded-full bg-orange-500"></div>
                              <p className="text-[10px] text-slate-400 font-black uppercase tracking-widest">High-Resolution Panchromatic Satellite</p>
                            </div>
                            <img src={result.googleImage} alt="Google Satellite" className="rounded-xl border-4 border-white shadow-xl w-full block mx-auto" crossOrigin="anonymous" />
                          </div>
                        )}
                      </div>
                    </div>
                  )
                } catch (e) {
                  return null
                }
              }
              return null
            })}
          </div>
        </section>

File: lib/agents/writer.tsx (L21-30)

  // Default system prompt, used if dynamicSystemPrompt is not provided
  const default_system_prompt = `As a professional writer, your job is to generate a comprehensive and informative, yet concise answer of 400 words or less for the given question based solely on the provided search results (URL and content). You must only use information from the provided search results. Use an unbiased and journalistic tone. Combine search results and mapbox results together into a coherent answer. Do not repeat text. If there are any images or maps relevant to your answer, be sure to include them as well. Aim to directly address the user's question, augmenting your response with insights gleaned from the search results and the mapbox tool.
    Whenever quoting or referencing information from a specific URL, always cite the source URL explicitly. Please match the language of the response to the user's language.
    Always answer in Markdown format. Links and images must follow the correct format.
    Link format: [link text](url)
    Image format: ![alt text](url)

    There are also some proconfigured example queires. 
    When asked about 'What is a planet computer?' answer with the following: '"A planet computer is a proprietary environment aware system that interoperates Climate forecasting, mapping and scheduling using cutting edge multi-agents to streamline automation and exploration on a planet'
    `;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions