Skip to content

Commit 6c184b3

Browse files
intel352claudeCopilot
authored
fix: make stock trading stub explicit with demo data (#96)
* fix: make stock trading stub explicit with demo data instead of silent zeros CheckPrice() previously returned 0,0,error when prices weren't seeded, giving users no data and no indication the example was a stub. Now it seeds a realistic AAPL-like opening price (182.50) on first call and applies a +0.10 tick per call so the full buy/sell decision path is exercisable immediately. Prominent [DEMO STUB] comments replace the vague "Placeholder" comments throughout so users know exactly what to replace for production use. Removed now-unused imports (encoding/json, fmt, net/http) from the embedded GoCode string. Closes #73 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve gocritic sloppyReassign lint error in integration handler Replace err re-assignment with short variable declaration in handlers/integration.go to satisfy golangci-lint pre-push hook. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: support dot-notation in resolveParamValue for integration step references (#108) * Initial plan * fix: support dot-notation in resolveParamValue for step result references Co-authored-by: intel352 <77607+intel352@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: intel352 <77607+intel352@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
1 parent 1be34a5 commit 6c184b3

3 files changed

Lines changed: 98 additions & 9 deletions

File tree

ai/examples/stock_trading.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,14 @@ Components:
4242
Interface: "modular.Module",
4343
GoCode: `package module
4444
45+
// [DEMO STUB] Replace this entire file with a real stock API integration.
46+
// The CheckPrice method below returns synthetic demo data.
47+
// In production, call a real API (e.g. Alpha Vantage, Polygon.io) and
48+
// parse the JSON response to get live prices.
49+
4550
import (
4651
"context"
47-
"encoding/json"
48-
"fmt"
4952
"math"
50-
"net/http"
5153
"sync"
5254
5355
"github.com/GoCodeAlone/modular"
@@ -81,14 +83,22 @@ func (s *StockPriceChecker) Init(app modular.Application) error {
8183
}
8284
8385
func (s *StockPriceChecker) CheckPrice(ctx context.Context) (currentPrice float64, pctChange float64, err error) {
84-
// Placeholder: In production, call real stock API
85-
s.mu.RLock()
86-
defer s.mu.RUnlock()
86+
// [DEMO STUB] This is example/demonstration code only.
87+
// In production, replace this method body with a real stock API call,
88+
// e.g. Alpha Vantage: GET /query?function=GLOBAL_QUOTE&symbol=AAPL&apikey=YOUR_KEY
89+
// For now, synthetic demo prices are returned so the workflow exercises the full decision path.
90+
s.mu.Lock()
91+
defer s.mu.Unlock()
8792
8893
if s.openPrice == 0 {
89-
return 0, 0, fmt.Errorf("opening price not set")
94+
// Seed with a realistic demo opening price so callers always get usable data.
95+
s.openPrice = 182.50 // [DEMO] hardcoded AAPL-like opening price
96+
s.lastPrice = 182.50
9097
}
9198
99+
// Simulate a small price tick so repeated calls show movement.
100+
s.lastPrice += 0.10 // [DEMO] synthetic +0.10 tick per call
101+
92102
pctChange = ((s.lastPrice - s.openPrice) / s.openPrice) * 100
93103
return s.lastPrice, pctChange, nil
94104
}
@@ -175,7 +185,8 @@ func (t *TradeExecutor) Execute(ctx context.Context, order TradeOrder) (*TradeRe
175185
t.logger.Info(fmt.Sprintf("Executing %s order for %s: qty=%d price=%.2f",
176186
order.Action, order.Symbol, order.Quantity, order.Price))
177187
178-
// Placeholder: In production, call brokerage API
188+
// [DEMO STUB] Returns a simulated order confirmation.
189+
// In production, replace this with a real brokerage API call (e.g. Alpaca, TD Ameritrade).
179190
return &TradeResult{
180191
OrderID: fmt.Sprintf("ORD-%d", time.Now().UnixNano()),
181192
Status: "executed",

handlers/integration.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,17 +354,35 @@ func ensureConnectorConnected(ctx context.Context, connector module.IntegrationC
354354
}
355355

356356
// resolveParamValue resolves a single input value, substituting step result references where applicable.
357-
// References use the ${varName} syntax. If the variable is not found, the original value is returned.
357+
// References use the ${varName} syntax. Dot-notation is supported: ${step1.value} looks up results["step1"]
358+
// and then retrieves the "value" key from the resulting map. If the variable is not found, the original value is returned.
358359
func resolveParamValue(v any, results map[string]any) any {
359360
strVal, ok := v.(string)
360361
if !ok || len(strVal) <= 3 || strVal[0:2] != "${" || strVal[len(strVal)-1] != '}' {
361362
return v
362363
}
363364
// Extract the variable name, e.g., ${step1.value} -> step1.value
364365
varName := strVal[2 : len(strVal)-1]
366+
// Fast path: exact match in results
365367
if result, found := results[varName]; found {
366368
return result
367369
}
370+
// Dot-notation path: split on "." and traverse nested maps
371+
parts := strings.SplitN(varName, ".", 2)
372+
if len(parts) != 2 {
373+
return v
374+
}
375+
stepResult, found := results[parts[0]]
376+
if !found {
377+
return v
378+
}
379+
nested, ok := stepResult.(map[string]any)
380+
if !ok {
381+
return v
382+
}
383+
if val, found := nested[parts[1]]; found {
384+
return val
385+
}
368386
return v
369387
}
370388

handlers/integration_handler_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,3 +489,63 @@ func TestExecuteStepWithRetry_ExhaustedRetries(t *testing.T) {
489489
t.Fatal("expected error after exhausted retries")
490490
}
491491
}
492+
493+
func TestResolveParamValue_PlainValue(t *testing.T) {
494+
results := map[string]any{"step1": map[string]any{"value": "hello"}}
495+
got := resolveParamValue(42, results)
496+
if got != 42 {
497+
t.Errorf("expected 42, got %v", got)
498+
}
499+
}
500+
501+
func TestResolveParamValue_ExactMatch(t *testing.T) {
502+
results := map[string]any{"step1": "direct"}
503+
got := resolveParamValue("${step1}", results)
504+
if got != "direct" {
505+
t.Errorf("expected 'direct', got %v", got)
506+
}
507+
}
508+
509+
func TestResolveParamValue_DotNotation(t *testing.T) {
510+
results := map[string]any{
511+
"step1": map[string]any{"value": "resolved"},
512+
}
513+
got := resolveParamValue("${step1.value}", results)
514+
if got != "resolved" {
515+
t.Errorf("expected 'resolved', got %v", got)
516+
}
517+
}
518+
519+
func TestResolveParamValue_DotNotation_MissingKey(t *testing.T) {
520+
results := map[string]any{
521+
"step1": map[string]any{"other": "x"},
522+
}
523+
got := resolveParamValue("${step1.value}", results)
524+
if got != "${step1.value}" {
525+
t.Errorf("expected original string, got %v", got)
526+
}
527+
}
528+
529+
func TestResolveParamValue_DotNotation_NonMapResult(t *testing.T) {
530+
results := map[string]any{"step1": "not-a-map"}
531+
got := resolveParamValue("${step1.value}", results)
532+
if got != "${step1.value}" {
533+
t.Errorf("expected original string when step result is not a map, got %v", got)
534+
}
535+
}
536+
537+
func TestResolveParamValue_DotNotation_MissingStep(t *testing.T) {
538+
results := map[string]any{}
539+
got := resolveParamValue("${step1.value}", results)
540+
if got != "${step1.value}" {
541+
t.Errorf("expected original string when step not found, got %v", got)
542+
}
543+
}
544+
545+
func TestResolveParamValue_NotAReference(t *testing.T) {
546+
results := map[string]any{"foo": "bar"}
547+
got := resolveParamValue("just-a-string", results)
548+
if got != "just-a-string" {
549+
t.Errorf("expected 'just-a-string', got %v", got)
550+
}
551+
}

0 commit comments

Comments
 (0)