Skip to content

Commit 98832ba

Browse files
author
BruceWJ
committed
new: rebalance workflow
1 parent 8dbb3ee commit 98832ba

21 files changed

Lines changed: 3873 additions & 819 deletions

.claude/settings.local.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(grep:*)",
5+
"Bash(npx supabase:*)",
6+
"Bash(SUPABASE_ACCESS_TOKEN=\"sbp_72e308dcfc7de7eb83f03ab8fbea9366e3ffe6d6\" npx supabase functions deploy analyze-stock-coordinator --project-ref lnvjsqyvhczgxvygbqer)",
7+
"Bash(SUPABASE_ACCESS_TOKEN=\"sbp_72e308dcfc7de7eb83f03ab8fbea9366e3ffe6d6\" npx supabase db push --project-ref lnvjsqyvhczgxvygbqer)"
8+
],
9+
"deny": []
10+
}
11+
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ dist-ssr
2222
*.njsproj
2323
*.sln
2424
*.sw?
25+
/supabase

deploy-functions.sh

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/bin/bash
2+
3+
# Deployment script for Supabase Edge Functions
4+
# Run this when Docker is working properly
5+
6+
echo "🚀 Deploying Supabase Edge Functions..."
7+
8+
SUPABASE_ACCESS_TOKEN="sbp_72e308dcfc7de7eb83f03ab8fbea9366e3ffe6d6"
9+
PROJECT_REF="lnvjsqyvhczgxvygbqer"
10+
11+
# Deploy analyze-stock (main entry point - critical fix for Alpaca credentials)
12+
echo "📦 Deploying analyze-stock..."
13+
SUPABASE_ACCESS_TOKEN=$SUPABASE_ACCESS_TOKEN npx supabase functions deploy analyze-stock --project-ref $PROJECT_REF
14+
15+
echo "📦 Deploying credentials-proxy..."
16+
SUPABASE_ACCESS_TOKEN=$SUPABASE_ACCESS_TOKEN npx supabase functions deploy credentials-proxy --project-ref $PROJECT_REF
17+
18+
echo "📦 Deploying alpaca-proxy..."
19+
SUPABASE_ACCESS_TOKEN=$SUPABASE_ACCESS_TOKEN npx supabase functions deploy alpaca-proxy --project-ref $PROJECT_REF
20+
21+
echo "📦 Deploying settings-proxy..."
22+
SUPABASE_ACCESS_TOKEN=$SUPABASE_ACCESS_TOKEN npx supabase functions deploy settings-proxy --project-ref $PROJECT_REF
23+
24+
25+
echo "📦 Deploying execute-trade..."
26+
SUPABASE_ACCESS_TOKEN=$SUPABASE_ACCESS_TOKEN npx supabase functions deploy execute-trade --project-ref $PROJECT_REF
27+
28+
29+
# Deploy coordinator
30+
echo "📦 Deploying analyze-stock-coordinator..."
31+
SUPABASE_ACCESS_TOKEN=$SUPABASE_ACCESS_TOKEN npx supabase functions deploy analyze-stock-coordinator --project-ref $PROJECT_REF
32+
33+
# Deploy all agent functions
34+
agents=(
35+
"agent-market-analyst"
36+
"agent-news-analyst"
37+
"agent-social-media-analyst"
38+
"agent-fundamentals-analyst"
39+
"agent-bull-researcher"
40+
"agent-bear-researcher"
41+
"agent-research-manager"
42+
"agent-trader"
43+
"agent-risky-analyst"
44+
"agent-safe-analyst"
45+
"agent-neutral-analyst"
46+
"agent-risk-manager"
47+
"agent-portfolio-manager"
48+
"opportunity-agent"
49+
)
50+
51+
for agent in "${agents[@]}"; do
52+
echo "📦 Deploying $agent..."
53+
SUPABASE_ACCESS_TOKEN=$SUPABASE_ACCESS_TOKEN npx supabase functions deploy $agent --project-ref $PROJECT_REF
54+
done
55+
56+
echo "✅ All functions deployed successfully!"

fix_cancellation_correct.sql

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
-- Fix RLS policy for rebalance_requests to allow cancellation
2+
-- Run this in the Supabase SQL Editor
3+
4+
-- First, drop the existing restrictive update policy
5+
DROP POLICY IF EXISTS "Users can update own pending rebalance requests" ON public.rebalance_requests;
6+
7+
-- Drop any other conflicting update policies
8+
DROP POLICY IF EXISTS "Users can update own rebalance requests" ON public.rebalance_requests;
9+
10+
-- Create a simpler policy that allows users to update their own rebalance requests
11+
-- We'll rely on application logic to enforce what can be updated
12+
CREATE POLICY "Users can update own rebalance requests"
13+
ON public.rebalance_requests
14+
FOR UPDATE
15+
USING (auth.uid() = user_id)
16+
WITH CHECK (auth.uid() = user_id);
17+
18+
-- The above policy allows users to update their own rebalance requests
19+
-- The application will control what fields can be updated based on the current status
20+
21+
-- Verify the policy was created
22+
SELECT
23+
polname as policy_name,
24+
polcmd as command,
25+
pol.polpermissive as is_permissive,
26+
CASE pol.polcmd
27+
WHEN 'r' THEN 'SELECT'
28+
WHEN 'a' THEN 'INSERT'
29+
WHEN 'w' THEN 'UPDATE'
30+
WHEN 'd' THEN 'DELETE'
31+
END as operation
32+
FROM pg_policy pol
33+
JOIN pg_class pc ON pol.polrelid = pc.oid
34+
WHERE pc.relname = 'rebalance_requests'
35+
AND pc.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
36+
ORDER BY pol.polname;
37+
38+
-- Also check current rebalance requests to see their status
39+
SELECT id, status, is_canceled, created_at, updated_at
40+
FROM public.rebalance_requests
41+
WHERE user_id = auth.uid()
42+
ORDER BY created_at DESC
43+
LIMIT 5;

fix_cancellation_with_trigger.sql

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
-- Alternative approach using a function to handle cancellation
2+
-- This bypasses RLS issues by using a SECURITY DEFINER function
3+
4+
-- Create a function to cancel rebalance requests
5+
CREATE OR REPLACE FUNCTION public.cancel_rebalance_request(p_request_id UUID)
6+
RETURNS BOOLEAN
7+
LANGUAGE plpgsql
8+
SECURITY DEFINER
9+
SET search_path = public
10+
AS $$
11+
DECLARE
12+
v_user_id UUID;
13+
v_current_status TEXT;
14+
BEGIN
15+
-- Get the user_id and current status of the request
16+
SELECT user_id, status INTO v_user_id, v_current_status
17+
FROM public.rebalance_requests
18+
WHERE id = p_request_id;
19+
20+
-- Check if request exists
21+
IF NOT FOUND THEN
22+
RAISE EXCEPTION 'Rebalance request not found';
23+
END IF;
24+
25+
-- Check if the user owns this request
26+
IF v_user_id != auth.uid() THEN
27+
RAISE EXCEPTION 'Unauthorized: You can only cancel your own rebalance requests';
28+
END IF;
29+
30+
-- Check if already in terminal state
31+
IF v_current_status IN ('completed', 'cancelled') THEN
32+
RAISE EXCEPTION 'Cannot cancel: Rebalance is already %', v_current_status;
33+
END IF;
34+
35+
-- Update the request to cancelled
36+
UPDATE public.rebalance_requests
37+
SET
38+
status = 'cancelled',
39+
is_canceled = true,
40+
updated_at = NOW()
41+
WHERE id = p_request_id;
42+
43+
-- Also cancel any related analyses
44+
UPDATE public.analysis_history
45+
SET
46+
is_canceled = true,
47+
analysis_status = -1
48+
WHERE
49+
rebalance_request_id = p_request_id
50+
AND analysis_status = 0; -- Only cancel running analyses
51+
52+
RETURN TRUE;
53+
END;
54+
$$;
55+
56+
-- Grant execute permission to authenticated users
57+
GRANT EXECUTE ON FUNCTION public.cancel_rebalance_request(UUID) TO authenticated;
58+
59+
-- Add comment for documentation
60+
COMMENT ON FUNCTION public.cancel_rebalance_request IS 'Safely cancels a rebalance request and its associated analyses. Only the owner can cancel their own requests.';
61+
62+
-- Test the function (replace with actual UUID)
63+
-- SELECT public.cancel_rebalance_request('your-rebalance-id-here');

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Index from "./pages/Index";
88
import Settings from "./pages/Settings";
99
import Profile from "./pages/Profile";
1010
import AnalysisRecords from "./pages/AnalysisRecords";
11+
import RebalanceRecords from "./pages/RebalanceRecords";
1112
import AlphaVantageTest from "./pages/AlphaVantageTest";
1213
import NotFound from "./pages/NotFound";
1314
import { supabase, supabaseHelpers } from "@/lib/supabase";
@@ -22,6 +23,7 @@ const AppRoutes = () => {
2223
<Route path="/settings" element={<Settings />} />
2324
<Route path="/profile" element={<Profile />} />
2425
<Route path="/analysis-records" element={<AnalysisRecords />} />
26+
<Route path="/rebalance-records" element={<RebalanceRecords />} />
2527
<Route path="/alpha-vantage-test" element={<AlphaVantageTest />} />
2628
<Route path="*" element={<NotFound />} />
2729
</Routes>

src/components/AnalysisDetailModal.tsx

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,9 @@ function WorkflowStepsLayout({ analysisData, onApproveOrder, onRejectOrder, isOr
375375
onRejectOrder?: () => void;
376376
isOrderExecuted?: boolean;
377377
}) {
378+
// Check if this analysis is part of a rebalance request
379+
const isRebalanceAnalysis = !!analysisData.rebalance_request_id;
380+
378381
const workflowSteps = [
379382
{
380383
id: 'analysis',
@@ -419,17 +422,22 @@ function WorkflowStepsLayout({ analysisData, onApproveOrder, onRejectOrder, isOr
419422
{ name: 'Neutral Analyst', key: 'neutralAnalyst', icon: Activity },
420423
{ name: 'Risk Manager', key: 'riskManager', icon: Shield }
421424
]
422-
},
423-
{
425+
}
426+
];
427+
428+
// Only add Portfolio Management step if this is NOT a rebalance analysis
429+
// For rebalance analyses, the portfolio manager runs once for all stocks together
430+
if (!isRebalanceAnalysis) {
431+
workflowSteps.push({
424432
id: 'portfolio',
425433
title: 'Portfolio Management',
426434
description: 'Position sizing and trade order generation',
427435
icon: Briefcase,
428436
agents: [
429437
{ name: 'Portfolio Manager', key: 'portfolioManager', icon: Briefcase }
430438
]
431-
}
432-
];
439+
});
440+
}
433441

434442
const getAgentStatus = (agentKey: string, stepId?: string) => {
435443
// Special handling for research phase agents
@@ -762,7 +770,9 @@ function WorkflowStepsLayout({ analysisData, onApproveOrder, onRejectOrder, isOr
762770
Overall Progress
763771
</h3>
764772
<p className="text-sm text-muted-foreground mt-1">
765-
Analysis workflow execution status
773+
{isRebalanceAnalysis
774+
? "Stock analysis for rebalance workflow (Portfolio Manager runs after all stocks complete)"
775+
: "Analysis workflow execution status"}
766776
</p>
767777
</div>
768778
<div>
@@ -1050,7 +1060,15 @@ export default function AnalysisDetailModal({ ticker, analysisId, isOpen, onClos
10501060
if (analysisToLoad.analysis_status === -1) {
10511061
status = analysisToLoad.is_canceled ? 'canceled' : 'error';
10521062
} else if (analysisToLoad.analysis_status === 0) {
1053-
status = 'running';
1063+
// Check if this is a rebalance analysis and Risk Manager has completed
1064+
if (analysisToLoad.rebalance_request_id &&
1065+
analysisToLoad.agent_insights?.riskManager) {
1066+
// For rebalance workflows, consider complete when Risk Manager finishes
1067+
console.log('Rebalance analysis with Risk Manager complete - marking as completed');
1068+
status = 'completed';
1069+
} else {
1070+
status = 'running';
1071+
}
10541072
} else if (analysisToLoad.analysis_status === 1) {
10551073
status = 'completed';
10561074
}
@@ -1123,15 +1141,13 @@ export default function AnalysisDetailModal({ ticker, analysisId, isOpen, onClos
11231141

11241142
// Start polling if running
11251143
if (status === 'running' && !analysisDate) {
1126-
console.log('Starting polling...');
11271144
if (intervalRef.current) {
11281145
clearInterval(intervalRef.current);
11291146
}
11301147
intervalRef.current = setInterval(() => {
11311148
loadAnalysis();
1132-
}, 2000);
1149+
}, 3000); // Poll every 3 seconds
11331150
} else if (status !== 'running' && intervalRef.current) {
1134-
console.log('Stopping polling, analysis completed');
11351151
clearInterval(intervalRef.current);
11361152
intervalRef.current = undefined;
11371153
}
@@ -1371,7 +1387,17 @@ export default function AnalysisDetailModal({ ticker, analysisId, isOpen, onClos
13711387
<ScrollArea className="h-[calc(90vh-280px)]">
13721388
<div className="px-6 pb-6">
13731389
<TabsContent value="actions" className="mt-6 space-y-4">
1374-
{/* Trade Order Section */}
1390+
{/* For rebalance analyses, show a message that actions are handled at rebalance level */}
1391+
{analysisData.rebalance_request_id ? (
1392+
<div className="rounded-lg border bg-muted/20 p-6 text-center">
1393+
<Shield className="w-12 h-12 mx-auto mb-4 text-muted-foreground opacity-50" />
1394+
<h3 className="text-lg font-semibold mb-2">Part of Rebalance Workflow</h3>
1395+
<p className="text-sm text-muted-foreground">
1396+
This analysis is part of a portfolio rebalance. Trade orders will be generated
1397+
after all stock analyses complete and are managed in the Rebalance view.
1398+
</p>
1399+
</div>
1400+
) : (
13751401
<div className="space-y-4">
13761402
{/* Summary Cards */}
13771403
<div className="grid grid-cols-3 gap-4">
@@ -1455,6 +1481,7 @@ export default function AnalysisDetailModal({ ticker, analysisId, isOpen, onClos
14551481
</Card>
14561482
)}
14571483
</div>
1484+
)}
14581485
</TabsContent>
14591486

14601487
<TabsContent value="workflow" className="mt-6">
@@ -1529,6 +1556,11 @@ export default function AnalysisDetailModal({ ticker, analysisId, isOpen, onClos
15291556
// Get entries from agent_insights
15301557
let entries = Object.entries(analysisData.agent_insights);
15311558

1559+
// Filter out Portfolio Manager for rebalance analyses
1560+
if (analysisData.rebalance_request_id) {
1561+
entries = entries.filter(([agent]) => agent !== 'portfolioManager');
1562+
}
1563+
15321564
// Add missing agents that have messages but no insights
15331565
const missingAgents = ['fundamentalsAnalyst', 'safeAnalyst'];
15341566
missingAgents.forEach(agentKey => {

src/components/Header.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
LogOut,
99
User as UserIcon,
1010
FileText,
11-
Home
11+
Home,
12+
RefreshCw
1213
} from "lucide-react";
1314
import { useAuth, hasRequiredApiKeys } from "@/lib/auth-supabase";
1415
import LoginModal from "./LoginModal";
@@ -57,6 +58,12 @@ export default function Header() {
5758
Analysis Records
5859
</Button>
5960
</Link>
61+
<Link to="/rebalance-records">
62+
<Button variant="ghost" size="sm">
63+
<RefreshCw className="h-4 w-4 mr-2" />
64+
Rebalance Records
65+
</Button>
66+
</Link>
6067
</>
6168
)}
6269
</div>

0 commit comments

Comments
 (0)