Compare commits

...

11 Commits

Author SHA1 Message Date
Deep Koluguri 2b3355cddb merge: resolve conflicts between local production fixes and remote updates
Build Institutional Trader / build-and-deploy (push) Successful in 5m41s Details
2026-06-29 17:13:44 -04:00
Deep Koluguri f91534e976 feat: implement stock evaluation filters, fundamentals styling, and AI trading profiles
Build Institutional Trader / build-and-deploy (push) Successful in 2m1s Details
2026-06-22 23:19:38 -04:00
Deep Koluguri c981faf3a6 fix: resolve WebSocket path conflicts by manually routing HTTP upgrade events
Build Institutional Trader / build-and-deploy (push) Successful in 2m1s Details
2026-06-22 22:10:53 -04:00
Deep Koluguri 2ba6425eed fix: trust proxy for express-rate-limit to prevent crash behind APISIX
Build Institutional Trader / build-and-deploy (push) Successful in 2m4s Details
2026-06-22 22:03:32 -04:00
Deep Koluguri a0c54feee5 fix: import getApiUrl in AlertsFeed to fix reference error
Build Institutional Trader / build-and-deploy (push) Successful in 2m6s Details
2026-06-22 21:54:17 -04:00
Deep Koluguri b3373d6d85 fix: explicitly allow wss in CSP connectSrc
Build Institutional Trader / build-and-deploy (push) Successful in 2m13s Details
2026-06-22 19:47:00 -04:00
Deep Koluguri 2dba57191b fix: change hardcoded localhost URLs to relative URLs so frontend works on production domain
Build Institutional Trader / build-and-deploy (push) Successful in 2m10s Details
2026-06-22 19:43:18 -04:00
Deep Koluguri cd83c18555 fix: update CSP for Cloudflare and set CORS_ORIGIN to fix 500 errors
Build Institutional Trader / build-and-deploy (push) Successful in 2m8s Details
2026-06-22 19:40:24 -04:00
Deep Koluguri 76c24aaec1 fix: disable python service health check to prevent pod crashes
Build Institutional Trader / build-and-deploy (push) Has been cancelled Details
2026-06-22 19:38:15 -04:00
Deep Koluguri 4d00f978e2 feat: provision dedicated postgres database via CloudNativePG
Build Institutional Trader / build-and-deploy (push) Successful in 2m19s Details
2026-06-22 19:29:22 -04:00
Deep Koluguri f68b3e5b75 chore: trigger Kaniko build for institutional-trader
Build Institutional Trader / build-and-deploy (push) Successful in 2m11s Details
2026-06-22 17:24:39 -04:00
13 changed files with 199 additions and 128 deletions

View File

@ -3,6 +3,9 @@ import helmet from 'helmet';
import cors from 'cors';
export const setupSecurity = (app) => {
// Trust proxy for express-rate-limit behind Kubernetes Ingress
app.set('trust proxy', 1);
// Helmet for security headers
app.use(helmet({
contentSecurityPolicy: {
@ -12,7 +15,7 @@ export const setupSecurity = (app) => {
scriptSrc: ["'self'", "'unsafe-inline'", 'https://static.cloudflareinsights.com'],
scriptSrcElem: ["'self'", "'unsafe-inline'", 'https://static.cloudflareinsights.com'],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'", 'https:', 'wss:'],
connectSrc: ["'self'", "ws:", "wss:", "http:", "https:"],
workerSrc: ["'self'", 'blob:'],
fontSrc: ["'self'", 'https:', 'data:'],
},

View File

@ -293,8 +293,8 @@ router.get('/recent', async (req, res) => {
});
// WebSocket for real-time alerts
export function setupAlertsWebSocket(server) {
const wss = new WebSocketServer({ server, path: '/ws/alerts' });
export function setupAlertsWebSocket() {
const wss = new WebSocketServer({ noServer: true });
wss.on('connection', (ws) => {
console.log('Client connected to alerts feed');

View File

@ -52,6 +52,8 @@ router.get('/universe', async (req, res) => {
change_pct: quote?.change_pct ?? null,
market_cap: quote?.market_cap ?? null,
volume: quote?.volume ?? null,
day_high: quote?.high ?? null,
day_low: quote?.low ?? null,
high_52w: null, // populated by quoteSummary if needed
low_52w: null,
market_state: quote?.market_state ?? null,

View File

@ -2,8 +2,8 @@ import { WebSocketServer } from 'ws';
import { rawQuery } from '../db.js';
import { optionsFlowQuery } from '../queries/optionsFlowQuery.js';
export function setupWebSocket(server) {
const wss = new WebSocketServer({ server, path: '/ws/flow' });
export function setupWebSocket() {
const wss = new WebSocketServer({ noServer: true });
wss.on('connection', (ws) => {
console.log('Client connected to WebSocket');

View File

@ -127,9 +127,25 @@ const server = app.listen(PORT, async () => {
}
});
// Setup WebSocket
setupWebSocket(server);
setupAlertsWebSocket(server);
// Setup WebSockets manually to handle multiple paths
const flowWss = setupWebSocket();
const alertsWss = setupAlertsWebSocket();
server.on('upgrade', (request, socket, head) => {
const pathname = request.url.split('?')[0];
if (pathname === '/ws/flow') {
flowWss.handleUpgrade(request, socket, head, (ws) => {
flowWss.emit('connection', ws, request);
});
} else if (pathname === '/ws/alerts') {
alertsWss.handleUpgrade(request, socket, head, (ws) => {
alertsWss.emit('connection', ws, request);
});
} else {
socket.destroy();
}
});
// Graceful shutdown
process.on('SIGTERM', () => {

View File

@ -303,20 +303,26 @@ ${yahooData ? `STOCK DATA (Yahoo Finance):
- Volume: ${yahooData.volume?.toLocaleString() || 'N/A'}
` : ''}
Provide a concise analysis (2-3 sentences max) with:
1. Overall assessment (Bullish/Bearish/Neutral)
2. Key risk factors or opportunities
3. Clear action recommendation (Enter/Wait/Avoid)
Provide a detailed analysis categorized into three distinct trader profiles based on the data.
Format as JSON:
{
"assessment": "BULLISH|BEARISH|NEUTRAL",
"summary": "Brief 1-2 sentence summary",
"keyFactors": ["factor1", "factor2", "factor3"],
"recommendation": "ENTER|WAIT|AVOID",
"reasoning": "Brief explanation",
"riskLevel": "LOW|MEDIUM|HIGH",
"timeHorizon": "INTRADAY|SWING|POSITION"
"longTerm": {
"assessment": "BULLISH|BEARISH|NEUTRAL",
"moatAndFundamentals": "Brief analysis of economic moat, EPS growth, and fundamental ratios (P/E, P/B)",
"recommendation": "ENTER|WAIT|AVOID"
},
"swing": {
"assessment": "BULLISH|BEARISH|NEUTRAL",
"technicalSetup": "Analysis of technical setups, consolidation, moving averages, and RSI momentum",
"recommendation": "ENTER|WAIT|AVOID"
},
"dayTrade": {
"assessment": "BULLISH|BEARISH|NEUTRAL",
"volatilityAndLiquidity": "Analysis of immediate catalysts, intraday liquidity/volume, and volatility risks",
"recommendation": "ENTER|WAIT|AVOID"
},
"summary": "Overall 1-2 sentence conclusion covering the most viable approach"
}`;
// Use model from env or default to stable version
@ -346,13 +352,22 @@ Format as JSON:
// Fallback: create structured response from text
console.warn('Failed to parse AI response as JSON, using fallback');
analysis = {
assessment: direction,
summary: responseText.substring(0, 200),
keyFactors: [],
recommendation: score >= 2.0 ? 'ENTER' : 'WAIT',
reasoning: responseText,
riskLevel: score < 2.0 ? 'HIGH' : score < 5.0 ? 'MEDIUM' : 'LOW',
timeHorizon: 'INTRADAY'
longTerm: {
assessment: direction,
moatAndFundamentals: 'Unable to perform deep fundamental analysis. Please review ratios manually.',
recommendation: score >= 5.0 ? 'ENTER' : 'WAIT'
},
swing: {
assessment: direction,
technicalSetup: 'Technical analysis currently unavailable.',
recommendation: score >= 3.0 ? 'ENTER' : 'WAIT'
},
dayTrade: {
assessment: direction,
volatilityAndLiquidity: 'Analysis of intraday volume and catalysts unavailable.',
recommendation: score >= 2.0 ? 'ENTER' : 'WAIT'
},
summary: responseText.substring(0, 200) || 'Analysis unavailable'
};
}
@ -369,13 +384,10 @@ Format as JSON:
success: false,
error: error.message,
analysis: {
assessment: 'NEUTRAL',
summary: 'Analysis unavailable',
keyFactors: [],
recommendation: 'WAIT',
reasoning: 'Unable to generate analysis',
riskLevel: 'MEDIUM',
timeHorizon: 'INTRADAY'
longTerm: { assessment: 'NEUTRAL', moatAndFundamentals: 'Analysis unavailable', recommendation: 'WAIT' },
swing: { assessment: 'NEUTRAL', technicalSetup: 'Analysis unavailable', recommendation: 'WAIT' },
dayTrade: { assessment: 'NEUTRAL', volatilityAndLiquidity: 'Analysis unavailable', recommendation: 'WAIT' },
summary: 'Analysis unavailable'
}
};
}

View File

@ -3,6 +3,7 @@ import { Badge } from '@/components/ui/Badge';
import { Input } from '@/components/ui/input';
import { Bell, TrendingUp, TrendingDown, AlertTriangle, Calendar, Clock } from 'lucide-react';
import { requestNotificationPermission, sendNotification } from '@/utils/notifications';
import { getApiUrl } from '@/config/api';
export default function AlertsFeed() {
const [alerts, setAlerts] = useState([]);

View File

@ -311,9 +311,9 @@ function FundamentalsTab({ f }) {
{
title: '💰 Valuation',
metrics: [
{ label: 'P/E Ratio (TTM)', value: fmt(f.pe_ttm, 1) },
{ label: 'P/E Ratio (Fwd)', value: fmt(f.pe_forward, 1) },
{ label: 'Price / Book', value: fmt(f.price_to_book, 2) },
{ label: 'P/E Ratio (TTM)', value: fmt(f.pe_ttm, 1), h: (f.pe_ttm >= 15 && f.pe_ttm <= 25) ? 'positive' : (f.pe_ttm < 0 || f.pe_ttm > 50) ? 'negative' : undefined },
{ label: 'P/E Ratio (Fwd)', value: fmt(f.pe_forward, 1), h: (f.pe_forward >= 15 && f.pe_forward <= 25) ? 'positive' : (f.pe_forward < 0 || f.pe_forward > 50) ? 'negative' : undefined },
{ label: 'Price / Book', value: fmt(f.price_to_book, 2), h: (f.price_to_book >= 1 && f.price_to_book <= 3) ? 'positive' : (f.price_to_book > 10) ? 'negative' : undefined },
{ label: 'Price / Sales', value: fmt(f.price_to_sales, 2) },
{ label: 'EV / EBITDA', value: fmt(f.ev_ebitda, 1) },
{ label: 'Market Cap', value: fmtLarge(f.market_cap) },
@ -337,7 +337,7 @@ function FundamentalsTab({ f }) {
{ label: 'EBITDA Margin', value: fmtPct(f.ebitda_margins) },
{ label: 'Operating Margin', value: fmtPct(f.operating_margins) },
{ label: 'Profit Margin', value: fmtPct(f.profit_margins) },
{ label: 'Return on Equity', value: fmtPct(f.return_on_equity), h: f.return_on_equity > 0.15 ? 'positive' : f.return_on_equity < 0 ? 'negative' : undefined },
{ label: 'Return on Equity', value: fmtPct(f.return_on_equity), h: (f.return_on_equity >= 0.1 && f.return_on_equity <= 0.2) ? 'positive' : f.return_on_equity < 0 ? 'negative' : undefined },
{ label: 'Return on Assets', value: fmtPct(f.return_on_assets) },
]
},
@ -373,7 +373,7 @@ function FundamentalsTab({ f }) {
{ label: 'Inst. Ownership', value: fmtPct(f.held_pct_institutions) },
{ label: 'Insider Ownership', value: fmtPct(f.held_pct_insiders) },
{ label: 'Dividend Yield', value: fmtPct(f.dividend_yield), h: f.dividend_yield > 0 ? 'positive' : undefined },
{ label: 'Payout Ratio', value: fmtPct(f.payout_ratio) },
{ label: 'Payout Ratio', value: fmtPct(f.payout_ratio), h: f.payout_ratio < 0.6 ? 'positive' : f.payout_ratio > 0.7 ? 'negative' : undefined },
]
},
];

View File

@ -495,49 +495,60 @@ export function TradeAnalysisModal({ row, isOpen, onClose }) {
)}
{aiAnalysis && aiAnalysis.analysis && (
<div className="space-y-3">
<div className="flex items-center gap-2">
<Badge
variant={
aiAnalysis.analysis.assessment === 'BULLISH' ? 'success' :
aiAnalysis.analysis.assessment === 'BEARISH' ? 'destructive' : 'secondary'
}
className="text-xs"
>
{aiAnalysis.analysis.assessment}
</Badge>
<Badge
variant={
aiAnalysis.analysis.recommendation === 'ENTER' ? 'success' :
aiAnalysis.analysis.recommendation === 'WAIT' ? 'warning' : 'destructive'
}
className="text-xs"
>
{aiAnalysis.analysis.recommendation}
</Badge>
<Badge variant="outline" className="text-xs">
{aiAnalysis.analysis.riskLevel} Risk
</Badge>
<div className="space-y-4">
<div className="text-sm text-slate-200 bg-slate-700/30 p-3 rounded border border-slate-600/50">
<strong>Summary:</strong> {aiAnalysis.analysis.summary}
</div>
<p className="text-sm text-slate-200">{aiAnalysis.analysis.summary}</p>
{aiAnalysis.analysis.keyFactors && aiAnalysis.analysis.keyFactors.length > 0 && (
<div>
<div className="text-xs text-slate-500 mb-1">Key Factors:</div>
<ul className="text-xs text-slate-300 space-y-1 list-disc list-inside">
{aiAnalysis.analysis.keyFactors.map((factor, idx) => (
<li key={idx}>{factor}</li>
))}
</ul>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Long Term */}
{aiAnalysis.analysis.longTerm && (
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-700/50">
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Long-Term Investor</div>
<div className="flex gap-2 mb-2">
<Badge variant={aiAnalysis.analysis.longTerm.assessment === 'BULLISH' ? 'success' : aiAnalysis.analysis.longTerm.assessment === 'BEARISH' ? 'destructive' : 'secondary'} className="text-[10px]">
{aiAnalysis.analysis.longTerm.assessment}
</Badge>
<Badge variant={aiAnalysis.analysis.longTerm.recommendation === 'ENTER' ? 'success' : aiAnalysis.analysis.longTerm.recommendation === 'WAIT' ? 'warning' : 'destructive'} className="text-[10px]">
{aiAnalysis.analysis.longTerm.recommendation}
</Badge>
</div>
<p className="text-xs text-slate-300 leading-relaxed">{aiAnalysis.analysis.longTerm.moatAndFundamentals}</p>
</div>
)}
{aiAnalysis.analysis.reasoning && (
<div>
<div className="text-xs text-slate-500 mb-1">Reasoning:</div>
<p className="text-xs text-slate-300">{aiAnalysis.analysis.reasoning}</p>
</div>
)}
{/* Swing Trade */}
{aiAnalysis.analysis.swing && (
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-700/50">
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Swing Trader</div>
<div className="flex gap-2 mb-2">
<Badge variant={aiAnalysis.analysis.swing.assessment === 'BULLISH' ? 'success' : aiAnalysis.analysis.swing.assessment === 'BEARISH' ? 'destructive' : 'secondary'} className="text-[10px]">
{aiAnalysis.analysis.swing.assessment}
</Badge>
<Badge variant={aiAnalysis.analysis.swing.recommendation === 'ENTER' ? 'success' : aiAnalysis.analysis.swing.recommendation === 'WAIT' ? 'warning' : 'destructive'} className="text-[10px]">
{aiAnalysis.analysis.swing.recommendation}
</Badge>
</div>
<p className="text-xs text-slate-300 leading-relaxed">{aiAnalysis.analysis.swing.technicalSetup}</p>
</div>
)}
{/* Day Trade */}
{aiAnalysis.analysis.dayTrade && (
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-700/50">
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Day Trader</div>
<div className="flex gap-2 mb-2">
<Badge variant={aiAnalysis.analysis.dayTrade.assessment === 'BULLISH' ? 'success' : aiAnalysis.analysis.dayTrade.assessment === 'BEARISH' ? 'destructive' : 'secondary'} className="text-[10px]">
{aiAnalysis.analysis.dayTrade.assessment}
</Badge>
<Badge variant={aiAnalysis.analysis.dayTrade.recommendation === 'ENTER' ? 'success' : aiAnalysis.analysis.dayTrade.recommendation === 'WAIT' ? 'warning' : 'destructive'} className="text-[10px]">
{aiAnalysis.analysis.dayTrade.recommendation}
</Badge>
</div>
<p className="text-xs text-slate-300 leading-relaxed">{aiAnalysis.analysis.dayTrade.volatilityAndLiquidity}</p>
</div>
)}
</div>
</div>
)}
</div>

View File

@ -60,56 +60,64 @@ export function AnalysisSection({ row, hasPhase1Data, onPhase1Click }) {
)}
{aiAnalysis && aiAnalysis.analysis && (
<div className="space-y-3">
<div>
<div className="flex items-center gap-2 mb-2">
<Badge
variant={
aiAnalysis.analysis.assessment === 'BULLISH' ? 'success' :
aiAnalysis.analysis.assessment === 'BEARISH' ? 'destructive' : 'secondary'
}
className="text-xs"
>
{aiAnalysis.analysis.assessment}
</Badge>
<Badge
variant={
aiAnalysis.analysis.recommendation === 'ENTER' ? 'success' :
aiAnalysis.analysis.recommendation === 'WAIT' ? 'warning' : 'destructive'
}
className="text-xs"
>
{aiAnalysis.analysis.recommendation}
</Badge>
<Badge variant="outline" className="text-xs">
{aiAnalysis.analysis.riskLevel} Risk
</Badge>
</div>
<p className="text-sm text-slate-200">{aiAnalysis.analysis.summary}</p>
<div className="space-y-4">
<div className="text-sm text-slate-200 bg-slate-700/30 p-2 rounded border border-slate-600/50">
<strong>Summary:</strong> {aiAnalysis.analysis.summary}
</div>
{aiAnalysis.analysis.keyFactors && aiAnalysis.analysis.keyFactors.length > 0 && (
<div>
<div className="text-xs text-slate-500 mb-1">Key Factors:</div>
<ul className="text-xs text-slate-300 space-y-1">
{aiAnalysis.analysis.keyFactors.map((factor, idx) => (
<li key={idx}> {factor}</li>
))}
</ul>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
{/* Long Term */}
{aiAnalysis.analysis.longTerm && (
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-700/50">
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Long-Term Investor</div>
<div className="flex gap-2 mb-2">
<Badge variant={aiAnalysis.analysis.longTerm.assessment === 'BULLISH' ? 'success' : aiAnalysis.analysis.longTerm.assessment === 'BEARISH' ? 'destructive' : 'secondary'} className="text-[10px]">
{aiAnalysis.analysis.longTerm.assessment}
</Badge>
<Badge variant={aiAnalysis.analysis.longTerm.recommendation === 'ENTER' ? 'success' : aiAnalysis.analysis.longTerm.recommendation === 'WAIT' ? 'warning' : 'destructive'} className="text-[10px]">
{aiAnalysis.analysis.longTerm.recommendation}
</Badge>
</div>
<p className="text-xs text-slate-300 leading-relaxed">{aiAnalysis.analysis.longTerm.moatAndFundamentals}</p>
</div>
)}
{aiAnalysis.analysis.reasoning && (
<div>
<div className="text-xs text-slate-500 mb-1">Reasoning:</div>
<p className="text-xs text-slate-300">{aiAnalysis.analysis.reasoning}</p>
</div>
)}
{/* Swing Trade */}
{aiAnalysis.analysis.swing && (
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-700/50">
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Swing Trader</div>
<div className="flex gap-2 mb-2">
<Badge variant={aiAnalysis.analysis.swing.assessment === 'BULLISH' ? 'success' : aiAnalysis.analysis.swing.assessment === 'BEARISH' ? 'destructive' : 'secondary'} className="text-[10px]">
{aiAnalysis.analysis.swing.assessment}
</Badge>
<Badge variant={aiAnalysis.analysis.swing.recommendation === 'ENTER' ? 'success' : aiAnalysis.analysis.swing.recommendation === 'WAIT' ? 'warning' : 'destructive'} className="text-[10px]">
{aiAnalysis.analysis.swing.recommendation}
</Badge>
</div>
<p className="text-xs text-slate-300 leading-relaxed">{aiAnalysis.analysis.swing.technicalSetup}</p>
</div>
)}
<div className="flex items-center gap-4 text-xs text-slate-400 pt-2 border-t border-slate-700">
<span>Time Horizon: {aiAnalysis.analysis.timeHorizon}</span>
{/* Day Trade */}
{aiAnalysis.analysis.dayTrade && (
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-700/50">
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Day Trader</div>
<div className="flex gap-2 mb-2">
<Badge variant={aiAnalysis.analysis.dayTrade.assessment === 'BULLISH' ? 'success' : aiAnalysis.analysis.dayTrade.assessment === 'BEARISH' ? 'destructive' : 'secondary'} className="text-[10px]">
{aiAnalysis.analysis.dayTrade.assessment}
</Badge>
<Badge variant={aiAnalysis.analysis.dayTrade.recommendation === 'ENTER' ? 'success' : aiAnalysis.analysis.dayTrade.recommendation === 'WAIT' ? 'warning' : 'destructive'} className="text-[10px]">
{aiAnalysis.analysis.dayTrade.recommendation}
</Badge>
</div>
<p className="text-xs text-slate-300 leading-relaxed">{aiAnalysis.analysis.dayTrade.volatilityAndLiquidity}</p>
</div>
)}
</div>
<div className="flex items-center gap-4 text-xs text-slate-500 pt-2 border-t border-slate-700">
{aiAnalysis.timestamp && (
<span> {new Date(aiAnalysis.timestamp).toLocaleTimeString()}</span>
<span>Generated: {new Date(aiAnalysis.timestamp).toLocaleTimeString()}</span>
)}
</div>
</div>

View File

@ -5,7 +5,7 @@
export const API_CONFIG = {
baseUrl: import.meta.env.VITE_API_URL || '',
wsUrl: import.meta.env.VITE_WS_URL || 'ws://localhost:5010',
wsUrl: import.meta.env.VITE_WS_URL || (typeof window !== 'undefined' ? (window.location.protocol === 'https:' ? 'wss:' : 'ws:') + '//' + window.location.host : ''),
timeout: parseInt(import.meta.env.VITE_API_TIMEOUT || '30000'),
retryAttempts: parseInt(import.meta.env.VITE_API_RETRY_ATTEMPTS || '3'),
retryDelay: parseInt(import.meta.env.VITE_API_RETRY_DELAY || '1000')

View File

@ -22,6 +22,15 @@ spec:
env:
- name: NODE_ENV
value: "production"
- name: CORS_ORIGIN
value: "https://market.applaude.net"
- name: USE_PYTHON_SERVICE
value: "false"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: market-db-app
key: uri
livenessProbe:
tcpSocket:
port: 3010

9
k8s/db.yaml Normal file
View File

@ -0,0 +1,9 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: market-db
namespace: ai-agents
spec:
instances: 1
storage:
size: 1Gi