diff --git a/SampleApplication.Tests/SampleApplication.Tests.csproj b/SampleApplication.Tests/SampleApplication.Tests.csproj
new file mode 100644
index 0000000..a3e0643
--- /dev/null
+++ b/SampleApplication.Tests/SampleApplication.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SampleApplication.Tests/ShapeProcessTest.cs b/SampleApplication.Tests/ShapeProcessTest.cs
new file mode 100644
index 0000000..d52b446
--- /dev/null
+++ b/SampleApplication.Tests/ShapeProcessTest.cs
@@ -0,0 +1,101 @@
+using SampleApplication.Entities;
+
+namespace SampleApplication.Tests
+{
+ public class ShapeProcessTest
+ {
+ [Fact]
+ public void Rectangle_Calculation_IsCorrect()
+ {
+ var processor = new SampleApplication.ShapeProcessor();
+
+ var rectangle = new ShapeInput
+ {
+ Id = 1,
+ Type = ShapeType.Rectangle,
+ Dimensions = new[] { 10.0, 5.0 }
+ };
+
+ processor.AddShapes(new[] { rectangle });
+
+ var (area, perimeter) = processor.GetResult(rectangle.Id);
+
+ Assert.Equal(50.0, area, 2);
+ Assert.Equal(30.0, perimeter, 2);
+ }
+
+ [Fact]
+ public void Circle_Calculation_IsCorrect()
+ {
+ var processor = new SampleApplication.ShapeProcessor();
+
+ var circle = new ShapeInput
+ {
+ Id = 1,
+ Type = ShapeType.Circle,
+ Dimensions = new[] { 7.0 }
+ };
+
+ processor.AddShapes(new[] { circle });
+
+ var (area, perimeter) = processor.GetResult(circle.Id);
+
+ Assert.Equal(153.94, area, 2);
+ Assert.Equal(43.98, perimeter, 2);
+ }
+
+ [Fact]
+ public void Triangle_Calculation_IsCorrect()
+ {
+ var processor = new SampleApplication.ShapeProcessor();
+
+ var triangle = new ShapeInput
+ {
+ Id = 1,
+ Type = ShapeType.Triangle,
+ Dimensions = new[] { 3.0, 4.0 }
+ };
+
+ processor.AddShapes(new[] { triangle });
+
+ var (area, perimeter) = processor.GetResult(triangle.Id);
+
+ Assert.Equal(6.00, area, 2);
+ Assert.Equal(12.00, perimeter, 2);
+ }
+
+ [Fact]
+ public void Invalid_Dimensions_Throws()
+ {
+ var processor = new SampleApplication.ShapeProcessor();
+
+ Assert.Throws(() =>
+ processor.AddShapes(new[]
+ {
+ new ShapeInput
+ {
+ Id = 1,
+ Type = ShapeType.Rectangle,
+ Dimensions = new[] { -1.0, 2.0 }
+ }
+ })
+ );
+ }
+
+ [Fact]
+ public void Statistics_AreCorrect()
+ {
+ var processor = new SampleApplication.ShapeProcessor();
+ var shapes = new[]
+ {
+ new ShapeInput { Id = 1, Type = ShapeType.Rectangle, Dimensions = new[] { 2.0, 3.0 } },
+ new ShapeInput { Id = 2, Type = ShapeType.Circle, Dimensions = new[] { 3.0 } }
+ };
+ processor.AddShapes(shapes);
+ var (totalShapes, totalArea, maxPerimeterId) = processor.GetStatistics();
+ Assert.Equal(2, totalShapes);
+ Assert.Equal(34.27,totalArea, 2);
+ Assert.Equal(2, maxPerimeterId);
+ }
+ }
+}
diff --git a/SampleApplication.slnx b/SampleApplication.slnx
new file mode 100644
index 0000000..1fdad95
--- /dev/null
+++ b/SampleApplication.slnx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/SampleApplication/Controllers/ShapeController.cs b/SampleApplication/Controllers/ShapeController.cs
new file mode 100644
index 0000000..27b5f30
--- /dev/null
+++ b/SampleApplication/Controllers/ShapeController.cs
@@ -0,0 +1,77 @@
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using SampleApplication.Entities;
+
+namespace SampleApplication.Controllers
+{
+ [Route("api/shapes")]
+ public class ShapesController : ControllerBase
+ {
+
+ private readonly SampleDbContext _db;
+
+ private readonly ShapeProcessor _calculator;
+
+ public ShapesController(
+
+ SampleDbContext db,
+
+ ShapeProcessor calculator)
+
+ {
+
+ _db = db;
+
+ _calculator = calculator;
+
+ }
+
+ [HttpPost]
+
+ public IActionResult AddShapes(List shapes)
+
+ {
+
+ // TODO:
+
+ // - Validate
+
+ // - Ignore duplicate IDs
+
+ // - Save via EF Core
+
+ this._calculator.AddShapes(shapes);
+
+ this._db.Shapes.AddRange(shapes);
+
+ return Ok(shapes);
+ }
+
+ [HttpGet("{id}")]
+
+ public async Task Get(int id)
+
+ {
+
+ // TODO:
+
+ var shape = await this._db.Shapes.Where(s => s.Id == id).FirstOrDefaultAsync();
+
+ return Ok(shape);
+ }
+
+ [HttpGet("stats")]
+
+ public IActionResult Stats()
+
+ {
+
+ // TODO:
+
+ throw new NotImplementedException();
+
+ }
+
+ }
+}
diff --git a/SampleApplication/Entities/ShapeInput.cs b/SampleApplication/Entities/ShapeInput.cs
new file mode 100644
index 0000000..a16e93a
--- /dev/null
+++ b/SampleApplication/Entities/ShapeInput.cs
@@ -0,0 +1,13 @@
+using Microsoft.OpenApi;
+
+namespace SampleApplication.Entities
+{
+ public class ShapeInput
+ {
+ public int Id { get; set; }
+
+ public ShapeType Type { get; set; }
+
+ public double[] Dimensions { get; set; }
+ }
+}
diff --git a/SampleApplication/Migrations/20260119092501_InitialCreate.Designer.cs b/SampleApplication/Migrations/20260119092501_InitialCreate.Designer.cs
new file mode 100644
index 0000000..445c38b
--- /dev/null
+++ b/SampleApplication/Migrations/20260119092501_InitialCreate.Designer.cs
@@ -0,0 +1,49 @@
+//
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using SampleApplication;
+
+#nullable disable
+
+namespace SampleApplication.Migrations
+{
+ [DbContext(typeof(SampleDbContext))]
+ [Migration("20260119092501_InitialCreate")]
+ partial class InitialCreate
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.2")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("SampleApplication.Entities.ShapeInput", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.PrimitiveCollection("Dimensions")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Type")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("Shapes");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/SampleApplication/Migrations/20260119092501_InitialCreate.cs b/SampleApplication/Migrations/20260119092501_InitialCreate.cs
new file mode 100644
index 0000000..6c6a61f
--- /dev/null
+++ b/SampleApplication/Migrations/20260119092501_InitialCreate.cs
@@ -0,0 +1,35 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace SampleApplication.Migrations
+{
+ ///
+ public partial class InitialCreate : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Shapes",
+ columns: table => new
+ {
+ Id = table.Column(type: "int", nullable: false)
+ .Annotation("SqlServer:Identity", "1, 1"),
+ Type = table.Column(type: "int", nullable: false),
+ Dimensions = table.Column(type: "nvarchar(max)", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Shapes", x => x.Id);
+ });
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Shapes");
+ }
+ }
+}
diff --git a/SampleApplication/Migrations/SampleDbContextModelSnapshot.cs b/SampleApplication/Migrations/SampleDbContextModelSnapshot.cs
new file mode 100644
index 0000000..96923b0
--- /dev/null
+++ b/SampleApplication/Migrations/SampleDbContextModelSnapshot.cs
@@ -0,0 +1,46 @@
+//
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using SampleApplication;
+
+#nullable disable
+
+namespace SampleApplication.Migrations
+{
+ [DbContext(typeof(SampleDbContext))]
+ partial class SampleDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.2")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("SampleApplication.Entities.ShapeInput", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.PrimitiveCollection("Dimensions")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Type")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("Shapes");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/SampleApplication/Program.cs b/SampleApplication/Program.cs
new file mode 100644
index 0000000..30bc4f6
--- /dev/null
+++ b/SampleApplication/Program.cs
@@ -0,0 +1,32 @@
+using Microsoft.EntityFrameworkCore;
+using SampleApplication;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.AddDbContext(options =>
+ options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
+
+builder.Services.AddSingleton();
+
+builder.Services.AddControllers();
+// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
+builder.Services.AddOpenApi();
+
+var app = builder.Build();
+
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ app.MapOpenApi();
+}
+
+app.UseHttpsRedirection();
+
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
diff --git a/SampleApplication/Properties/launchSettings.json b/SampleApplication/Properties/launchSettings.json
new file mode 100644
index 0000000..eba8d85
--- /dev/null
+++ b/SampleApplication/Properties/launchSettings.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5078",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "https://localhost:7203;http://localhost:5078",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/SampleApplication/SampleApplication.csproj b/SampleApplication/SampleApplication.csproj
new file mode 100644
index 0000000..38cf46d
--- /dev/null
+++ b/SampleApplication/SampleApplication.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
diff --git a/SampleApplication/SampleApplication.http b/SampleApplication/SampleApplication.http
new file mode 100644
index 0000000..619cd2f
--- /dev/null
+++ b/SampleApplication/SampleApplication.http
@@ -0,0 +1,6 @@
+@SampleApplication_HostAddress = http://localhost:5078
+
+GET {{SampleApplication_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/SampleApplication/SampleDbContext.cs b/SampleApplication/SampleDbContext.cs
new file mode 100644
index 0000000..0c83aa0
--- /dev/null
+++ b/SampleApplication/SampleDbContext.cs
@@ -0,0 +1,13 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace SampleApplication
+{
+ public class SampleDbContext : DbContext
+ {
+ public SampleDbContext(DbContextOptions options) : base(options)
+ {
+ }
+
+ public DbSet Shapes { get; set; }
+ }
+}
diff --git a/SampleApplication/ShapeProcessor.cs b/SampleApplication/ShapeProcessor.cs
new file mode 100644
index 0000000..632c6fe
--- /dev/null
+++ b/SampleApplication/ShapeProcessor.cs
@@ -0,0 +1,102 @@
+using SampleApplication.Entities;
+using System.Net.Http.Headers;
+using System.Reflection.Metadata.Ecma335;
+
+namespace SampleApplication
+{
+ public class ShapeProcessor
+ {
+ public List shapes = new List();
+ public void AddShapes(IEnumerable shapes)
+ {
+ // TODO:
+ // - Ignore duplicate Ids
+ // - Validate dimensions
+ // - Store shapes in memory
+
+ foreach(var shape in shapes)
+ {
+ if (!ValidateDimensions(shape))
+ {
+ throw new ArgumentException("Invalid shape dimensions");
+ }
+
+ this.shapes.Add(shape);
+ }
+ }
+
+ public (double Area, double Perimeter) GetResult(int id)
+ {
+ // TODO:
+ // - Throw KeyNotFoundException if not found
+ // - Compute area & perimeter
+
+ if(!shapes.Any(s => s.Id == id))
+ {
+ throw new KeyNotFoundException("Shape not found");
+ }
+
+ var shape = shapes.First(s => s.Id == id);
+
+ return this.CalculateAreaPerimeter(shape);
+ }
+
+ public (int TotalShapes, double TotalArea, int MaxPerimeterId) GetStatistics()
+ {
+ // TODO:
+
+ var totalShapes = shapes.Count;
+
+ double totalArea = shapes.Sum(s => this.CalculateAreaPerimeter(s).area);
+
+ var maxPerimeterShape = shapes.OrderByDescending(s => this.CalculateAreaPerimeter(s).perimeter).FirstOrDefault();
+
+ return (totalShapes, totalArea, maxPerimeterShape != null ? maxPerimeterShape.Id : 0);
+ }
+
+ private bool ValidateDimensions(ShapeInput shape)
+ {
+ return shape.Type switch
+ {
+ ShapeType.Circle => shape.Dimensions.Length == 1 && shape.Dimensions[0] > 0,
+ ShapeType.Rectangle => shape.Dimensions.Length == 2 && shape.Dimensions.All(d => d > 0),
+ ShapeType.Triangle => shape.Dimensions.Length == 2,
+ _ => false,
+ };
+ }
+
+ private (double area, double perimeter) CalculateAreaPerimeter(ShapeInput shape)
+ {
+ var perimeter = shape.Type switch
+ {
+ ShapeType.Circle =>
+ 2 * Math.PI * shape.Dimensions[0],
+ ShapeType.Rectangle =>
+ 2 * (shape.Dimensions[0] + shape.Dimensions[1]),
+ ShapeType.Triangle => this.CalculateTrianglePerimeter(shape),
+ _ => throw new NotSupportedException("Shape type not supported"),
+ };
+
+ var area = shape.Type switch
+ {
+ ShapeType.Circle =>
+ Math.PI * shape.Dimensions[0] * shape.Dimensions[0],
+ ShapeType.Rectangle =>
+ shape.Dimensions[0] * shape.Dimensions[1],
+ ShapeType.Triangle =>
+ 0.5 * shape.Dimensions[0] * shape.Dimensions[1],
+ _ => throw new NotSupportedException("Shape type not supported"),
+ };
+
+ return (area, perimeter);
+ }
+
+ private double CalculateTrianglePerimeter(ShapeInput shape)
+ {
+ double @base = shape.Dimensions[0];
+ double height = shape.Dimensions[1];
+ double hypotenuse = Math.Sqrt(@base * @base + height * height);
+ return @base + height + hypotenuse;
+ }
+ }
+}
diff --git a/SampleApplication/ShapeType.cs b/SampleApplication/ShapeType.cs
new file mode 100644
index 0000000..837c562
--- /dev/null
+++ b/SampleApplication/ShapeType.cs
@@ -0,0 +1,9 @@
+namespace SampleApplication
+{
+ public enum ShapeType
+ {
+ Rectangle,
+ Circle,
+ Triangle
+ }
+}
diff --git a/SampleApplication/appsettings.Development.json b/SampleApplication/appsettings.Development.json
new file mode 100644
index 0000000..393efbb
--- /dev/null
+++ b/SampleApplication/appsettings.Development.json
@@ -0,0 +1,11 @@
+{
+ "ConnectionStrings": {
+ "DefaultConnection": "Server=KBR\\SQLSERVER2022;Database=SampleAppDb;User Id=sa;Password=pass@1234;TrustServerCertificate=True"
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/SampleApplication/appsettings.json b/SampleApplication/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/SampleApplication/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}