PlanSolve 0.25.2
See the version list below for details.
dotnet add package PlanSolve --version 0.25.2
NuGet\Install-Package PlanSolve -Version 0.25.2
<PackageReference Include="PlanSolve" Version="0.25.2" />
<PackageVersion Include="PlanSolve" Version="0.25.2" />
<PackageReference Include="PlanSolve" />
paket add PlanSolve --version 0.25.2
#r "nuget: PlanSolve, 0.25.2"
#:package PlanSolve@0.25.2
#addin nuget:?package=PlanSolve&version=0.25.2
#tool nuget:?package=PlanSolve&version=0.25.2
PlanSolve .NET SDK
A .NET client library for the PlanSolve optimization API. Solve complex field service routing, professional services task assignment, and shift scheduling problems with a simple, async API.
Installation
Install via NuGet:
dotnet add package PlanSolve
Or via Package Manager:
Install-Package PlanSolve
Requirements
- .NET 10.0 or later
- PlanSolve API key (required for authenticated requests)
API Workflow
All PlanSolve optimization APIs follow the same workflow:
- Construct Request - Build your optimization request with the required data
- Start Solver - Submit the request to start the optimization process
- Poll Status - Check the status until the solver completes
- Get Results - Retrieve the optimized solution
Quick Start
Field Service Optimization
To instantiate the client and perform a basic field service optimization:
using PlanSolve;
using PlanSolve.FieldService;
var client = new PlanSolveClient("YOUR_API_KEY");
// Create vehicles (field technicians)
var vehicles = new List<Vehicle>
{
new()
{
Id = "tech1",
Location = new[] { 40.7128, -74.0060 }, // NYC coordinates [lat, lng]
Shifts = new List<Shift>
{
new()
{
Id = "morning-shift",
MinStartTime = DateTime.Parse("2024-01-15T08:00:00"),
MaxEndTime = DateTime.Parse("2024-01-15T17:00:00")
}
},
Skills = new[] { "repair", "installation" }
},
new()
{
Id = "tech2",
Location = new[] { 40.7128, -74.0060 },
Shifts = new List<Shift>
{
new()
{
Id = "afternoon-shift",
MinStartTime = DateTime.Parse("2024-01-15T10:00:00"),
MaxEndTime = DateTime.Parse("2024-01-15T19:00:00")
}
},
Skills = new[] { "repair" }
}
};
// Create visits (customer appointments)
var visits = new List<Visit>
{
new()
{
Id = "visit1",
Name = "Times Square Repair",
Location = new[] { 40.7589, -73.9851 }, // Times Square coordinates [lat, lng]
TimeWindows = new List<TimeWindow>
{
new()
{
MinStartTime = DateTime.Parse("2024-01-15T09:00:00"),
MaxEndTime = DateTime.Parse("2024-01-15T17:00:00")
}
},
ServiceDuration = "PT30M", // ISO 8601 duration (30 minutes)
Priority = "HIGH",
RequiredSkills = new[] { "repair" }
},
new()
{
Id = "visit2",
Name = "Penn Station Installation",
Location = new[] { 40.7505, -73.9934 }, // Penn Station coordinates [lat, lng]
TimeWindows = new List<TimeWindow>
{
new()
{
MinStartTime = DateTime.Parse("2024-01-15T10:00:00"),
MaxEndTime = DateTime.Parse("2024-01-15T16:00:00")
}
},
ServiceDuration = "PT45M", // 45 minutes
Priority = "MEDIUM",
RequiredSkills = new[] { "installation" }
}
};
// Create the optimization request
var request = new FieldServiceStartRequest
{
Vehicles = vehicles,
Visits = visits
};
try
{
// Option 1: Start and poll manually
var response = await client.FieldService.StartAsync(request);
Console.WriteLine($"Optimization started! Job ID: {response.JobId}");
// Check status until completion
SolverStatusResponse status;
do
{
await Task.Delay(2000); // Wait 2 seconds
status = await client.FieldService.GetStatusAsync(response.JobId);
Console.WriteLine($"Status: {status.Status}");
} while (status.Solving || status.SolverStatus == SolverStatus.SOLVING_SCHEDULED);
if (status.Status == "COMPLETED")
{
// Get the final result
var result = await client.FieldService.GetResultAsync(response.JobId);
Console.WriteLine("Optimization completed!");
// Print the optimized routes
foreach (var vehicle in result.Vehicles)
{
Console.WriteLine($"\nRoute for {vehicle.Id}:");
foreach (var visitId in vehicle.Visits)
{
var visit = result.Visits.FirstOrDefault(v => v.Id == visitId);
if (visit != null)
{
Console.WriteLine($" - {visit.Name}: {visit.ArrivalTime} - {visit.DepartureTime}");
}
}
}
}
// Option 2: Use the convenience method to wait for completion
var result2 = await client.FieldService.StartAndWaitForCompletionAsync(request);
Console.WriteLine("Optimization completed with result!");
}
catch (Exception ex)
{
Console.Error.WriteLine($"Optimization failed: {ex.Message}");
}
finally
{
client.Dispose();
}
Professional Services Optimization
For professional services task assignment optimization:
using PlanSolve;
using PlanSolve.ProfessionalServices;
var client = new PlanSolveClient("YOUR_API_KEY");
// Create employees (consultants, developers, etc.)
var employees = new List<Employee>
{
new()
{
Id = "emp1",
Shifts = new List<Shift>
{
new("shift1", DateTime.Parse("2024-01-15T08:00:00"), DateTime.Parse("2024-01-15T18:00:00"))
},
Skills = new[] { "Java", "Spring", "Kotlin" }
},
new()
{
Id = "emp2",
Shifts = new List<Shift>
{
new("shift2", DateTime.Parse("2024-01-15T09:00:00"), DateTime.Parse("2024-01-15T17:00:00"))
},
Skills = new[] { "Python", "SQL", "React" }
}
};
// Create tasks (projects, assignments, etc.)
var tasks = new List<Task>
{
new()
{
Id = "task1",
Name = "Develop REST API",
Deadline = DateTime.Parse("2024-01-20T17:00:00"),
Duration = "PT16H", // ISO 8601 duration (16 hours)
Priority = "HIGH",
RequiredSkills = new[] { "Java", "Spring" }
},
new()
{
Id = "task2",
Name = "Database Design",
Deadline = DateTime.Parse("2024-01-22T17:00:00"),
Duration = "PT8H", // 8 hours
Priority = "MEDIUM",
RequiredSkills = new[] { "SQL", "Database Design" }
}
};
// Create the optimization request
var request = new ProfessionalServicesStartRequest
{
Employees = employees,
Tasks = tasks
};
try
{
// Option 1: Start and poll manually
var response = await client.ProfessionalServices.StartAsync(request);
Console.WriteLine($"Optimization started! Job ID: {response.JobId}");
// Poll for status
SolverStatusResponse status;
do
{
await Task.Delay(2000); // Wait 2 seconds
status = await client.ProfessionalServices.GetStatusAsync(response.JobId);
Console.WriteLine($"Status: {status.Status}");
} while (status.Solving);
if (status.Status == "COMPLETED")
{
// Get the final result
var result = await client.ProfessionalServices.GetResultAsync(response.JobId);
Console.WriteLine("Optimization completed!");
// Print the optimized task assignments
foreach (var employee in result.Employees)
{
Console.WriteLine($"\nEmployee {employee.Id} assigned tasks:");
foreach (var taskId in employee.Tasks)
{
var task = result.Tasks.FirstOrDefault(t => t.Id == taskId);
if (task != null)
{
Console.WriteLine($" - {task.Name}: {task.StartTime} - {task.EndTime}");
}
}
}
}
// Option 2: Use the convenience method to wait for completion
var result2 = await client.ProfessionalServices.StartAndWaitForCompletionAsync(request);
Console.WriteLine("Optimization completed with result!");
}
catch (Exception ex)
{
Console.Error.WriteLine($"Optimization failed: {ex.Message}");
}
finally
{
client.Dispose();
}
Shift Assignment Optimization
For shift scheduling and assignment:
using PlanSolve;
using PlanSolve.Shifts;
var client = new PlanSolveClient("YOUR_API_KEY");
// Create employees
var employees = new List<Employee>
{
new()
{
Id = "emp1",
Shifts = new List<Shift>
{
new("shift1", DateTime.Parse("2024-01-15T08:00:00"), DateTime.Parse("2024-01-15T18:00:00"))
},
Skills = new[] { "cashier", "inventory" }
},
new()
{
Id = "emp2",
Shifts = new List<Shift>
{
new("shift2", DateTime.Parse("2024-01-15T09:00:00"), DateTime.Parse("2024-01-15T17:00:00"))
},
Skills = new[] { "manager", "cashier" }
}
};
// Create tasks (shift assignments)
var tasks = new List<Task>
{
new()
{
Id = "task1",
Name = "Morning Cashier Shift",
Deadline = DateTime.Parse("2024-01-15T12:00:00"),
Duration = "PT4H", // 4 hours
Priority = "HIGH",
RequiredSkills = new[] { "cashier" }
},
new()
{
Id = "task2",
Name = "Inventory Management",
Deadline = DateTime.Parse("2024-01-15T16:00:00"),
Duration = "PT2H", // 2 hours
Priority = "MEDIUM",
RequiredSkills = new[] { "inventory" }
}
};
// Create the optimization request
var request = new ShiftStartRequest
{
Employees = employees,
Tasks = tasks
};
try
{
// Option 1: Start and poll manually
var response = await client.Shift.StartAsync(request);
Console.WriteLine($"Optimization started! Job ID: {response.JobId}");
// Poll for status
SolverStatusResponse status;
do
{
await Task.Delay(2000);
status = await client.Shift.GetStatusAsync(response.JobId);
Console.WriteLine($"Status: {status.Status}");
} while (status.Solving);
if (status.Status == "COMPLETED")
{
var result = await client.Shift.GetResultAsync(response.JobId);
Console.WriteLine("Optimization completed!");
}
// Option 2: Use the convenience method to wait for completion
var result2 = await client.Shift.StartAndWaitForCompletionAsync(request);
Console.WriteLine("Optimization completed with result!");
}
catch (Exception ex)
{
Console.Error.WriteLine($"Optimization failed: {ex.Message}");
}
finally
{
client.Dispose();
}
API Reference
PlanSolveClient
The main client class for interacting with the PlanSolve API.
Constructor
new PlanSolveClient(string apiKey)
new PlanSolveClient(string apiKey, HttpClient httpClient)
apiKey(required): Your PlanSolve API key for authenticated requestshttpClient(optional): Custom HttpClient instance for dependency injection
Properties
FieldService: Access to field service optimization methodsProfessionalServices: Access to professional services optimization methodsShift: Access to shift assignment optimization methods
Disposal
The client implements IDisposable and should be disposed when no longer needed, especially if you're not providing your own HttpClient.
FieldServiceApiClient
Handles field service optimization requests and results.
Methods
StartAsync(FieldServiceStartRequest request): Start a new field service optimizationGetResultAsync(string jobId): Get the completed optimization resultGetStatusAsync(string jobId): Get the status of a running optimization jobStartAndWaitForCompletionAsync(FieldServiceStartRequest request, int pollIntervalMs = 5000, int maxAttempts = 10, CancellationToken cancellationToken = default): Start optimization and wait for completion with pollingWaitForCompletionAsync(string jobId, int pollIntervalMs = 5000, int maxAttempts = 10, CancellationToken cancellationToken = default): Wait for an existing job to complete
ProfessionalServicesApiClient
Handles professional services task assignment optimization requests and results.
Methods
StartAsync(ProfessionalServicesStartRequest request): Start a new professional services optimizationGetResultAsync(string jobId): Get the completed optimization resultGetStatusAsync(string jobId): Get the status of a running optimization jobStartAndWaitForCompletionAsync(ProfessionalServicesStartRequest request, int pollIntervalMs = 5000, int maxAttempts = 1000, CancellationToken cancellationToken = default): Start optimization and wait for completion with pollingWaitForCompletionAsync(string jobId, int pollIntervalMs = 5000, int maxAttempts = 1000, CancellationToken cancellationToken = default): Wait for an existing job to complete
ShiftApiClient
Handles shift assignment optimization requests and results.
Methods
StartAsync(ShiftStartRequest request): Start a new shift assignment optimizationGetResultAsync(string jobId): Get the completed optimization resultGetStatusAsync(string jobId): Get the status of a running optimization jobStartAndWaitForCompletionAsync(ShiftStartRequest request, int pollIntervalMs = 5000, int maxAttempts = 1000, CancellationToken cancellationToken = default): Start optimization and wait for completion with pollingWaitForCompletionAsync(string jobId, int pollIntervalMs = 5000, int maxAttempts = 1000, CancellationToken cancellationToken = default): Wait for an existing job to complete
Data Models
Field Service Models
Vehicle
public class Vehicle
{
public string Id { get; set; }
public double[] Location { get; set; } // [latitude, longitude]
public List<Shift> Shifts { get; set; }
public string[] Skills { get; set; }
}
Shift
public class Shift
{
public string Id { get; set; }
public DateTime MinStartTime { get; set; }
public DateTime MaxEndTime { get; set; }
}
Visit
public class Visit
{
public string Id { get; set; }
public string Name { get; set; }
public double[] Location { get; set; } // [latitude, longitude]
public List<TimeWindow> TimeWindows { get; set; }
public string ServiceDuration { get; set; } // ISO 8601 duration string
public string Priority { get; set; } // "HIGH", "MEDIUM", or "LOW"
public string[] RequiredSkills { get; set; }
}
TimeWindow
public class TimeWindow
{
public DateTime MinStartTime { get; set; }
public DateTime MaxEndTime { get; set; }
}
Professional Services Models
Employee
public class Employee
{
public string Id { get; set; }
public List<Shift> Shifts { get; set; }
public string[] Skills { get; set; }
}
Task
public class Task
{
public string Id { get; set; }
public string Name { get; set; }
public DateTime? Deadline { get; set; }
public string Duration { get; set; } // ISO 8601 duration string
public string Priority { get; set; }
public string[] RequiredSkills { get; set; }
}
Shift Models
Employee
public class Employee
{
public string Id { get; set; }
public List<Shift> Shifts { get; set; }
public string[] Skills { get; set; }
}
Task
public class Task
{
public string Id { get; set; }
public string Name { get; set; }
public DateTime? Deadline { get; set; }
public string Duration { get; set; } // ISO 8601 duration string
public string Priority { get; set; }
public string[] RequiredSkills { get; set; }
}
Advanced Usage
Using Dependency Injection
// Register in your DI container
services.AddHttpClient<PlanSolveClient>(client =>
{
client.BaseAddress = new Uri("https://plansolve.app/");
client.DefaultRequestHeaders.Add("X-API-KEY", "YOUR_API_KEY");
});
// Or use with your own HttpClient
var httpClient = new HttpClient();
var client = new PlanSolveClient("YOUR_API_KEY", httpClient);
Error Handling
try
{
var response = await client.FieldService.StartAsync(request);
// Handle success
}
catch (HttpRequestException ex) when (ex.Message.Contains("401"))
{
Console.Error.WriteLine("Invalid API key");
}
catch (HttpRequestException ex) when (ex.Message.Contains("400"))
{
Console.Error.WriteLine("Invalid request data");
}
catch (HttpRequestException ex) when (ex.Message.Contains("429"))
{
Console.Error.WriteLine("Rate limit exceeded");
}
catch (Exception ex)
{
Console.Error.WriteLine($"Unexpected error: {ex.Message}");
}
Cancellation Support
All async methods support CancellationToken:
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); // 5 minute timeout
try
{
var result = await client.FieldService.StartAndWaitForCompletionAsync(
request,
cancellationToken: cts.Token
);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was cancelled");
}
Resilience
The SDK uses Polly for resilience patterns including retry logic and circuit breakers. Failed requests are automatically retried with exponential backoff.
Examples
See the Tests/ directory for additional usage examples and integration tests.
Support
For API support and questions, please refer to the main PlanSolve documentation or contact support.
License
This project is licensed under the MIT License - see the LICENSE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Polly (>= 8.6.5)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.