This commit is contained in:
2026-03-12 02:10:57 +01:00
parent a8e769c73d
commit 3c6dd52d8f
29 changed files with 3323 additions and 0 deletions

View 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)
}

View 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
}

View 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
View 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
View 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
}

View 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
View 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"`
}

View 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
View 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
}

View 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
View 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"},
},
},
},
}
}