Initial commit

This commit is contained in:
2026-07-13 09:25:40 +07:00
parent c08ff54676
commit bccfb156d7
1938 changed files with 641646 additions and 0 deletions

View File

@@ -0,0 +1,207 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System.Text.Json;
using RobotNet10.NavigationTune.Shared.Models;
namespace RobotNet10.NavigationTune.Data;
/// <summary>
/// Database context for tuning system
/// </summary>
public class TuningDbContext(DbContextOptions<TuningDbContext> options) : DbContext(options)
{
public DbSet<NavigationParameterSet> ParameterSets { get; set; }
public DbSet<TestScenarioEntity> TestScenarios { get; set; }
public DbSet<TestRun> TestRuns { get; set; }
public DbSet<TestMetrics> TestMetrics { get; set; }
public DbSet<SafetyViolation> SafetyViolations { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// JSON converter for complex types
var jsonOptions = new JsonSerializerOptions { WriteIndented = false };
// ParameterSets - Configure complex types as JSON
modelBuilder.Entity<NavigationParameterSet>(entity =>
{
entity.ToTable("parameter_sets");
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(200);
entity.Property(e => e.Description).HasMaxLength(500);
entity.Property(e => e.CreatedAt).IsRequired();
entity.Property(e => e.UpdatedAt);
entity.Property(e => e.IsDefault);
entity.Property(e => e.Version);
entity.Property(e => e.ControllerType).HasConversion<int>(); // Store enum as int
// Store complex types as JSON
entity.Property(e => e.MovePidConfig)
.HasConversion(
v => JsonSerializer.Serialize(v, jsonOptions),
v => JsonSerializer.Deserialize<PIDConfig>(v, jsonOptions) ?? new PIDConfig())
.HasColumnType("TEXT"); // SQLite uses TEXT, PostgreSQL will use jsonb
entity.Property(e => e.RotatePidConfig)
.HasConversion(
v => JsonSerializer.Serialize(v, jsonOptions),
v => JsonSerializer.Deserialize<PIDConfig>(v, jsonOptions) ?? new PIDConfig())
.HasColumnType("TEXT");
entity.Property(e => e.PurePursuitConfig)
.HasConversion(
v => JsonSerializer.Serialize(v, jsonOptions),
v => JsonSerializer.Deserialize<PurePursuitConfig>(v, jsonOptions) ?? new PurePursuitConfig())
.HasColumnType("TEXT");
entity.Property(e => e.StanleyConfig)
.HasConversion(
v => JsonSerializer.Serialize(v, jsonOptions),
v => JsonSerializer.Deserialize<StanleyConfig>(v, jsonOptions) ?? new StanleyConfig())
.HasColumnType("TEXT");
entity.Property(e => e.EstimatorConfig)
.HasConversion(
v => JsonSerializer.Serialize(v, jsonOptions),
v => JsonSerializer.Deserialize<VelocityEstimatorConfig>(v, jsonOptions) ?? new VelocityEstimatorConfig())
.HasColumnType("TEXT");
entity.Property(e => e.SignalConfig)
.HasConversion(
v => JsonSerializer.Serialize(v, jsonOptions),
v => JsonSerializer.Deserialize<VelocitySignalProcessingConfig>(v, jsonOptions) ?? new VelocitySignalProcessingConfig())
.HasColumnType("TEXT");
entity.Property(e => e.MotorDynamicsConfig)
.HasConversion(
v => JsonSerializer.Serialize(v, jsonOptions),
v => JsonSerializer.Deserialize<MotorDynamicsConfig>(v, jsonOptions) ?? new MotorDynamicsConfig())
.HasColumnType("TEXT");
entity.Property(e => e.NavigationConfig)
.HasConversion(
v => JsonSerializer.Serialize(v, jsonOptions),
v => JsonSerializer.Deserialize<NavigationConfig>(v, jsonOptions) ?? new NavigationConfig())
.HasColumnType("TEXT");
entity.HasIndex(e => e.Name);
entity.HasIndex(e => e.IsDefault);
entity.HasIndex(e => e.CreatedAt);
});
// TestScenarios - Store config as JSON since TestScenario is abstract
modelBuilder.Entity<TestScenarioEntity>(entity =>
{
entity.ToTable("test_scenarios");
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(200);
entity.Property(e => e.Description).HasMaxLength(500);
entity.Property(e => e.Type).IsRequired();
entity.Property(e => e.ConfigJson).IsRequired().HasColumnType("TEXT");
entity.Property(e => e.CreatedAt).IsRequired();
entity.Property(e => e.IsDefault);
entity.HasIndex(e => e.Type);
entity.HasIndex(e => e.Name);
entity.HasIndex(e => e.CreatedAt);
});
// TestRuns
modelBuilder.Entity<TestRun>(entity =>
{
entity.ToTable("test_runs");
entity.HasKey(e => e.Id);
// Foreign keys
entity.Property(e => e.ScenarioId).IsRequired();
entity.Property(e => e.ParameterSetId).IsRequired();
// Navigation properties - Ignore since we load separately to avoid circular dependencies
entity.Ignore(e => e.Scenario);
entity.Ignore(e => e.ParameterSet);
// Properties
entity.Property(e => e.StartTime).IsRequired();
entity.Property(e => e.EndTime);
entity.Property(e => e.Status).IsRequired().HasConversion<int>();
entity.Property(e => e.Duration);
entity.Property(e => e.Notes).HasMaxLength(1000);
entity.Property(e => e.ErrorMessage).HasMaxLength(1000);
// Relationships
entity.HasOne(e => e.Metrics)
.WithOne()
.HasForeignKey<TestMetrics>(m => m.TestRunId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasMany(e => e.SafetyViolations)
.WithOne()
.HasForeignKey(v => v.TestRunId)
.OnDelete(DeleteBehavior.Cascade);
// Indexes
entity.HasIndex(e => e.ScenarioId);
entity.HasIndex(e => e.ParameterSetId);
entity.HasIndex(e => e.StartTime);
entity.HasIndex(e => e.Status);
entity.HasIndex(e => new { e.ScenarioId, e.ParameterSetId });
});
// TestMetrics
modelBuilder.Entity<TestMetrics>(entity =>
{
entity.ToTable("test_metrics");
entity.HasKey(e => e.Id);
// Foreign key
entity.Property(e => e.TestRunId).IsRequired();
// All metrics are double/double
entity.Property(e => e.CrossTrackErrorRMS);
entity.Property(e => e.CrossTrackErrorPeak);
entity.Property(e => e.CrossTrackErrorMean);
entity.Property(e => e.CrossTrackErrorStdDev);
entity.Property(e => e.HeadingErrorRMS);
entity.Property(e => e.HeadingErrorPeak);
entity.Property(e => e.GoalPositionError);
entity.Property(e => e.GoalHeadingError);
entity.Property(e => e.VelocityStdDev);
entity.Property(e => e.AccelerationStdDev);
entity.Property(e => e.PathLengthRatio);
entity.Property(e => e.CompletionTime);
entity.Property(e => e.AverageSpeed);
entity.Property(e => e.MaxSpeed);
entity.Property(e => e.OverallScore);
entity.Property(e => e.TrackingScore);
entity.Property(e => e.SmoothnessScore);
entity.Property(e => e.EfficiencyScore);
entity.Property(e => e.PassedCriteria);
// Unique index on TestRunId (one-to-one relationship)
entity.HasIndex(e => e.TestRunId).IsUnique();
});
// SafetyViolations
modelBuilder.Entity<SafetyViolation>(entity =>
{
entity.ToTable("safety_violations");
entity.HasKey(e => e.Id);
// Foreign key
entity.Property(e => e.TestRunId).IsRequired();
// Properties
entity.Property(e => e.Timestamp).IsRequired();
entity.Property(e => e.Type).IsRequired().HasConversion<int>();
entity.Property(e => e.Severity).IsRequired().HasConversion<int>();
entity.Property(e => e.Value);
entity.Property(e => e.Threshold);
entity.Property(e => e.Message).IsRequired().HasMaxLength(500);
// Indexes
entity.HasIndex(e => e.TestRunId);
entity.HasIndex(e => e.Timestamp);
entity.HasIndex(e => new { e.TestRunId, e.Timestamp });
});
}
}