You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2025/10/20 11:40:44 UTC
(camel-jbang-examples) branch financial-analyzer created (now aae9c34)
This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a change to branch financial-analyzer
in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git
at aae9c34 Financial Analyzer example
This branch includes the following new commits:
new b474abe We don't need run.sh, just follow the README
new 84a96be We don't need run.sh, just follow the README
new aae9c34 Financial Analyzer example
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(camel-jbang-examples) 02/03: We don't need run.sh, just follow the README
Posted by ac...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a commit to branch financial-analyzer
in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git
commit 84a96be331f0d4f410cdc7324272034b64686911
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Tue Oct 14 18:10:37 2025 +0200
We don't need run.sh, just follow the README
Signed-off-by: Andrea Cosentino <an...@gmail.com>
---
docling-langchain4j-rag/README.adoc | 1 -
1 file changed, 1 deletion(-)
diff --git a/docling-langchain4j-rag/README.adoc b/docling-langchain4j-rag/README.adoc
index 326b535..3da4c9b 100644
--- a/docling-langchain4j-rag/README.adoc
+++ b/docling-langchain4j-rag/README.adoc
@@ -53,7 +53,6 @@ docling-langchain4j-rag/
├── docling-langchain4j-rag.yaml # Main YAML configuration
├── application.properties # Configuration settings
├── compose.yaml # Docker Compose for services
-├── run.sh # Convenience run script
├── sample.md # Sample document (copy to documents/ for testing)
├── README.adoc # This file
├── documents/ # Input directory (files auto-deleted after processing)
(camel-jbang-examples) 01/03: We don't need run.sh, just follow the README
Posted by ac...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a commit to branch financial-analyzer
in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git
commit b474abea9a9dd467eb2b30ad6d103c3cc5815dc7
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Tue Oct 14 18:09:53 2025 +0200
We don't need run.sh, just follow the README
Signed-off-by: Andrea Cosentino <an...@gmail.com>
---
docling-langchain4j-rag/run.sh | 27 ---------------------------
1 file changed, 27 deletions(-)
diff --git a/docling-langchain4j-rag/run.sh b/docling-langchain4j-rag/run.sh
deleted file mode 100755
index 1c43df8..0000000
--- a/docling-langchain4j-rag/run.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Run the Docling + LangChain4j RAG example
-
-jbang -Dcamel.jbang.version=4.16.0-SNAPSHOT camel@apache/camel run \
- --fresh \
- --dep=camel:docling \
- --dep=camel:langchain4j-chat \
- --dep=camel:platform-http \
- --dep=dev.langchain4j:langchain4j:1.6.0 \
- --dep=dev.langchain4j:langchain4j-ollama:1.6.0 \
- --properties=application.properties \
- docling-langchain4j-rag.yaml
(camel-jbang-examples) 03/03: Financial Analyzer example
Posted by ac...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a commit to branch financial-analyzer
in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git
commit aae9c3405ea9d5dab6edd342cbc985091bb8e147
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Mon Oct 20 13:40:05 2025 +0200
Financial Analyzer example
Signed-off-by: Andrea Cosentino <an...@gmail.com>
---
financial-doc-analyzer/README.adoc | 288 +++++++++++
financial-doc-analyzer/application.properties | 44 ++
.../examples/banking-sector-brief.pdf | Bin 0 -> 33526 bytes
.../examples/magnificent-seven-update.pdf | Bin 0 -> 35941 bytes
.../examples/semiconductor-sector-analysis.pdf | Bin 0 -> 30155 bytes
financial-doc-analyzer/examples/tesla-q3-2024.pdf | Bin 0 -> 27956 bytes
financial-doc-analyzer/financial-doc-analyzer.yaml | 540 +++++++++++++++++++++
7 files changed, 872 insertions(+)
diff --git a/financial-doc-analyzer/README.adoc b/financial-doc-analyzer/README.adoc
new file mode 100644
index 0000000..0a0a739
--- /dev/null
+++ b/financial-doc-analyzer/README.adoc
@@ -0,0 +1,288 @@
+= Financial Document Analyzer with Market Intelligence
+
+An automated financial document analysis system that combines Apache Camel, Docling document conversion, LangChain4j AI analysis, and MarketMind REST API for real-time market data.
+
+== Overview
+
+This application automatically processes financial documents (earnings reports, SEC filings, analyst reports) and generates comprehensive investment analysis enriched with real-time stock market data.
+
+**Key Features:**
+
+* 📄 Multi-format document processing (PDF, DOCX, HTML, Markdown)
+* 🔄 AI-powered document conversion via Docling (async mode for large PDFs)
+* 🤖 Financial analysis using local LLMs (Ollama)
+* 📊 Real-time market data from MarketMind REST API (Yahoo Finance)
+* 🎯 Automated stock symbol extraction
+* 📈 Analyst ratings and price targets
+* 💾 Markdown analysis reports
+
+== Prerequisites
+
+* **JBang** - https://www.jbang.dev
+* **Java 11+**
+* **Python 3** - For MarketMind API
+* **MarketMind** - Clone from https://github.com/oscerd/MarketMind
+
+== Quick Start
+
+=== 1. Start MarketMind API
+
+[source,bash]
+----
+cd /path/to/MarketMind
+python3 api.py
+----
+
+Verify: `curl http://localhost:8000/health`
+
+=== 2. Start Infrastructure Services
+
+[source,bash]
+----
+# Start Docling-Serve (document conversion)
+camel infra run docling
+
+# Start Ollama (LLM inference)
+camel infra run ollama
+----
+
+=== 3. Run the Application
+
+[source,bash]
+----
+camel run
+ --dep=camel:docling \
+ --dep=camel:langchain4j-chat \
+ --dep=camel:platform-http \
+ --dep=camel:http \
+ --dep=dev.langchain4j:langchain4j:1.6.0 \
+ --dep=dev.langchain4j:langchain4j-ollama:1.6.0 \
+ financial-doc-analyzer.yaml
+----
+
+=== 4. Process Documents
+
+[source,bash]
+----
+# Copy a sample document to process
+cp examples/tesla-q3-2024.pdf documents/
+
+# Watch the logs for analyst ratings
+# Analysis report will be saved to output/
+----
+
+== How It Works
+
+1. **Document Detection** - File watcher monitors `documents/` directory
+2. **Conversion** - Docling converts PDF/DOCX to Markdown (async mode for large files)
+3. **Symbol Extraction** - Regex patterns identify stock symbols (AAPL, TSLA, etc.)
+4. **Market Data** - Fetches real-time quotes and analyst ratings from MarketMind API
+5. **AI Analysis** - LLM analyzes document with market context
+6. **Report Generation** - Saves comprehensive analysis to `output/` directory
+7. **Cleanup** - Successfully processed files are deleted (failed → `.error/`)
+
+== Analyst Ratings Output
+
+The application logs analyst ratings to console:
+
+[source,text]
+----
+================================================================================
+📊 ANALYST RATINGS SUMMARY
+================================================================================
+
+✅ TSLA
+ TSLA is currently trading at $439.27 (+10.52, +2.45%). Analysts have a
+ consensus rating of 'hold' with a target price of $366.77, indicating
+ -16.5% potential from current levels.
+
+✅ GM
+ GM is currently trading at $58.37 (+1.03, +1.80%). Analysts have a
+ consensus rating of 'buy' with a target price of $64.48, indicating
+ +10.5% potential from current levels.
+
+================================================================================
+----
+
+== Configuration
+
+Edit `application.properties`:
+
+[source,properties]
+----
+# Directories
+documents.directory=documents
+output.directory=output
+
+# Services
+docling.serve.url=http://localhost:5001
+ollama.base.url=http://localhost:11434
+ollama.model.name=granite4:tiny-h
+marketmind.api.url=http://localhost:8000
+
+# HTTP Server
+camel.server.port=8080
+----
+
+=== Available LLM Models
+
+Change `ollama.model.name` to:
+
+* `granite4:tiny-h` - Fast, recommended (default)
+* `llama3.2` - Higher quality, slower
+* `llama3.2:1b` - Fastest, lower quality
+* `mistral` - Alternative high-quality model
+
+Pull new models: `docker exec -it ollama ollama pull <model-name>`
+
+== Example Documents
+
+The `examples/` directory contains sample financial documents:
+
+* `tesla-q3-2024.pdf` - Tesla Q3 earnings (TSLA, GM, F, RIVN, LCID)
+* `semiconductor-sector-analysis.pdf` - Chip stocks (NVDA, AMD, INTC, QCOM, AVGO)
+* `magnificent-seven-update.pdf` - Big tech (AAPL, MSFT, GOOGL, AMZN, NVDA, META, TSLA)
+* `banking-sector-brief.pdf` - Banks (JPM, BAC, WFC, C)
+
+See `examples/DOWNLOAD-REAL-PDFS.md` for instructions on downloading real earnings reports from company investor relations pages.
+
+== API Endpoints
+
+The application exposes REST endpoints:
+
+* `GET /api/health` - Health check
+* `GET /api/market-data/{symbol}` - Market data for specific symbol
+
+[source,bash]
+----
+curl http://localhost:8080/api/health
+curl http://localhost:8080/api/market-data/AAPL
+----
+
+== Troubleshooting
+
+=== No Analyst Ratings Found
+
+Check MarketMind API is running:
+[source,bash]
+----
+curl http://localhost:8000/quote/AAPL
+----
+
+Verify `application.properties` has correct URL:
+[source,properties]
+----
+marketmind.api.url=http://localhost:8000
+----
+
+=== Document Processing Timeout
+
+Large PDFs (>50 pages) may take longer to process. The Docling component uses async mode with a 5-minute timeout (300 seconds) and polls every 2 seconds. Failed files are moved to `documents/.error/`.
+
+**Async Settings:**
+* `useAsyncMode: true` - Asynchronous conversion enabled
+* `asyncTimeout: 300000` - 5 minute max wait time
+* `asyncPollInterval: 2000` - Check status every 2 seconds
+
+**Solutions:**
+* Async mode is already enabled (better for large PDFs)
+* Use smaller documents (earnings press releases instead of full 10-K reports)
+* Increase `asyncTimeout` in the YAML if needed for very large files
+
+=== AI Analysis Timeout
+
+If you see `dev.langchain4j.exception.TimeoutException: request timed out`:
+
+**Current Settings:**
+* LLM timeout: 600 seconds (10 minutes) - configured in `financial-doc-analyzer.yaml:34`
+* This should be sufficient for most documents
+
+**Solutions:**
+* Verify Ollama is running: `docker ps | grep ollama` or `curl http://localhost:11434`
+* Use a faster model: Change `ollama.model.name` to `llama3.2:1b` in `application.properties`
+* Reduce document size: Use press releases instead of full 10-K reports
+* Increase timeout: Edit line 34 in YAML: `.timeout(ofSeconds(900))` for 15 minutes
+
+**Recommended Fast Models:**
+* `llama3.2:1b` - Fastest (1 billion parameters)
+* `granite4:tiny-h` - Very fast (1.5B parameters)
+* `orca-mini` - Good balance
+
+=== Empty Market Intelligence
+
+Check logs for:
+* Symbol extraction: `🔍 Extracting stock symbols`
+* Market data fetching: `Fetching data for symbol: AAPL`
+* API responses: Look for HTTP 200 responses
+
+== Cleanup
+
+[source,bash]
+----
+# Stop Camel application (Ctrl+C)
+
+# Stop infrastructure services
+camel infra stop docling
+camel infra stop ollama
+
+# Stop MarketMind API
+pkill -f "python.*api.py"
+----
+
+== Architecture
+
+[source,text]
+----
+┌─────────────┐
+│ Documents │ (PDF, DOCX, MD)
+└──────┬──────┘
+ │
+ ▼
+┌─────────────┐
+│ Docling │ Convert to Markdown
+└──────┬──────┘
+ │
+ ▼
+┌─────────────┐
+│ Symbol │ Regex extraction (AAPL, TSLA, ...)
+│ Extraction │
+└──────┬──────┘
+ │
+ ▼
+┌─────────────┐
+│ MarketMind │ Fetch quotes & analyst ratings
+│ REST API │
+└──────┬──────┘
+ │
+ ▼
+┌─────────────┐
+│ Ollama LLM │ Financial analysis with context
+└──────┬──────┘
+ │
+ ▼
+┌─────────────┐
+│ Report │ Markdown with analyst ratings
+└─────────────┘
+----
+
+== Project Files
+
+[source,text]
+----
+financial-doc-analyzer/
+├── financial-doc-analyzer.yaml # Camel routes (3 routes)
+├── application.properties # Configuration
+├── README.adoc # This file
+├── documents/ # Drop documents here
+├── documents/.error/ # Failed documents
+├── output/ # Analysis reports
+└── examples/ # Sample documents (MD + PDF)
+----
+
+== Contributing
+
+For issues or questions:
+* Apache Camel Community: https://camel.apache.org/community/support/
+* Contribute: https://camel.apache.org/community/contributing/
+
+Happy analyzing! 🐪
diff --git a/financial-doc-analyzer/application.properties b/financial-doc-analyzer/application.properties
new file mode 100644
index 0000000..2f6ee6a
--- /dev/null
+++ b/financial-doc-analyzer/application.properties
@@ -0,0 +1,44 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Camel Application Configuration
+
+# Application name
+camel.main.name=FinancialDocumentAnalyzer
+
+# Directories
+documents.directory=documents
+output.directory=output
+
+# Docling-Serve Configuration
+docling.serve.url=http://localhost:5001
+
+# Ollama LLM Configuration
+ollama.base.url=http://localhost:11434
+# Recommended models:
+# - llama3.2 (default, good balance)
+# - llama3.2:1b (faster, smaller)
+# - mistral (alternative high-quality)
+# - phi3 (Microsoft's efficient model)
+ollama.model.name=granite4:tiny-h
+
+# MarketMind REST API Configuration
+marketmind.api.url=localhost:8000
+
+# HTTP Server Configuration
+camel.server.port=8080
+
+# Camel Main Configuration
+camel.main.routes-include-pattern=financial-doc-analyzer.yaml
diff --git a/financial-doc-analyzer/examples/banking-sector-brief.pdf b/financial-doc-analyzer/examples/banking-sector-brief.pdf
new file mode 100644
index 0000000..5d01ed4
Binary files /dev/null and b/financial-doc-analyzer/examples/banking-sector-brief.pdf differ
diff --git a/financial-doc-analyzer/examples/magnificent-seven-update.pdf b/financial-doc-analyzer/examples/magnificent-seven-update.pdf
new file mode 100644
index 0000000..a7c9aa8
Binary files /dev/null and b/financial-doc-analyzer/examples/magnificent-seven-update.pdf differ
diff --git a/financial-doc-analyzer/examples/semiconductor-sector-analysis.pdf b/financial-doc-analyzer/examples/semiconductor-sector-analysis.pdf
new file mode 100644
index 0000000..037b8f0
Binary files /dev/null and b/financial-doc-analyzer/examples/semiconductor-sector-analysis.pdf differ
diff --git a/financial-doc-analyzer/examples/tesla-q3-2024.pdf b/financial-doc-analyzer/examples/tesla-q3-2024.pdf
new file mode 100644
index 0000000..9237da4
Binary files /dev/null and b/financial-doc-analyzer/examples/tesla-q3-2024.pdf differ
diff --git a/financial-doc-analyzer/financial-doc-analyzer.yaml b/financial-doc-analyzer/financial-doc-analyzer.yaml
new file mode 100644
index 0000000..55c1b8b
--- /dev/null
+++ b/financial-doc-analyzer/financial-doc-analyzer.yaml
@@ -0,0 +1,540 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Financial Document Analysis with Market Intelligence
+# Combines: Docling (document conversion) + LangChain4j (AI analysis) + Market Data APIs
+# Use Case: Automated analysis of financial documents with real-time market intelligence
+
+# Bean Definitions
+- beans:
+ # Configure Ollama Chat Model for document analysis
+ - name: chatModel
+ type: "#class:dev.langchain4j.model.ollama.OllamaChatModel"
+ scriptLanguage: groovy
+ script: |
+ import dev.langchain4j.model.ollama.OllamaChatModel
+ import static java.time.Duration.ofSeconds
+
+ return OllamaChatModel.builder()
+ .baseUrl("{{ollama.base.url}}")
+ .modelName("{{ollama.model.name}}")
+ .temperature(0.2)
+ .timeout(ofSeconds(600))
+ .build()
+
+# Route Definitions
+
+# Route 1: Financial Document Analysis Pipeline
+- route:
+ id: financial-document-analyzer
+ from:
+ uri: file:{{documents.directory}}
+ parameters:
+ include: ".*\\.(pdf|docx|html|md)"
+ delete: true
+ moveFailed: .error
+ idempotent: true
+ idempotentKey: "${file:name}-${file:modified}"
+ steps:
+ - log: "📄 Processing financial document: ${header.CamelFileName}"
+ - setProperty:
+ name: originalFileName
+ simple: "${header.CamelFileName}"
+
+ # Convert GenericFile to file path
+ - setBody:
+ simple: "${body.file.absolutePath}"
+
+ # Step 1: Convert document to Markdown using Docling (async mode for large PDFs)
+ - log: "🔄 Converting document to Markdown with Docling-Serve (async mode)..."
+ - to:
+ uri: docling:CONVERT_TO_MARKDOWN
+ parameters:
+ useDoclingServe: true
+ doclingServeUrl: "{{docling.serve.url}}"
+ contentInBody: true
+ useAsyncMode: true
+ asyncTimeout: 300000
+ asyncPollInterval: 2000
+ - log: "✅ Document converted to Markdown"
+
+ # Step 2: Store converted markdown
+ - setProperty:
+ name: documentContent
+ simple: "${body}"
+
+ # Step 3: Extract stock symbols using simple regex (faster than AI)
+ - log: "🔍 Extracting stock symbols from document..."
+ - script:
+ groovy: |
+ import java.util.regex.Pattern
+ import java.util.regex.Matcher
+
+ def content = exchange.getProperty("documentContent", String.class)
+
+ // Pattern to match stock symbols (2-5 uppercase letters, possibly followed by stock references)
+ // Looks for patterns like: AAPL, (NASDAQ: GOOGL), Ticker: MSFT, etc.
+ def patterns = [
+ /\b([A-Z]{2,5})\b(?=\s*\(|\s*-|\s*stock|\s*shares|\s*Inc|\s*Corp)/, // AAPL (company
+ /(?i)(?:NYSE|NASDAQ|ticker|symbol):\s*([A-Z]{2,5})\b/, // NYSE: AAPL
+ /\(([A-Z]{2,5})\)/, // (AAPL)
+ /\b([A-Z]{2,5})\b(?=.*(?:stock|shares|trading|market))/ // Context-based
+ ]
+
+ def symbols = new LinkedHashSet()
+
+ patterns.each { patternStr ->
+ def pattern = Pattern.compile(patternStr)
+ def matcher = pattern.matcher(content)
+ while (matcher.find()) {
+ def symbol = matcher.group(1)
+ // Filter out common false positives (financial terms, acronyms, etc.)
+ def falsePositives = ["CEO", "CFO", "USA", "USD", "INC", "LLC", "LTD", "PDF", "HTML", "HTTP",
+ "ASP", "EPS", "YOY", "QOQ", "TTM", "EBIT", "ARPU", "BUY", "SELL", "HOLD",
+ "TV", "FX", "EU", "EV", "AI", "AWS", "API", "APP", "MAC", "IPAD", "GPU", "CPU", "US", "FSD"]
+ if (symbol && !falsePositives.contains(symbol)) {
+ symbols.add(symbol)
+ }
+ }
+ }
+
+ def result = symbols.isEmpty() ? "NONE" : symbols.join(",")
+ exchange.setProperty("stockSymbols", result)
+ log.info("📊 Extracted symbols: {}", result)
+ - log: "Symbols extracted: ${exchangeProperty.stockSymbols}"
+
+ # Step 4: Fetch market data for extracted symbols from MarketMind API
+ - choice:
+ when:
+ - simple: "${exchangeProperty.stockSymbols} != 'NONE' && ${exchangeProperty.stockSymbols} != null"
+ steps:
+ - script:
+ groovy: |
+ import java.net.URL
+ import java.net.HttpURLConnection
+
+ def symbols = exchange.getProperty("stockSymbols", String.class)
+ def symbolList = symbols.split(",").collect { it.trim().toUpperCase() }.findAll { it }
+ def marketUrl = camelContext.resolvePropertyPlaceholders("{{marketmind.api.url}}")
+
+ log.info("Fetching market data for {} symbols from {}", symbolList.size(), marketUrl)
+
+ def jsonParts = []
+
+ symbolList.each { symbol ->
+ try {
+ log.info("Fetching data for symbol: {}", symbol)
+
+ // Fetch quote data
+ def quoteUrl = new URL("http://${marketUrl}/quote/${symbol}")
+ def quoteConn = quoteUrl.openConnection() as HttpURLConnection
+ quoteConn.setRequestMethod("GET")
+ quoteConn.setConnectTimeout(10000)
+ quoteConn.setReadTimeout(10000)
+
+ def quoteData = "{}"
+ if (quoteConn.responseCode == 200) {
+ quoteData = quoteConn.inputStream.text
+ } else {
+ log.warn("Failed to fetch quote for {}: {}", symbol, quoteConn.responseCode)
+ }
+ quoteConn.disconnect()
+
+ // Fetch analyst data
+ def analystUrl = new URL("http://${marketUrl}/analyst/${symbol}")
+ def analystConn = analystUrl.openConnection() as HttpURLConnection
+ analystConn.setRequestMethod("GET")
+ analystConn.setConnectTimeout(10000)
+ analystConn.setReadTimeout(10000)
+
+ def analystData = "{}"
+ if (analystConn.responseCode == 200) {
+ analystData = analystConn.inputStream.text
+ } else {
+ log.warn("Failed to fetch analyst for {}: {}", symbol, analystConn.responseCode)
+ }
+ analystConn.disconnect()
+
+ // Build JSON part for this symbol
+ def part = ' "' + symbol + '": {\n'
+ part += ' "quote": ' + quoteData + ',\n'
+ part += ' "analyst": ' + analystData + '\n'
+ part += ' }'
+
+ jsonParts << part
+ log.info("Successfully fetched data for {}", symbol)
+
+ } catch (Exception e) {
+ log.error("Error fetching data for {}: {}", symbol, e.message)
+ // Add empty data for failed symbols
+ def part = ' "' + symbol + '": {\n'
+ part += ' "quote": {},\n'
+ part += ' "analyst": {}\n'
+ part += ' }'
+ jsonParts << part
+ }
+ }
+
+ def jsonStr = "{\n" + jsonParts.join(",\n") + "\n}"
+ exchange.setProperty("marketIntelligence", jsonStr)
+ log.info("Market intelligence data collected for {} symbols", symbolList.size())
+ otherwise:
+ steps:
+ - setProperty:
+ name: marketIntelligence
+ constant: "No stock symbols found"
+
+ # Step 5: Perform comprehensive AI analysis with market context
+ - log: "🤖 Performing AI analysis with market intelligence (this may take several minutes for large documents)..."
+ - setBody:
+ simple: |
+ You are a financial analyst AI assistant. Analyze the following financial document and provide:
+
+ 1. **Executive Summary** (3-4 sentences)
+ 2. **Key Financial Metrics** (revenue, profit, growth rates, etc.)
+ 3. **Companies and Stock Symbols Mentioned**
+ 4. **Investment Thesis** (bullish/bearish signals based on fundamentals)
+ 5. **Risk Factors** identified
+ 6. **Market Context** (based on provided market data below)
+ 7. **Actionable Insights** for investors
+
+ Stock Symbols Identified: ${exchangeProperty.stockSymbols}
+
+ Document Content:
+ ${exchangeProperty.documentContent}
+
+ Market Intelligence Data (including analyst consensus ratings):
+ ${exchangeProperty.marketIntelligence}
+
+ Provide a comprehensive, professional analysis suitable for investment decision-making.
+ - to:
+ uri: langchain4j-chat:financial-analysis
+ parameters:
+ chatModel: "#chatModel"
+ - setProperty:
+ name: aiAnalysis
+ simple: "${body}"
+ - log: "✅ AI analysis completed"
+
+ # Step 5b: Extract analyst ratings from MarketMind data for report
+ - script:
+ groovy: |
+ import java.util.regex.Pattern
+
+ def marketIntel = exchange.getProperty("marketIntelligence", String.class)
+ def symbols = exchange.getProperty("stockSymbols", String.class)
+
+ def analystRatingsText = ""
+
+ try {
+ def analystSummaries = []
+
+ // Simple string search approach
+ symbols.split(",").each { sym ->
+ def symbol = sym.trim().toUpperCase()
+
+ // Extract values using simple patterns
+ def consensus = null
+ def targetPrice = null
+ def currentPrice = null
+ def change = null
+ def changePct = null
+
+ // Look for recommendation
+ def consensusMatch = (marketIntel =~ /(?s)"${symbol}".*?"recommendation"\s*:\s*"([^"]+)"/)
+ if (consensusMatch.find()) {
+ consensus = consensusMatch.group(1)
+ }
+
+ // Look for target_mean
+ def targetMatch = (marketIntel =~ /(?s)"${symbol}".*?"target_mean"\s*:\s*([0-9.]+)/)
+ if (targetMatch.find()) {
+ targetPrice = Double.parseDouble(targetMatch.group(1))
+ }
+
+ // Look for current_price
+ def priceMatch = (marketIntel =~ /(?s)"${symbol}".*?"current_price"\s*:\s*([0-9.]+)/)
+ if (priceMatch.find()) {
+ currentPrice = Double.parseDouble(priceMatch.group(1))
+ }
+
+ // Look for change
+ def changeMatch = (marketIntel =~ /(?s)"${symbol}".*?"change"\s*:\s*([+-]?[0-9.]+)/)
+ if (changeMatch.find()) {
+ change = Double.parseDouble(changeMatch.group(1))
+ }
+
+ // Look for change_percent
+ def changePctMatch = (marketIntel =~ /(?s)"${symbol}".*?"change_percent"\s*:\s*([+-]?[0-9.]+)/)
+ if (changePctMatch.find()) {
+ changePct = Double.parseDouble(changePctMatch.group(1))
+ }
+
+ // Build narrative if we have minimum data
+ if (consensus && currentPrice != null) {
+ def narrative = "${symbol} is currently trading at \$${String.format('%.2f', currentPrice)}"
+
+ if (change != null && changePct != null) {
+ narrative += " (${String.format('%+.2f', change)}, ${String.format('%+.2f%%', changePct)})"
+ }
+
+ narrative += ". Analysts have a consensus rating of '${consensus}'"
+
+ if (targetPrice != null) {
+ narrative += " with a target price of \$${String.format('%.2f', targetPrice)}"
+ def upside = ((targetPrice - currentPrice) / currentPrice) * 100
+ narrative += ", indicating ${String.format('%+.1f%%', upside)} potential from current levels"
+ }
+
+ narrative += "."
+
+ analystSummaries << [symbol: symbol, consensus: consensus, narrative: narrative]
+ }
+ }
+
+ // Build analyst ratings section for report
+ if (!analystSummaries.isEmpty()) {
+ analystRatingsText = "## Analyst Ratings Summary\n\n"
+ analystSummaries.each { summary ->
+ analystRatingsText += "**${summary.symbol}** (${summary.consensus.toUpperCase()})\n\n"
+ analystRatingsText += "${summary.narrative}\n\n"
+ }
+ }
+
+ // Store for logging later
+ exchange.setProperty("analystSummaries", analystSummaries)
+ exchange.setProperty("analystRatingsText", analystRatingsText)
+
+ } catch (Exception e) {
+ log.error("Error extracting analyst ratings: ${e.message}")
+ }
+
+ # Step 6: Generate comprehensive investment report
+ - script:
+ groovy: |
+ import java.text.SimpleDateFormat
+
+ def fileName = exchange.getProperty("originalFileName")
+ def documentContent = exchange.getProperty("documentContent", String.class)
+ def symbols = exchange.getProperty("stockSymbols", String.class)
+ def marketIntel = exchange.getProperty("marketIntelligence", String.class)
+ def analysis = exchange.getProperty("aiAnalysis", String.class)
+ def analystRatings = exchange.getProperty("analystRatingsText", String.class)
+ def dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+
+ // Build report using string concatenation to avoid backtick issues
+ def report = '# Financial Document Analysis Report\n\n'
+ report += 'Source Document: ' + fileName + '\n\n'
+ report += 'Analysis Date: ' + dateStr + '\n\n'
+ report += 'Stock Symbols Identified: ' + (symbols ?: 'None') + '\n\n\n\n'
+
+ // Add Analyst Ratings Summary (if available)
+ if (analystRatings) {
+ report += '---\n\n'
+ report += analystRatings + '\n'
+ report += '---\n\n'
+ }
+
+ report += '## AI-Powered Financial Analysis\n\n'
+ report += analysis + '\n\n\n\n'
+
+ exchange.message.setBody(report)
+
+ # Step 7: Save the analysis report
+ - setHeader:
+ name: CamelFileName
+ simple: "${exchangeProperty.originalFileName}_analysis_${date:now:yyyyMMdd_HHmmss}.md"
+ - to:
+ uri: file:{{output.directory}}
+ - log: "💾 Analysis report saved: ${header.CamelFileName}"
+
+ # Step 8: Log analyst ratings to console
+ - script:
+ groovy: |
+ def analystSummaries = exchange.getProperty("analystSummaries", List.class)
+
+ if (analystSummaries == null || analystSummaries.isEmpty()) {
+ log.warn("⚠️ No analyst ratings found in MarketMind data")
+ } else {
+ log.info("=" * 80)
+ log.info("📊 ANALYST RATINGS SUMMARY")
+ log.info("=" * 80)
+ log.info("")
+
+ analystSummaries.each { summary ->
+ // Emoji based on consensus
+ def emoji = ""
+ def rating = summary.consensus.toUpperCase()
+ switch(rating) {
+ case ~/.*STRONG.?BUY.*/:
+ emoji = "🚀"
+ break
+ case ~/.*BUY.*/:
+ emoji = "✅"
+ break
+ case ~/.*OUTPERFORM.*/:
+ emoji = "📈"
+ break
+ case ~/.*HOLD.*/:
+ emoji = "⏸️"
+ break
+ case ~/.*UNDERPERFORM.*/:
+ emoji = "📉"
+ break
+ case ~/.*SELL.*/:
+ emoji = "❌"
+ break
+ default:
+ emoji = "ℹ️"
+ }
+
+ log.info("${emoji} ${summary.symbol}")
+ log.info(" ${summary.narrative}")
+ log.info("")
+ }
+ log.info("=" * 80)
+ }
+
+ - log: "✨ Processing complete for: ${exchangeProperty.originalFileName} (source file will be deleted automatically)"
+
+# Route 2: Market Data API Endpoint (mock)
+- route:
+ id: get-market-data
+ from:
+ uri: platform-http:/api/market-data/{symbol}
+ steps:
+ - log: "Fetching market data for: ${header.symbol}"
+ - toD:
+ uri: "http:{{marketmind.api.url}}/quote/${header.symbol}"
+ parameters:
+ httpMethod: GET
+ - setHeader:
+ name: Content-Type
+ constant: "application/json"
+
+# Route 3: Interactive Document Analysis API
+- route:
+ id: analyze-document-api
+ from:
+ uri: platform-http:/api/analyze
+ parameters:
+ httpMethodRestrict: "POST"
+ steps:
+ - log: "📥 Received document for analysis via API"
+ - setProperty:
+ name: uploadedDocument
+ simple: "${body}"
+
+ # Convert document using Docling
+ - log: "Converting uploaded document..."
+ - to:
+ uri: docling:CONVERT_TO_MARKDOWN
+ parameters:
+ useDoclingServe: true
+ doclingServeUrl: "{{docling.serve.url}}"
+ contentInBody: true
+ - setProperty:
+ name: convertedContent
+ simple: "${body}"
+
+ # Extract symbols
+ - setBody:
+ simple: |
+ Extract stock ticker symbols from this document.
+ Return only a comma-separated list of symbols or "NONE".
+
+ ${exchangeProperty.convertedContent}
+ - to:
+ uri: langchain4j-chat:extract
+ parameters:
+ chatModel: "#chatModel"
+ - setProperty:
+ name: symbols
+ simple: "${body}"
+
+ # Generate analysis
+ - setBody:
+ simple: |
+ Provide a brief investment-focused analysis of this document.
+ Symbols found: ${exchangeProperty.symbols}
+
+ Document:
+ ${exchangeProperty.convertedContent}
+ - to:
+ uri: langchain4j-chat:quick-analysis
+ parameters:
+ chatModel: "#chatModel"
+ - setHeader:
+ name: Content-Type
+ constant: "application/json"
+ - script:
+ groovy: |
+ def symbols = exchange.getProperty("symbols")
+ def analysis = exchange.message.getBody(String.class).replaceAll('"', '\\\\"')
+ def timestamp = new Date().toString()
+
+ def json = '{\n'
+ json += ' "symbols": "' + symbols + '",\n'
+ json += ' "analysis": "' + analysis + '",\n'
+ json += ' "timestamp": "' + timestamp + '"\n'
+ json += '}'
+
+ exchange.message.setBody(json)
+
+# Route 5: Health Check
+- route:
+ id: health-check
+ from:
+ uri: platform-http:/api/health
+ steps:
+ - log: "Health check requested"
+ - script:
+ groovy: |
+ def doclingUrl = camelContext.resolvePropertyPlaceholders("{{docling.serve.url}}")
+ def ollamaUrl = camelContext.resolvePropertyPlaceholders("{{ollama.base.url}}")
+ def ollamaModel = camelContext.resolvePropertyPlaceholders("{{ollama.model.name}}")
+ def docsDir = camelContext.resolvePropertyPlaceholders("{{documents.directory}}")
+ def outDir = camelContext.resolvePropertyPlaceholders("{{output.directory}}")
+ def marketUrl = camelContext.resolvePropertyPlaceholders("{{marketmind.api.url}}")
+
+ def json = '{\n'
+ json += ' "status": "healthy",\n'
+ json += ' "timestamp": "' + new Date().toString() + '",\n'
+ json += ' "components": {\n'
+ json += ' "docling": {\n'
+ json += ' "url": "' + doclingUrl + '",\n'
+ json += ' "status": "configured"\n'
+ json += ' },\n'
+ json += ' "ollama": {\n'
+ json += ' "url": "' + ollamaUrl + '",\n'
+ json += ' "model": "' + ollamaModel + '",\n'
+ json += ' "status": "configured"\n'
+ json += ' },\n'
+ json += ' "marketmind_api": {\n'
+ json += ' "url": "' + marketUrl + '",\n'
+ json += ' "type": "MarketMind REST API",\n'
+ json += ' "status": "configured"\n'
+ json += ' }\n'
+ json += ' },\n'
+ json += ' "directories": {\n'
+ json += ' "documents": "' + docsDir + '",\n'
+ json += ' "output": "' + outDir + '"\n'
+ json += ' }\n'
+ json += '}'
+
+ exchange.message.setBody(json)
+ - setHeader:
+ name: Content-Type
+ constant: "application/json"