153 lines
6.1 KiB
C#
153 lines
6.1 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Logging;
|
|
using RobotNet10.NavigationTune.Shared.Interfaces;
|
|
using RobotNet10.NavigationTune.Shared.Models;
|
|
|
|
namespace RobotNet10.NavigationTune.Controllers;
|
|
|
|
/// <summary>
|
|
/// REST API controller for tuning advisor - re-analyze tests and apply suggestions
|
|
/// </summary>
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
public class TuningAdvisorController(
|
|
ITuningAdvisor tuningAdvisor,
|
|
ITestRepository testRepository,
|
|
IParameterManager parameterManager,
|
|
ILogger<TuningAdvisorController> logger) : ControllerBase
|
|
{
|
|
/// <summary>
|
|
/// Re-analyze a completed test run using telemetry provided in the request body.
|
|
/// Note: Telemetry data is not persisted to database, so the client must supply it.
|
|
/// The primary analysis is done automatically in the orchestrator after test completion.
|
|
/// </summary>
|
|
[HttpPost("analyze/{testRunId}")]
|
|
public async Task<ActionResult<TuningReport>> AnalyzeTestRun(
|
|
Guid testRunId, [FromBody] List<TelemetryData>? telemetry = null)
|
|
{
|
|
try
|
|
{
|
|
var testRun = await testRepository.GetByIdAsync(testRunId);
|
|
if (testRun == null)
|
|
return NotFound(new { error = $"Test run {testRunId} not found" });
|
|
|
|
if (testRun.Status != TestStatus.Completed)
|
|
return BadRequest(new { error = "Only completed tests can be analyzed" });
|
|
|
|
var parameterSet = await parameterManager.GetByIdAsync(testRun.ParameterSetId);
|
|
if (parameterSet == null)
|
|
return NotFound(new { error = $"Parameter set {testRun.ParameterSetId} not found" });
|
|
|
|
if (telemetry == null || telemetry.Count < 10)
|
|
return BadRequest(new { error = "Telemetry data is required for analysis (min 10 samples). Telemetry is not stored in the database; it is only available during test execution via SignalR." });
|
|
|
|
var metrics = testRun.Metrics;
|
|
if (metrics == null)
|
|
return BadRequest(new { error = "No metrics available for this test run" });
|
|
|
|
var report = tuningAdvisor.Analyze(telemetry, metrics, parameterSet);
|
|
report.TestRunId = testRunId;
|
|
|
|
return Ok(report);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Error analyzing test run {TestRunId}", testRunId);
|
|
return StatusCode(500, new { error = "Failed to analyze test run", details = ex.Message });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply selected suggestions to a parameter set (returns new set, does not persist).
|
|
/// </summary>
|
|
[HttpPost("apply")]
|
|
public async Task<ActionResult<NavigationParameterSet>> ApplySuggestions(
|
|
[FromBody] ApplySuggestionsRequest request)
|
|
{
|
|
try
|
|
{
|
|
var parameterSet = await parameterManager.GetByIdAsync(request.ParameterSetId);
|
|
if (parameterSet == null)
|
|
return NotFound(new { error = $"Parameter set {request.ParameterSetId} not found" });
|
|
|
|
var selectedSuggestions = request.Report.Suggestions
|
|
.Where(s => request.SuggestionIds.Contains(s.Id))
|
|
.ToList();
|
|
|
|
if (selectedSuggestions.Count == 0)
|
|
return BadRequest(new { error = "No valid suggestions selected" });
|
|
|
|
var modified = tuningAdvisor.ApplyAllSuggestions(parameterSet, selectedSuggestions);
|
|
return Ok(modified);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Error applying suggestions");
|
|
return StatusCode(500, new { error = "Failed to apply suggestions", details = ex.Message });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply suggestions and save as a new parameter set.
|
|
/// </summary>
|
|
[HttpPost("apply-and-save")]
|
|
public async Task<ActionResult<NavigationParameterSet>> ApplyAndSave(
|
|
[FromBody] ApplyAndSaveRequest request)
|
|
{
|
|
try
|
|
{
|
|
var parameterSet = await parameterManager.GetByIdAsync(request.ParameterSetId);
|
|
if (parameterSet == null)
|
|
return NotFound(new { error = $"Parameter set {request.ParameterSetId} not found" });
|
|
|
|
var selectedSuggestions = request.Report.Suggestions
|
|
.Where(s => request.SuggestionIds.Contains(s.Id))
|
|
.ToList();
|
|
|
|
if (selectedSuggestions.Count == 0)
|
|
return BadRequest(new { error = "No valid suggestions selected" });
|
|
|
|
var modified = tuningAdvisor.ApplyAllSuggestions(parameterSet, selectedSuggestions);
|
|
|
|
// Create new parameter set with unique name
|
|
modified.Id = Guid.NewGuid();
|
|
modified.Name = request.NewParameterSetName
|
|
?? $"{parameterSet.Name}_tuned_{DateTime.UtcNow:yyyyMMdd_HHmmss}";
|
|
modified.Description = $"Auto-tuned from '{parameterSet.Name}'. Applied {selectedSuggestions.Count} suggestion(s).";
|
|
modified.IsDefault = false;
|
|
modified.CreatedAt = DateTime.UtcNow;
|
|
modified.Version = parameterSet.Version + 1;
|
|
|
|
await parameterManager.SaveAsync(modified);
|
|
|
|
return Ok(modified);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Error applying and saving suggestions");
|
|
return StatusCode(500, new { error = "Failed to apply and save suggestions", details = ex.Message });
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request model for applying suggestions to a parameter set.
|
|
/// </summary>
|
|
public class ApplySuggestionsRequest
|
|
{
|
|
public Guid ParameterSetId { get; set; }
|
|
public List<Guid> SuggestionIds { get; set; } = new();
|
|
public TuningReport Report { get; set; } = null!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request model for applying suggestions and saving as a new parameter set.
|
|
/// </summary>
|
|
public class ApplyAndSaveRequest
|
|
{
|
|
public Guid ParameterSetId { get; set; }
|
|
public List<Guid> SuggestionIds { get; set; } = new();
|
|
public TuningReport Report { get; set; } = null!;
|
|
public string? NewParameterSetName { get; set; }
|
|
}
|