final
This commit is contained in:
24
internal/domain/api_client.go
Normal file
24
internal/domain/api_client.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package domain
|
||||
|
||||
import "context"
|
||||
|
||||
// LocationRequest represents a request for location information
|
||||
type LocationRequest struct {
|
||||
APIKey string `json:"apikey"`
|
||||
Name string `json:"name"`
|
||||
Surname string `json:"surname"`
|
||||
}
|
||||
|
||||
// AccessLevelRequest represents a request for access level information
|
||||
type AccessLevelRequest struct {
|
||||
APIKey string `json:"apikey"`
|
||||
Name string `json:"name"`
|
||||
Surname string `json:"surname"`
|
||||
BirthYear int `json:"birthYear"`
|
||||
}
|
||||
|
||||
// APIClient defines the interface for API operations
|
||||
type APIClient interface {
|
||||
GetLocation(ctx context.Context, req LocationRequest) ([]byte, error)
|
||||
GetAccessLevel(ctx context.Context, req AccessLevelRequest) ([]byte, error)
|
||||
}
|
||||
50
internal/domain/city_coordinates.go
Normal file
50
internal/domain/city_coordinates.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package domain
|
||||
|
||||
// CityCoordinates contains coordinates for Polish cities with power plants
|
||||
var CityCoordinates = map[string]struct{ Lat, Lon float64 }{
|
||||
"Zabrze": {Lat: 50.3015, Lon: 18.7912},
|
||||
"Piotrków Trybunalski": {Lat: 51.4054, Lon: 19.7031},
|
||||
"Grudziądz": {Lat: 53.4836, Lon: 18.7536},
|
||||
"Tczew": {Lat: 54.0915, Lon: 18.7793},
|
||||
"Radom": {Lat: 51.4027, Lon: 21.1471},
|
||||
"Chelmno": {Lat: 53.3479, Lon: 18.4256},
|
||||
"Żarnowiec": {Lat: 54.7317, Lon: 18.0431},
|
||||
}
|
||||
|
||||
// PowerPlantInfo contains information about a power plant
|
||||
type PowerPlantInfo struct {
|
||||
IsActive bool `json:"is_active"`
|
||||
Power string `json:"power"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// PowerPlantsData represents the structure from findhim_locations.json
|
||||
type PowerPlantsData struct {
|
||||
PowerPlants map[string]PowerPlantInfo `json:"power_plants"`
|
||||
}
|
||||
|
||||
// ToLocations converts power plants data to locations with coordinates
|
||||
func (p *PowerPlantsData) ToLocations() []Location {
|
||||
var locations []Location
|
||||
|
||||
for city, info := range p.PowerPlants {
|
||||
// Only include active power plants
|
||||
if !info.IsActive {
|
||||
continue
|
||||
}
|
||||
|
||||
coords, ok := CityCoordinates[city]
|
||||
if !ok {
|
||||
// Skip cities without known coordinates
|
||||
continue
|
||||
}
|
||||
|
||||
locations = append(locations, Location{
|
||||
Name: city,
|
||||
Latitude: coords.Lat,
|
||||
Longitude: coords.Lon,
|
||||
})
|
||||
}
|
||||
|
||||
return locations
|
||||
}
|
||||
66
internal/domain/final_answer.go
Normal file
66
internal/domain/final_answer.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package domain
|
||||
|
||||
// FinalAnswer represents the final answer for the task
|
||||
type FinalAnswer struct {
|
||||
APIKey string `json:"apikey"`
|
||||
Task string `json:"task"`
|
||||
Answer AnswerDetail `json:"answer"`
|
||||
}
|
||||
|
||||
// AnswerDetail contains the person and power plant details
|
||||
type AnswerDetail struct {
|
||||
Name string `json:"name"`
|
||||
Surname string `json:"surname"`
|
||||
AccessLevel int `json:"accessLevel"`
|
||||
PowerPlant string `json:"powerPlant"`
|
||||
}
|
||||
|
||||
// FindClosestPowerPlant finds the power plant closest to any person
|
||||
// Returns the person, power plant, and distance
|
||||
func FindClosestPowerPlant(personDataMap map[string]*PersonData, powerPlants []Location, plantsData *PowerPlantsData) (*PersonData, *Location, float64, string) {
|
||||
var closestPerson *PersonData
|
||||
var closestPlant *Location
|
||||
minDistance := 1e10
|
||||
var powerPlantCode string
|
||||
|
||||
for _, personData := range personDataMap {
|
||||
if len(personData.Locations) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use first location (primary)
|
||||
personLoc := personData.Locations[0]
|
||||
|
||||
for _, plant := range powerPlants {
|
||||
distance := Haversine(
|
||||
personLoc.Latitude,
|
||||
personLoc.Longitude,
|
||||
plant.Latitude,
|
||||
plant.Longitude,
|
||||
)
|
||||
|
||||
// If distance is smaller, or same distance but higher access level
|
||||
if distance < minDistance {
|
||||
minDistance = distance
|
||||
closestPerson = personData
|
||||
closestPlant = &plant
|
||||
|
||||
// Get power plant code from plantsData
|
||||
if info, ok := plantsData.PowerPlants[plant.Name]; ok {
|
||||
powerPlantCode = info.Code
|
||||
}
|
||||
} else if distance == minDistance && closestPerson != nil && personData.AccessLevel > closestPerson.AccessLevel {
|
||||
// Same distance, but higher access level
|
||||
closestPerson = personData
|
||||
closestPlant = &plant
|
||||
|
||||
// Get power plant code from plantsData
|
||||
if info, ok := plantsData.PowerPlants[plant.Name]; ok {
|
||||
powerPlantCode = info.Code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closestPerson, closestPlant, minDistance, powerPlantCode
|
||||
}
|
||||
56
internal/domain/llm.go
Normal file
56
internal/domain/llm.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package domain
|
||||
|
||||
import "context"
|
||||
|
||||
// LLMMessage represents a message in the conversation
|
||||
type LLMMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||
}
|
||||
|
||||
// ToolCall represents a function call from the LLM
|
||||
type ToolCall struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Function Function `json:"function"`
|
||||
}
|
||||
|
||||
// Function represents the function being called
|
||||
type Function struct {
|
||||
Name string `json:"name"`
|
||||
Arguments string `json:"arguments"`
|
||||
}
|
||||
|
||||
// Tool represents a tool definition for function calling
|
||||
type Tool struct {
|
||||
Type string `json:"type"`
|
||||
Function FunctionDef `json:"function"`
|
||||
}
|
||||
|
||||
// FunctionDef defines a function that can be called
|
||||
type FunctionDef struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Parameters interface{} `json:"parameters"`
|
||||
}
|
||||
|
||||
// LLMRequest represents a request to the LLM with function calling support
|
||||
type LLMRequest struct {
|
||||
Messages []LLMMessage `json:"messages"`
|
||||
Tools []Tool `json:"tools,omitempty"`
|
||||
ToolChoice string `json:"tool_choice,omitempty"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
}
|
||||
|
||||
// LLMResponse represents the response from the LLM
|
||||
type LLMResponse struct {
|
||||
Message LLMMessage `json:"message"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
}
|
||||
|
||||
// LLMProvider defines the interface for LLM providers
|
||||
type LLMProvider interface {
|
||||
Chat(ctx context.Context, request LLMRequest) (*LLMResponse, error)
|
||||
}
|
||||
180
internal/domain/location.go
Normal file
180
internal/domain/location.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Location represents a location with coordinates
|
||||
type Location struct {
|
||||
Name string `json:"name"`
|
||||
Latitude float64 `json:"lat"`
|
||||
Longitude float64 `json:"lon"`
|
||||
}
|
||||
|
||||
// PersonLocation represents a person's location
|
||||
type PersonLocation struct {
|
||||
Name string `json:"name"`
|
||||
Surname string `json:"surname"`
|
||||
Latitude float64 `json:"lat"`
|
||||
Longitude float64 `json:"lon"`
|
||||
}
|
||||
|
||||
// DistanceResult represents the distance calculation result
|
||||
type DistanceResult struct {
|
||||
Person string `json:"person"`
|
||||
Location string `json:"location"`
|
||||
DistanceKm float64 `json:"distance_km"`
|
||||
}
|
||||
|
||||
// Haversine calculates the distance between two points on Earth (in kilometers)
|
||||
// using the Haversine formula
|
||||
func Haversine(lat1, lon1, lat2, lon2 float64) float64 {
|
||||
const earthRadiusKm = 6371.0
|
||||
|
||||
// Convert degrees to radians
|
||||
lat1Rad := lat1 * math.Pi / 180
|
||||
lat2Rad := lat2 * math.Pi / 180
|
||||
deltaLat := (lat2 - lat1) * math.Pi / 180
|
||||
deltaLon := (lon2 - lon1) * math.Pi / 180
|
||||
|
||||
// Haversine formula
|
||||
a := math.Sin(deltaLat/2)*math.Sin(deltaLat/2) +
|
||||
math.Cos(lat1Rad)*math.Cos(lat2Rad)*
|
||||
math.Sin(deltaLon/2)*math.Sin(deltaLon/2)
|
||||
|
||||
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
|
||||
|
||||
return earthRadiusKm * c
|
||||
}
|
||||
|
||||
// CalculateDistance calculates distance between person and location
|
||||
func CalculateDistance(person PersonLocation, location Location) DistanceResult {
|
||||
distance := Haversine(person.Latitude, person.Longitude, location.Latitude, location.Longitude)
|
||||
|
||||
return DistanceResult{
|
||||
Person: person.Name + " " + person.Surname,
|
||||
Location: location.Name,
|
||||
DistanceKm: distance,
|
||||
}
|
||||
}
|
||||
|
||||
// FindClosestLocation finds the closest location to a person
|
||||
func FindClosestLocation(person PersonLocation, locations []Location) *DistanceResult {
|
||||
if len(locations) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var closest *DistanceResult
|
||||
minDistance := math.MaxFloat64
|
||||
|
||||
for _, loc := range locations {
|
||||
result := CalculateDistance(person, loc)
|
||||
if result.DistanceKm < minDistance {
|
||||
minDistance = result.DistanceKm
|
||||
closest = &result
|
||||
}
|
||||
}
|
||||
|
||||
return closest
|
||||
}
|
||||
|
||||
// NominatimResponse represents OpenStreetMap Nominatim API response
|
||||
type NominatimResponse struct {
|
||||
Lat string `json:"lat"`
|
||||
Lon string `json:"lon"`
|
||||
}
|
||||
|
||||
// GetPlantGeolocation fetches accurate coordinates for a city using geocoding API
|
||||
func GetPlantGeolocation(ctx context.Context, cityName string) (float64, float64, error) {
|
||||
url := fmt.Sprintf("https://nominatim.openstreetmap.org/search?city=%s&country=Poland&format=json&limit=1", cityName)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
|
||||
// OpenStreetMap requires User-Agent header
|
||||
req.Header.Set("User-Agent", "PersonProcessor/1.0")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
|
||||
var results []NominatimResponse
|
||||
if err := json.Unmarshal(body, &results); err != nil {
|
||||
return 0, 0, fmt.Errorf("parsing JSON: %w", err)
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
return 0, 0, fmt.Errorf("no results found for city: %s", cityName)
|
||||
}
|
||||
|
||||
// Parse coordinates
|
||||
var lat, lon float64
|
||||
if _, err := fmt.Sscanf(results[0].Lat, "%f", &lat); err != nil {
|
||||
return 0, 0, fmt.Errorf("parsing latitude: %w", err)
|
||||
}
|
||||
if _, err := fmt.Sscanf(results[0].Lon, "%f", &lon); err != nil {
|
||||
return 0, 0, fmt.Errorf("parsing longitude: %w", err)
|
||||
}
|
||||
|
||||
return lat, lon, nil
|
||||
}
|
||||
|
||||
// NearestPointResult represents result of finding nearest point
|
||||
type NearestPointResult struct {
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
DistanceKm float64 `json:"distance_km"`
|
||||
Index int `json:"index"`
|
||||
}
|
||||
|
||||
// FindNearestPoint finds the nearest point from a list to a reference point
|
||||
func FindNearestPoint(referenceLat, referenceLon float64, points [][]float64) *NearestPointResult {
|
||||
if len(points) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var nearest *NearestPointResult
|
||||
minDistance := math.MaxFloat64
|
||||
|
||||
for i, point := range points {
|
||||
if len(point) < 2 {
|
||||
continue // Skip invalid points
|
||||
}
|
||||
|
||||
lat := point[0]
|
||||
lon := point[1]
|
||||
|
||||
distance := Haversine(referenceLat, referenceLon, lat, lon)
|
||||
|
||||
if distance < minDistance {
|
||||
minDistance = distance
|
||||
nearest = &NearestPointResult{
|
||||
Latitude: lat,
|
||||
Longitude: lon,
|
||||
DistanceKm: distance,
|
||||
Index: i,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nearest
|
||||
}
|
||||
8
internal/domain/location_repository.go
Normal file
8
internal/domain/location_repository.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package domain
|
||||
|
||||
import "context"
|
||||
|
||||
// LocationRepository defines the interface for loading locations
|
||||
type LocationRepository interface {
|
||||
LoadLocations(ctx context.Context, apiKey string) ([]Location, error)
|
||||
}
|
||||
11
internal/domain/person.go
Normal file
11
internal/domain/person.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package domain
|
||||
|
||||
// Person represents a person from the list
|
||||
type Person struct {
|
||||
Name string `json:"name"`
|
||||
Surname string `json:"surname"`
|
||||
Gender string `json:"gender"`
|
||||
Born int `json:"born"`
|
||||
City string `json:"city"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
13
internal/domain/person_report.go
Normal file
13
internal/domain/person_report.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package domain
|
||||
|
||||
// PersonReport represents a complete report for one person
|
||||
type PersonReport struct {
|
||||
Name string `json:"name"`
|
||||
Surname string `json:"surname"`
|
||||
NearestPlant string `json:"nearest_plant"`
|
||||
PlantCode string `json:"plant_code"`
|
||||
DistanceKm float64 `json:"distance_km"`
|
||||
AccessLevel int `json:"access_level"`
|
||||
PrimaryLatitude float64 `json:"primary_latitude"`
|
||||
PrimaryLongitude float64 `json:"primary_longitude"`
|
||||
}
|
||||
32
internal/domain/report.go
Normal file
32
internal/domain/report.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package domain
|
||||
|
||||
import "sort"
|
||||
|
||||
// PersonWithDistance represents a person with their distance to a location
|
||||
type PersonWithDistance struct {
|
||||
Name string `json:"name"`
|
||||
Surname string `json:"surname"`
|
||||
LocationName string `json:"location_name"`
|
||||
DistanceKm float64 `json:"distance_km"`
|
||||
AccessLevel int `json:"access_level,omitempty"`
|
||||
}
|
||||
|
||||
// LocationReport represents a report for a single location
|
||||
type LocationReport struct {
|
||||
LocationName string `json:"location_name"`
|
||||
Persons []PersonWithDistance `json:"persons"`
|
||||
}
|
||||
|
||||
// SortPersonsByDistance sorts persons by distance (ascending)
|
||||
func (r *LocationReport) SortPersonsByDistance() {
|
||||
sort.Slice(r.Persons, func(i, j int) bool {
|
||||
return r.Persons[i].DistanceKm < r.Persons[j].DistanceKm
|
||||
})
|
||||
}
|
||||
|
||||
// PersonData holds all gathered data for a person
|
||||
type PersonData struct {
|
||||
Person Person
|
||||
Locations []PersonLocation // Multiple possible locations
|
||||
AccessLevel int
|
||||
}
|
||||
8
internal/domain/repository.go
Normal file
8
internal/domain/repository.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package domain
|
||||
|
||||
import "context"
|
||||
|
||||
// PersonRepository defines the interface for loading persons
|
||||
type PersonRepository interface {
|
||||
LoadPersons(ctx context.Context, filePath string) ([]Person, error)
|
||||
}
|
||||
132
internal/domain/tools.go
Normal file
132
internal/domain/tools.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package domain
|
||||
|
||||
// GetToolDefinitions returns tool definitions for function calling
|
||||
func GetToolDefinitions() []Tool {
|
||||
return []Tool{
|
||||
{
|
||||
Type: "function",
|
||||
Function: FunctionDef{
|
||||
Name: "get_location",
|
||||
Description: "Gets the current location information for a person. Returns latitude and longitude coordinates.",
|
||||
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"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "function",
|
||||
Function: FunctionDef{
|
||||
Name: "get_access_level",
|
||||
Description: "Gets the access level information for a person. Requires the person's birth year (only the year as integer, not full date). Returns access level data.",
|
||||
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 the year as integer, e.g., 1987, not full date)",
|
||||
},
|
||||
},
|
||||
"required": []string{"name", "surname", "birth_year"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "function",
|
||||
Function: FunctionDef{
|
||||
Name: "get_power_plant_location",
|
||||
Description: "Gets the geographical coordinates (latitude and longitude) of a power plant by its city name.",
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"city_name": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "The name of the city where the power plant is located",
|
||||
},
|
||||
},
|
||||
"required": []string{"city_name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "function",
|
||||
Function: FunctionDef{
|
||||
Name: "calculate_distance",
|
||||
Description: "Calculates the distance in kilometers between two geographical points using the Haversine formula.",
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"lat1": map[string]interface{}{
|
||||
"type": "number",
|
||||
"description": "Latitude of the first point",
|
||||
},
|
||||
"lon1": map[string]interface{}{
|
||||
"type": "number",
|
||||
"description": "Longitude of the first point",
|
||||
},
|
||||
"lat2": map[string]interface{}{
|
||||
"type": "number",
|
||||
"description": "Latitude of the second point",
|
||||
},
|
||||
"lon2": map[string]interface{}{
|
||||
"type": "number",
|
||||
"description": "Longitude of the second point",
|
||||
},
|
||||
},
|
||||
"required": []string{"lat1", "lon1", "lat2", "lon2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "function",
|
||||
Function: FunctionDef{
|
||||
Name: "find_nearest_point",
|
||||
Description: "Finds the nearest point from a list of points to a reference point (e.g., power plant location). Returns the nearest point with its distance.",
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"reference_lat": map[string]interface{}{
|
||||
"type": "number",
|
||||
"description": "Latitude of the reference point (e.g., power plant)",
|
||||
},
|
||||
"reference_lon": map[string]interface{}{
|
||||
"type": "number",
|
||||
"description": "Longitude of the reference point (e.g., power plant)",
|
||||
},
|
||||
"points": map[string]interface{}{
|
||||
"type": "array",
|
||||
"description": "Array of points to check, each point is [latitude, longitude]",
|
||||
"items": map[string]interface{}{
|
||||
"type": "array",
|
||||
"items": map[string]interface{}{
|
||||
"type": "number",
|
||||
},
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": []string{"reference_lat", "reference_lon", "points"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user