feat: integrate AI candidate review and automatic publication

This commit is contained in:
2026-10-09 22:33:38 +08:00
parent 04dbeb2ac6
commit 86f1e7a897
46 changed files with 2460 additions and 2362 deletions
+44 -5
View File
@@ -5,6 +5,7 @@ import (
"errors"
"flag"
"fmt"
"git.sundynix.cn/Blizzard/Veyra/internal/llm"
"git.sundynix.cn/Blizzard/Veyra/internal/logging"
"git.sundynix.cn/Blizzard/Veyra/internal/market"
"git.sundynix.cn/Blizzard/Veyra/internal/research"
@@ -41,8 +42,8 @@ func main() {
func run(ctx context.Context, args []string) (err error) {
flags := flag.NewFlagSet("veyra", flag.ContinueOnError)
envFile := flags.String("env-file", "configs/.env", "environment file (empty disables file loading)")
command := flags.String("command", "serve", "serve, db-create, db-migrate, user-create, or admin-set")
previousLogin := flags.String("previous-login", "", "current administrator login for admin-set (defaults to ADMIN_LOGIN)")
command := flags.String("command", "serve", "serve, db-create, db-migrate, user-create, admin-set, or user-set")
previousLogin := flags.String("previous-login", "", "current identity login for admin-set/user-set (defaults to configured login)")
role := flags.String("role", "ADMIN", "ADMIN or USER for user-create")
if err := flags.Parse(args); err != nil {
return err
@@ -51,7 +52,7 @@ func run(ctx context.Context, args []string) (err error) {
return fmt.Errorf("unexpected positional arguments")
}
switch *command {
case "serve", "db-create", "db-migrate", "user-create", "admin-set":
case "serve", "db-create", "db-migrate", "user-create", "admin-set", "user-set":
default:
return fmt.Errorf("unsupported command")
}
@@ -79,6 +80,8 @@ func run(ctx context.Context, args []string) (err error) {
switch *command {
case "db-create", "db-migrate":
return maintainDatabase(ctx, c, *command, logger)
case "user-set":
return setDevelopmentUser(ctx, c, *previousLogin, logger)
case "admin-set":
return setAdministrator(ctx, c, *previousLogin, logger)
case "user-create":
@@ -101,6 +104,7 @@ func serve(ctx context.Context, c config.Config, logger *zap.Logger) error {
var backtestRepo service.BacktestRepository
var marketRepo service.MarketRepository
var screeningRepo service.ScreeningRepository
var analysisRepo service.CandidateAnalysisRepository
vault, err := secret.NewVault(c.SecretEncryptionKey)
if err != nil {
return err
@@ -126,6 +130,7 @@ func serve(ctx context.Context, c config.Config, logger *zap.Logger) error {
backtestRepo = repository.NewBacktestRepository(pool)
marketRepo = repository.NewMarketRepository(pool)
screeningRepo = repository.NewScreeningRepository(pool)
analysisRepo = repository.NewCandidateAnalysisRepository(pool)
}
redisClient := redis.NewClient(&redis.Options{Addr: c.RedisAddr, Username: c.RedisUsername, Password: c.RedisPassword, DB: c.RedisDB, DialTimeout: time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, MaxRetries: -1, ContextTimeoutEnabled: true})
defer redisClient.Close()
@@ -137,6 +142,7 @@ func serve(ctx context.Context, c config.Config, logger *zap.Logger) error {
development := c.Environment == "local" || c.Environment == "test"
marketService := service.NewMarketService(marketRepo, providerRepo, market.New(), development)
screenings := service.NewScreeningService(screeningRepo, marketService, development)
analyses := service.NewCandidateAnalysisService(analysisRepo, providerRepo, vault, llm.New(), development)
notifications := service.NewNotificationService(notificationRepo, development)
backtests := service.NewBacktestService(backtestRepo, research.New(c.PythonExecutable), development, marketService)
workerContext, workerCancel := context.WithCancel(ctx)
@@ -148,8 +154,17 @@ func serve(ctx context.Context, c config.Config, logger *zap.Logger) error {
go func() { defer close(marketDone); marketService.Run(workerContext, logger) }()
screeningDone := make(chan struct{})
go func() { defer close(screeningDone); screenings.Run(workerContext, logger) }()
defer func() { workerCancel(); <-workerDone; <-researchDone; <-marketDone; <-screeningDone }()
identityOptions := handler.IdentityOptions{Screenings: screenings, Market: marketService, Backtests: backtests, Strategies: service.NewStrategyService(strategyRepo, development), Notifications: notifications, Auth: auth, Fills: service.NewTradeFillService(fillRepo, development), Plans: service.NewTradePlanService(planRepo, development), Candidates: service.NewCandidateService(candidateRepo, development), Providers: service.NewProviderService(providerRepo, vault), Accounts: service.NewAccountService(accountRepo), Limiter: ratelimit.NewRedis(redisClient), DevelopmentLogin: development, SecureCookie: !development || strings.HasPrefix(c.PublicOrigin, "https://"), PublicOrigin: c.PublicOrigin}
analysisDone := make(chan struct{})
go func() { defer close(analysisDone); analyses.Run(workerContext, logger) }()
defer func() {
workerCancel()
<-workerDone
<-researchDone
<-marketDone
<-screeningDone
<-analysisDone
}()
identityOptions := handler.IdentityOptions{Analyses: analyses, Screenings: screenings, Market: marketService, Backtests: backtests, Strategies: service.NewStrategyService(strategyRepo, development), Notifications: notifications, Auth: auth, Fills: service.NewTradeFillService(fillRepo, development), Plans: service.NewTradePlanService(planRepo, development), Candidates: service.NewCandidateService(candidateRepo, development), Providers: service.NewProviderService(providerRepo, vault), Accounts: service.NewAccountService(accountRepo), Limiter: ratelimit.NewRedis(redisClient), DevelopmentLogin: development, SecureCookie: !development || strings.HasPrefix(c.PublicOrigin, "https://"), PublicOrigin: c.PublicOrigin}
server := &http.Server{Addr: c.HTTPAddr, Handler: handler.NewRouter(health, logger, identityOptions), ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 60 * time.Second, MaxHeaderBytes: 1 << 20}
result := make(chan error, 1)
go func() { result <- server.ListenAndServe() }()
@@ -258,3 +273,27 @@ func setAdministrator(ctx context.Context, c config.Config, previousLogin string
logger.Info("administrator credential updated", zap.String("user_id", userID))
return nil
}
func setDevelopmentUser(ctx context.Context, c config.Config, previousLogin string, logger *zap.Logger) error {
if c.Environment != "local" && c.Environment != "test" {
return fmt.Errorf("development user setup is disabled outside local/test")
}
login, password := os.Getenv("DEV_USER_LOGIN"), os.Getenv("DEV_USER_PASSWORD")
if previousLogin == "" {
previousLogin = login
}
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel()
pool, err := database.Open(ctx, c)
if err != nil {
return err
}
defer pool.Close()
updater := service.NewDevelopmentUserCredentialService(repository.NewIdentityRepository(pool), true)
userID, err := updater.Set(ctx, previousLogin, login, password)
if err != nil {
return err
}
logger.Info("development user credential updated", zap.String("user_id", userID))
return nil
}