695 lines
24 KiB
Go
695 lines
24 KiB
Go
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
|
|
}
|
|
}
|