final
This commit is contained in:
694
internal/usecase/person_agent_processor.go
Normal file
694
internal/usecase/person_agent_processor.go
Normal file
@@ -0,0 +1,694 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/paramah/ai_devs4/s01e02/internal/domain"
|
||||
)
|
||||
|
||||
// PersonAgentProcessorUseCase processes each person with optimized LLM calls
|
||||
type PersonAgentProcessorUseCase struct {
|
||||
personRepo domain.PersonRepository
|
||||
apiClient domain.APIClient
|
||||
llmProvider domain.LLMProvider
|
||||
apiKey string
|
||||
outputDir string
|
||||
}
|
||||
|
||||
// NewPersonAgentProcessorUseCase creates a new use case instance
|
||||
func NewPersonAgentProcessorUseCase(
|
||||
personRepo domain.PersonRepository,
|
||||
apiClient domain.APIClient,
|
||||
llmProvider domain.LLMProvider,
|
||||
apiKey string,
|
||||
outputDir string,
|
||||
) *PersonAgentProcessorUseCase {
|
||||
return &PersonAgentProcessorUseCase{
|
||||
personRepo: personRepo,
|
||||
apiClient: apiClient,
|
||||
llmProvider: llmProvider,
|
||||
apiKey: apiKey,
|
||||
outputDir: outputDir,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute processes all persons
|
||||
func (uc *PersonAgentProcessorUseCase) Execute(ctx context.Context, inputFile string) error {
|
||||
log.Printf("\n╔════════════════════════════════════════════════════════════════")
|
||||
log.Printf("║ PHASE: INITIALIZATION")
|
||||
log.Printf("╚════════════════════════════════════════════════════════════════")
|
||||
|
||||
// Create output directories
|
||||
log.Printf("\n→ Creating output directories...")
|
||||
dirs := []string{
|
||||
filepath.Join(uc.outputDir, "locations"),
|
||||
filepath.Join(uc.outputDir, "accesslevel"),
|
||||
filepath.Join(uc.outputDir, "person_reports"),
|
||||
}
|
||||
for _, dir := range dirs {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("creating directory %s: %w", dir, err)
|
||||
}
|
||||
log.Printf(" ✓ Created: %s", dir)
|
||||
}
|
||||
|
||||
log.Printf("\n╔════════════════════════════════════════════════════════════════")
|
||||
log.Printf("║ PHASE: LOADING DATA")
|
||||
log.Printf("╚════════════════════════════════════════════════════════════════")
|
||||
|
||||
// Load persons
|
||||
log.Printf("\n→ Loading persons from: %s", inputFile)
|
||||
persons, err := uc.personRepo.LoadPersons(ctx, inputFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading persons: %w", err)
|
||||
}
|
||||
log.Printf("✓ Loaded %d persons", len(persons))
|
||||
for i, p := range persons {
|
||||
log.Printf(" %d. %s %s (born: %d, city: %s)", i+1, p.Name, p.Surname, p.Born, p.City)
|
||||
}
|
||||
|
||||
// Load power plants data
|
||||
log.Printf("\n→ Loading power plants data from API...")
|
||||
plantsData, err := uc.loadPowerPlantsData(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading power plants: %w", err)
|
||||
}
|
||||
|
||||
// Save power plants data
|
||||
plantsJSON, _ := json.MarshalIndent(plantsData, "", " ")
|
||||
plantsPath := filepath.Join(uc.outputDir, "findhim_locations.json")
|
||||
os.WriteFile(plantsPath, plantsJSON, 0644)
|
||||
log.Printf("✓ Loaded %d power plants", len(plantsData.PowerPlants))
|
||||
|
||||
// Get list of plant cities for agent
|
||||
var plantCities []string
|
||||
activePlants := 0
|
||||
for city, info := range plantsData.PowerPlants {
|
||||
plantCities = append(plantCities, city)
|
||||
if info.IsActive {
|
||||
activePlants++
|
||||
log.Printf(" • %s (%s) - %s [ACTIVE]", city, info.Code, info.Power)
|
||||
} else {
|
||||
log.Printf(" • %s (%s) - %s [INACTIVE]", city, info.Code, info.Power)
|
||||
}
|
||||
}
|
||||
log.Printf("✓ Active plants: %d/%d", activePlants, len(plantsData.PowerPlants))
|
||||
log.Printf("✓ Power plants data saved to: %s", plantsPath)
|
||||
|
||||
// Fetch accurate geolocation for all power plants using geocoding API
|
||||
log.Printf("\n→ Fetching accurate geolocations from geocoding API...")
|
||||
plantCoordinates, err := uc.fetchPlantGeolocations(ctx, plantsData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching plant geolocations: %w", err)
|
||||
}
|
||||
log.Printf("✓ Fetched accurate coordinates for %d plants", len(plantCoordinates))
|
||||
|
||||
// Process each person
|
||||
var personReports []domain.PersonReport
|
||||
|
||||
log.Printf("\n╔════════════════════════════════════════════════════════════════")
|
||||
log.Printf("║ PHASE: PROCESSING PERSONS (OPTIMIZED)")
|
||||
log.Printf("║ Strategy: 2 LLM calls per person + parallel distance calculation")
|
||||
log.Printf("╚════════════════════════════════════════════════════════════════")
|
||||
|
||||
for i, person := range persons {
|
||||
log.Printf("\n")
|
||||
log.Printf("┌────────────────────────────────────────────────────────────────")
|
||||
log.Printf("│ [%d/%d] PERSON: %s %s", i+1, len(persons), person.Name, person.Surname)
|
||||
log.Printf("│ Born: %d | City: %s", person.Born, person.City)
|
||||
log.Printf("└────────────────────────────────────────────────────────────────")
|
||||
|
||||
report, err := uc.processPerson(ctx, person, plantsData, plantCoordinates)
|
||||
if err != nil {
|
||||
log.Printf("\n✗✗✗ ERROR processing %s %s: %v", person.Name, person.Surname, err)
|
||||
continue
|
||||
}
|
||||
|
||||
personReports = append(personReports, *report)
|
||||
|
||||
// Save individual report
|
||||
reportJSON, _ := json.MarshalIndent(report, "", " ")
|
||||
reportPath := filepath.Join(uc.outputDir, "person_reports", fmt.Sprintf("%s_%s.json", person.Name, person.Surname))
|
||||
os.WriteFile(reportPath, reportJSON, 0644)
|
||||
log.Printf("\n✓ Individual report saved: %s", reportPath)
|
||||
}
|
||||
|
||||
// Find person with minimum distance to their nearest plant
|
||||
log.Printf("\n")
|
||||
log.Printf("╔════════════════════════════════════════════════════════════════")
|
||||
log.Printf("║ PHASE: FINDING MINIMUM DISTANCE")
|
||||
log.Printf("╚════════════════════════════════════════════════════════════════")
|
||||
log.Printf("\nAnalyzing all %d person reports to find minimum distance...", len(personReports))
|
||||
|
||||
var closestReport *domain.PersonReport
|
||||
minDistance := 1e10
|
||||
|
||||
for i := range personReports {
|
||||
log.Printf(" • %s %s → %s: %.2f km (access level: %d)",
|
||||
personReports[i].Name,
|
||||
personReports[i].Surname,
|
||||
personReports[i].NearestPlant,
|
||||
personReports[i].DistanceKm,
|
||||
personReports[i].AccessLevel)
|
||||
|
||||
if personReports[i].DistanceKm < minDistance {
|
||||
minDistance = personReports[i].DistanceKm
|
||||
closestReport = &personReports[i]
|
||||
}
|
||||
}
|
||||
|
||||
if closestReport != nil {
|
||||
log.Printf("\n╔════════════════════════════════════════════════════════════════")
|
||||
log.Printf("║ WINNER: CLOSEST PERSON-PLANT PAIR")
|
||||
log.Printf("╚════════════════════════════════════════════════════════════════")
|
||||
log.Printf(" Person: %s %s", closestReport.Name, closestReport.Surname)
|
||||
log.Printf(" Power Plant: %s (%s)", closestReport.NearestPlant, closestReport.PlantCode)
|
||||
log.Printf(" Distance: %.2f km", closestReport.DistanceKm)
|
||||
log.Printf(" Access Level: %d", closestReport.AccessLevel)
|
||||
log.Printf(" Coordinates: %.4f°N, %.4f°E", closestReport.PrimaryLatitude, closestReport.PrimaryLongitude)
|
||||
|
||||
// Save final answer
|
||||
finalAnswer := domain.FinalAnswer{
|
||||
APIKey: uc.apiKey,
|
||||
Task: "findhim",
|
||||
Answer: domain.AnswerDetail{
|
||||
Name: closestReport.Name,
|
||||
Surname: closestReport.Surname,
|
||||
AccessLevel: closestReport.AccessLevel,
|
||||
PowerPlant: closestReport.PlantCode,
|
||||
},
|
||||
}
|
||||
|
||||
answerJSON, _ := json.MarshalIndent(finalAnswer, "", " ")
|
||||
answerPath := filepath.Join(uc.outputDir, "final_answer.json")
|
||||
os.WriteFile(answerPath, answerJSON, 0644)
|
||||
log.Printf("\n✓ Final answer saved to: %s", answerPath)
|
||||
log.Printf("\n╔════════════════════════════════════════════════════════════════")
|
||||
log.Printf("║ Ready for verification!")
|
||||
log.Printf("║ Run: curl -X POST https://hub.ag3nts.org/verify \\")
|
||||
log.Printf("║ -H \"Content-Type: application/json\" \\")
|
||||
log.Printf("║ -d @output/final_answer.json")
|
||||
log.Printf("╚════════════════════════════════════════════════════════════════")
|
||||
}
|
||||
|
||||
log.Printf("\n✓✓✓ Processing completed successfully! ✓✓✓")
|
||||
return nil
|
||||
}
|
||||
|
||||
// processPerson processes one person - OPTIMIZED: only 2 LLM calls
|
||||
func (uc *PersonAgentProcessorUseCase) processPerson(
|
||||
ctx context.Context,
|
||||
person domain.Person,
|
||||
plantsData *domain.PowerPlantsData,
|
||||
plantCoordinates map[string]struct{ Lat, Lon float64 },
|
||||
) (*domain.PersonReport, error) {
|
||||
|
||||
log.Printf(" ┌─ Starting optimized analysis for: %s %s (born: %d)", person.Name, person.Surname, person.Born)
|
||||
log.Printf(" │ Strategy: LLM call 1 (locations) + parallel distance calc + LLM call 2 (access)")
|
||||
|
||||
// PHASE 1: Get person locations (LLM CALL 1)
|
||||
log.Printf(" │")
|
||||
log.Printf(" │ [LLM Call 1/2] Fetching person locations via function calling...")
|
||||
personLocations, err := uc.getPersonLocations(ctx, person)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting locations: %w", err)
|
||||
}
|
||||
log.Printf(" │ ✓ Found %d location(s) for %s %s", len(personLocations), person.Name, person.Surname)
|
||||
|
||||
if len(personLocations) == 0 {
|
||||
return nil, fmt.Errorf("no locations found for person")
|
||||
}
|
||||
|
||||
// PHASE 2: Calculate distances for ALL person locations to ALL power plants
|
||||
log.Printf(" │")
|
||||
log.Printf(" │ [Local Processing] Analyzing ALL %d location(s) of the person...", len(personLocations))
|
||||
|
||||
// Find globally nearest plant across ALL person locations
|
||||
var nearestPlant string
|
||||
var minDistance float64 = 1e10
|
||||
var bestLocation domain.PersonLocation
|
||||
|
||||
for i, personLoc := range personLocations {
|
||||
log.Printf(" │ ├─ Location [%d/%d]: %.4f°N, %.4f°E", i+1, len(personLocations), personLoc.Latitude, personLoc.Longitude)
|
||||
log.Printf(" │ │ ┌─ Checking distances to all plants:")
|
||||
|
||||
plant, dist := uc.findNearestPlantParallel(personLoc, plantsData, plantCoordinates)
|
||||
|
||||
log.Printf(" │ │ └─ Nearest plant for this location: %s (%.2f km)", plant, dist)
|
||||
|
||||
if dist < minDistance {
|
||||
minDistance = dist
|
||||
nearestPlant = plant
|
||||
bestLocation = personLoc
|
||||
log.Printf(" │ │ ★ NEW MINIMUM! Updated global minimum")
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf(" │ └─ ✓ Global analysis complete")
|
||||
log.Printf(" │ ✓ BEST result: %s at %.2f km (from location %.4f°N, %.4f°E)", nearestPlant, minDistance, bestLocation.Latitude, bestLocation.Longitude)
|
||||
|
||||
// PHASE 3: Get access level (LLM CALL 2)
|
||||
log.Printf(" │")
|
||||
log.Printf(" │ [LLM Call 2/2] Fetching access level via function calling...")
|
||||
accessLevel, err := uc.getAccessLevel(ctx, person)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting access level: %w", err)
|
||||
}
|
||||
log.Printf(" │ ✓ Access level: %d", accessLevel)
|
||||
|
||||
// Get plant code
|
||||
plantInfo, ok := plantsData.PowerPlants[nearestPlant]
|
||||
if !ok {
|
||||
log.Printf(" │ ✗ Plant not found in database: %s", nearestPlant)
|
||||
return nil, fmt.Errorf("plant not found: %s", nearestPlant)
|
||||
}
|
||||
|
||||
log.Printf(" │")
|
||||
log.Printf(" │ ═══ FINAL RESULTS ═══")
|
||||
log.Printf(" │ Nearest plant: %s (%s)", nearestPlant, plantInfo.Code)
|
||||
log.Printf(" │ Distance: %.2f km", minDistance)
|
||||
log.Printf(" │ Access level: %d", accessLevel)
|
||||
log.Printf(" │ Best location: %.4f°N, %.4f°E", bestLocation.Latitude, bestLocation.Longitude)
|
||||
log.Printf(" │ Total LLM calls: 2")
|
||||
|
||||
report := &domain.PersonReport{
|
||||
Name: person.Name,
|
||||
Surname: person.Surname,
|
||||
NearestPlant: nearestPlant,
|
||||
PlantCode: plantInfo.Code,
|
||||
DistanceKm: minDistance,
|
||||
AccessLevel: accessLevel,
|
||||
PrimaryLatitude: bestLocation.Latitude,
|
||||
PrimaryLongitude: bestLocation.Longitude,
|
||||
}
|
||||
|
||||
log.Printf(" └─ ✓ Successfully processed: %s %s (2 LLM calls total)", person.Name, person.Surname)
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
// getPersonLocations fetches locations for a person using LLM function calling (1 call)
|
||||
func (uc *PersonAgentProcessorUseCase) getPersonLocations(ctx context.Context, person domain.Person) ([]domain.PersonLocation, error) {
|
||||
tools := []domain.Tool{
|
||||
{
|
||||
Type: "function",
|
||||
Function: domain.FunctionDef{
|
||||
Name: "get_location",
|
||||
Description: "Gets the location information for a person by their name and surname",
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"name": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "The first name of the person",
|
||||
},
|
||||
"surname": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "The surname/last name of the person",
|
||||
},
|
||||
},
|
||||
"required": []string{"name", "surname"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
systemPrompt := fmt.Sprintf("Call get_location for %s %s and return the result.", person.Name, person.Surname)
|
||||
messages := []domain.LLMMessage{
|
||||
{Role: "system", Content: systemPrompt},
|
||||
{Role: "user", Content: "Get location now."},
|
||||
}
|
||||
|
||||
resp, err := uc.llmProvider.Chat(ctx, domain.LLMRequest{
|
||||
Messages: messages,
|
||||
Tools: tools,
|
||||
ToolChoice: "auto",
|
||||
Temperature: 0.0,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("LLM call failed: %w", err)
|
||||
}
|
||||
|
||||
if len(resp.Message.ToolCalls) == 0 {
|
||||
return nil, fmt.Errorf("no tool calls in response")
|
||||
}
|
||||
|
||||
// Execute get_location
|
||||
toolCall := resp.Message.ToolCalls[0]
|
||||
var args map[string]interface{}
|
||||
json.Unmarshal([]byte(toolCall.Function.Arguments), &args)
|
||||
|
||||
name, _ := args["name"].(string)
|
||||
surname, _ := args["surname"].(string)
|
||||
|
||||
log.Printf(" │ → API call: get_location(%s, %s)", name, surname)
|
||||
|
||||
req := domain.LocationRequest{
|
||||
APIKey: uc.apiKey,
|
||||
Name: name,
|
||||
Surname: surname,
|
||||
}
|
||||
|
||||
response, err := uc.apiClient.GetLocation(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("API call failed: %w", err)
|
||||
}
|
||||
|
||||
// Save to file
|
||||
fileName := fmt.Sprintf("%s_%s.json", name, surname)
|
||||
filePath := filepath.Join(uc.outputDir, "locations", fileName)
|
||||
os.WriteFile(filePath, response, 0644)
|
||||
|
||||
// Parse locations
|
||||
var locationData []map[string]interface{}
|
||||
var locations []domain.PersonLocation
|
||||
|
||||
if err := json.Unmarshal(response, &locationData); err != nil {
|
||||
return nil, fmt.Errorf("parsing locations: %w", err)
|
||||
}
|
||||
|
||||
for _, loc := range locationData {
|
||||
if lat, ok := loc["latitude"].(float64); ok {
|
||||
if lon, ok := loc["longitude"].(float64); ok {
|
||||
locations = append(locations, domain.PersonLocation{
|
||||
Name: name,
|
||||
Surname: surname,
|
||||
Latitude: lat,
|
||||
Longitude: lon,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return locations, nil
|
||||
}
|
||||
|
||||
// findNearestPlantParallel finds the nearest power plant using parallel goroutines (NO LLM)
|
||||
func (uc *PersonAgentProcessorUseCase) findNearestPlantParallel(
|
||||
personLoc domain.PersonLocation,
|
||||
plantsData *domain.PowerPlantsData,
|
||||
plantCoordinates map[string]struct{ Lat, Lon float64 },
|
||||
) (string, float64) {
|
||||
type result struct {
|
||||
plant string
|
||||
distance float64
|
||||
}
|
||||
|
||||
results := make(chan result, len(plantsData.PowerPlants))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Launch goroutine for each power plant
|
||||
for city, info := range plantsData.PowerPlants {
|
||||
if !info.IsActive {
|
||||
continue // Skip inactive plants
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(cityName string) {
|
||||
defer wg.Done()
|
||||
|
||||
coords, ok := plantCoordinates[cityName]
|
||||
if !ok {
|
||||
log.Printf(" │ │ ✗ Goroutine [%s]: no coordinates found", cityName)
|
||||
results <- result{cityName, 1e10}
|
||||
return
|
||||
}
|
||||
|
||||
distance := domain.Haversine(
|
||||
personLoc.Latitude,
|
||||
personLoc.Longitude,
|
||||
coords.Lat,
|
||||
coords.Lon,
|
||||
)
|
||||
|
||||
log.Printf(" │ │ • Goroutine [%s]: %.2f km (coords: %.4f°N, %.4f°E)", cityName, distance, coords.Lat, coords.Lon)
|
||||
results <- result{cityName, distance}
|
||||
}(city)
|
||||
}
|
||||
|
||||
// Close channel after all goroutines finish
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
// Collect results and find minimum
|
||||
minDistance := 1e10
|
||||
nearestPlant := ""
|
||||
|
||||
for r := range results {
|
||||
if r.distance < minDistance {
|
||||
minDistance = r.distance
|
||||
nearestPlant = r.plant
|
||||
}
|
||||
}
|
||||
|
||||
return nearestPlant, minDistance
|
||||
}
|
||||
|
||||
// getAccessLevel fetches access level for a person using LLM function calling (1 call)
|
||||
func (uc *PersonAgentProcessorUseCase) getAccessLevel(ctx context.Context, person domain.Person) (int, error) {
|
||||
tools := []domain.Tool{
|
||||
{
|
||||
Type: "function",
|
||||
Function: domain.FunctionDef{
|
||||
Name: "get_access_level",
|
||||
Description: "Gets the access level for a person",
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"name": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "The first name of the person",
|
||||
},
|
||||
"surname": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "The surname/last name of the person",
|
||||
},
|
||||
"birth_year": map[string]interface{}{
|
||||
"type": "integer",
|
||||
"description": "The birth year of the person (only year, not full date)",
|
||||
},
|
||||
},
|
||||
"required": []string{"name", "surname", "birth_year"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
systemPrompt := fmt.Sprintf("Call get_access_level for %s %s (birth year: %d).", person.Name, person.Surname, person.Born)
|
||||
messages := []domain.LLMMessage{
|
||||
{Role: "system", Content: systemPrompt},
|
||||
{Role: "user", Content: "Get access level now."},
|
||||
}
|
||||
|
||||
resp, err := uc.llmProvider.Chat(ctx, domain.LLMRequest{
|
||||
Messages: messages,
|
||||
Tools: tools,
|
||||
ToolChoice: "auto",
|
||||
Temperature: 0.0,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("LLM call failed: %w", err)
|
||||
}
|
||||
|
||||
if len(resp.Message.ToolCalls) == 0 {
|
||||
return 0, fmt.Errorf("no tool calls in response")
|
||||
}
|
||||
|
||||
// Execute get_access_level
|
||||
toolCall := resp.Message.ToolCalls[0]
|
||||
var args map[string]interface{}
|
||||
json.Unmarshal([]byte(toolCall.Function.Arguments), &args)
|
||||
|
||||
name, _ := args["name"].(string)
|
||||
surname, _ := args["surname"].(string)
|
||||
birthYear, _ := args["birth_year"].(float64)
|
||||
|
||||
log.Printf(" │ → API call: get_access_level(%s, %s, %d)", name, surname, int(birthYear))
|
||||
|
||||
req := domain.AccessLevelRequest{
|
||||
APIKey: uc.apiKey,
|
||||
Name: name,
|
||||
Surname: surname,
|
||||
BirthYear: int(birthYear),
|
||||
}
|
||||
|
||||
response, err := uc.apiClient.GetAccessLevel(ctx, req)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("API call failed: %w", err)
|
||||
}
|
||||
|
||||
// Save to file
|
||||
fileName := fmt.Sprintf("%s_%s.json", name, surname)
|
||||
filePath := filepath.Join(uc.outputDir, "accesslevel", fileName)
|
||||
os.WriteFile(filePath, response, 0644)
|
||||
|
||||
// Parse access level
|
||||
var accessData struct {
|
||||
AccessLevel int `json:"accessLevel"`
|
||||
}
|
||||
if err := json.Unmarshal(response, &accessData); err != nil {
|
||||
return 0, fmt.Errorf("parsing access level: %w", err)
|
||||
}
|
||||
|
||||
return accessData.AccessLevel, nil
|
||||
}
|
||||
|
||||
// loadPowerPlantsData loads power plants data from API
|
||||
func (uc *PersonAgentProcessorUseCase) loadPowerPlantsData(ctx context.Context) (*domain.PowerPlantsData, error) {
|
||||
url := fmt.Sprintf("https://hub.ag3nts.org/data/%s/findhim_locations.json", uc.apiKey)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var plantsData domain.PowerPlantsData
|
||||
if err := json.Unmarshal(body, &plantsData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &plantsData, nil
|
||||
}
|
||||
|
||||
// fetchPlantGeolocations fetches accurate coordinates for all power plants using geocoding API
|
||||
func (uc *PersonAgentProcessorUseCase) fetchPlantGeolocations(
|
||||
ctx context.Context,
|
||||
plantsData *domain.PowerPlantsData,
|
||||
) (map[string]struct{ Lat, Lon float64 }, error) {
|
||||
coordinates := make(map[string]struct{ Lat, Lon float64 })
|
||||
|
||||
for city := range plantsData.PowerPlants {
|
||||
log.Printf(" • Geocoding: %s...", city)
|
||||
|
||||
lat, lon, err := domain.GetPlantGeolocation(ctx, city)
|
||||
if err != nil {
|
||||
log.Printf(" ✗ Error: %v (using fallback)", err)
|
||||
// Use fallback coordinates if available
|
||||
if fallback, ok := domain.CityCoordinates[city]; ok {
|
||||
coordinates[city] = fallback
|
||||
log.Printf(" → Using fallback: %.4f°N, %.4f°E", fallback.Lat, fallback.Lon)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
coordinates[city] = struct{ Lat, Lon float64 }{Lat: lat, Lon: lon}
|
||||
log.Printf(" ✓ Fetched: %.6f°N, %.6f°E", lat, lon)
|
||||
}
|
||||
|
||||
return coordinates, nil
|
||||
}
|
||||
|
||||
// executeToolCall handles execution of various tool calls
|
||||
func (uc *PersonAgentProcessorUseCase) executeToolCall(
|
||||
ctx context.Context,
|
||||
toolCall domain.ToolCall,
|
||||
) (string, error) {
|
||||
var args map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &args); err != nil {
|
||||
return "", fmt.Errorf("parsing arguments: %w", err)
|
||||
}
|
||||
|
||||
switch toolCall.Function.Name {
|
||||
case "get_location":
|
||||
name, _ := args["name"].(string)
|
||||
surname, _ := args["surname"].(string)
|
||||
|
||||
log.Printf(" │ → Tool call: get_location(%s, %s)", name, surname)
|
||||
|
||||
req := domain.LocationRequest{
|
||||
APIKey: uc.apiKey,
|
||||
Name: name,
|
||||
Surname: surname,
|
||||
}
|
||||
|
||||
response, err := uc.apiClient.GetLocation(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Sprintf(`{"error": "%v"}`, err), nil
|
||||
}
|
||||
|
||||
// Save to file
|
||||
fileName := fmt.Sprintf("%s_%s.json", name, surname)
|
||||
filePath := filepath.Join(uc.outputDir, "locations", fileName)
|
||||
os.WriteFile(filePath, response, 0644)
|
||||
|
||||
return string(response), nil
|
||||
|
||||
case "get_access_level":
|
||||
name, _ := args["name"].(string)
|
||||
surname, _ := args["surname"].(string)
|
||||
birthYear, _ := args["birth_year"].(float64)
|
||||
|
||||
log.Printf(" │ → Tool call: get_access_level(%s, %s, %d)", name, surname, int(birthYear))
|
||||
|
||||
req := domain.AccessLevelRequest{
|
||||
APIKey: uc.apiKey,
|
||||
Name: name,
|
||||
Surname: surname,
|
||||
BirthYear: int(birthYear),
|
||||
}
|
||||
|
||||
response, err := uc.apiClient.GetAccessLevel(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Sprintf(`{"error": "%v"}`, err), nil
|
||||
}
|
||||
|
||||
// Save to file
|
||||
fileName := fmt.Sprintf("%s_%s.json", name, surname)
|
||||
filePath := filepath.Join(uc.outputDir, "accesslevel", fileName)
|
||||
os.WriteFile(filePath, response, 0644)
|
||||
|
||||
return string(response), nil
|
||||
|
||||
case "find_nearest_point":
|
||||
referenceLat, _ := args["reference_lat"].(float64)
|
||||
referenceLon, _ := args["reference_lon"].(float64)
|
||||
pointsRaw, _ := args["points"].([]interface{})
|
||||
|
||||
log.Printf(" │ → Tool call: find_nearest_point(ref: %.4f,%.4f, %d points)", referenceLat, referenceLon, len(pointsRaw))
|
||||
|
||||
// Parse points array
|
||||
var points [][]float64
|
||||
for _, p := range pointsRaw {
|
||||
if pointArr, ok := p.([]interface{}); ok && len(pointArr) >= 2 {
|
||||
lat, _ := pointArr[0].(float64)
|
||||
lon, _ := pointArr[1].(float64)
|
||||
points = append(points, []float64{lat, lon})
|
||||
}
|
||||
}
|
||||
|
||||
result := domain.FindNearestPoint(referenceLat, referenceLon, points)
|
||||
if result == nil {
|
||||
return `{"error": "no valid points provided"}`, nil
|
||||
}
|
||||
|
||||
log.Printf(" │ → Result: nearest point at index %d, distance %.2f km", result.Index, result.DistanceKm)
|
||||
|
||||
resultJSON, _ := json.Marshal(result)
|
||||
return string(resultJSON), nil
|
||||
|
||||
default:
|
||||
return fmt.Sprintf(`{"error": "unknown function: %s"}`, toolCall.Function.Name), nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user