diff --git a/.build-obj/Debug/net8.0/Fitness_core_BE.GlobalUsings.g.cs b/.build-obj/Debug/net8.0/Fitness_core_BE.GlobalUsings.g.cs
new file mode 100644
index 0000000..5e6145d
--- /dev/null
+++ b/.build-obj/Debug/net8.0/Fitness_core_BE.GlobalUsings.g.cs
@@ -0,0 +1,17 @@
+//
+global using Microsoft.AspNetCore.Builder;
+global using Microsoft.AspNetCore.Hosting;
+global using Microsoft.AspNetCore.Http;
+global using Microsoft.AspNetCore.Routing;
+global using Microsoft.Extensions.Configuration;
+global using Microsoft.Extensions.DependencyInjection;
+global using Microsoft.Extensions.Hosting;
+global using Microsoft.Extensions.Logging;
+global using System;
+global using System.Collections.Generic;
+global using System.IO;
+global using System.Linq;
+global using System.Net.Http;
+global using System.Net.Http.Json;
+global using System.Threading;
+global using System.Threading.Tasks;
diff --git a/.build-obj/Debug/net8.0/Fitness_core_BE.csproj.FileListAbsolute.txt b/.build-obj/Debug/net8.0/Fitness_core_BE.csproj.FileListAbsolute.txt
new file mode 100644
index 0000000..e69de29
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d0d4dc3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,36 @@
+# Build artifacts
+bin/
+obj/
+
+# Visual Studio
+.vs/
+*.user
+*.suo
+*.userosscache
+*.sln.docstates
+
+# Logs
+*.log
+
+# zips
+*.zip
+
+publish/
+
+# Entity Framework migrations are normally kept local in this repository.
+# Track the unified access migration and snapshot because the application
+# requires them to upgrade existing client installations safely.
+Migrations/*
+!Migrations/20260727103618_UnifiedAccessProfiles.cs
+!Migrations/20260727103618_UnifiedAccessProfiles.Designer.cs
+!Migrations/20260727104258_AttendanceDeviceScope.cs
+!Migrations/20260727104258_AttendanceDeviceScope.Designer.cs
+!Migrations/20260727115125_AttendanceSourceEventIdempotency.cs
+!Migrations/20260727115125_AttendanceSourceEventIdempotency.Designer.cs
+!Migrations/20260727120451_DeviceReportedPresence.cs
+!Migrations/20260727120451_DeviceReportedPresence.Designer.cs
+!Migrations/20260728064848_GatewayOnboardingSelection.cs
+!Migrations/20260728064848_GatewayOnboardingSelection.Designer.cs
+!Migrations/AppDBContextModelSnapshot.cs
+!Migrations/20260729081539_PersistGymCoordinates.cs
+!Migrations/20260729081539_PersistGymCoordinates.Designer.cs
diff --git a/.postman/config.json b/.postman/config.json
new file mode 100644
index 0000000..9ff04f4
--- /dev/null
+++ b/.postman/config.json
@@ -0,0 +1,17 @@
+{
+ "workspace": {
+ "id": "0d87f5dc-8434-403b-88bc-6d05f4639c4c"
+ },
+ "entities": {
+ "environments": [],
+ "flows": [],
+ "globals": [],
+ "mocks": [],
+ "specs": [
+ "..\\obj\\Debug\\net8.0\\EndpointInfo\\Fitness-core-BE.json"
+ ],
+ "collections": [
+ "..\\Fitness_Core_API.postman_collection.json"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/AI/GeminiClient.cs b/AI/GeminiClient.cs
new file mode 100644
index 0000000..fb90c25
--- /dev/null
+++ b/AI/GeminiClient.cs
@@ -0,0 +1,68 @@
+using System.Text.Json;
+
+namespace Fitness_core_BE.AI
+{
+ public class GeminiClient : IAiClient
+ {
+ private readonly HttpClient _http;
+ private readonly IConfiguration _config;
+
+ public GeminiClient(HttpClient http, IConfiguration configuration)
+ {
+ _http = http;
+ _config = configuration;
+ }
+
+ public async Task GenerateJsonAsync( string systemPrompt, object payload)
+ {
+ string apiKey = _config["Gemini:ApiKey"];
+
+ if (string.IsNullOrEmpty(apiKey))
+ throw new Exception("Gemini API key is not configured.");
+
+ var requestBody = new
+ {
+ contents = new[]
+ {
+ new
+ {
+ parts = new[]
+ {
+ new { text = systemPrompt },
+ new { text = JsonSerializer.Serialize(payload) }
+ }
+ }
+ },
+ generationConfig = new
+ {
+ temperature = 0.4,
+ maxOutputTokens = 10000
+ }
+ };
+
+ var response = await _http.PostAsJsonAsync(
+ $"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={apiKey}",
+ requestBody
+ );
+
+ if (!response.IsSuccessStatusCode)
+ {
+ var error = await response.Content.ReadAsStringAsync();
+ throw new Exception($"Gemini API Error: {error}");
+ }
+
+ var jsonString = await response.Content.ReadAsStringAsync();
+
+ using var doc = JsonDocument.Parse(jsonString);
+
+ var text = doc.RootElement
+ .GetProperty("candidates")[0]
+ .GetProperty("content")
+ .GetProperty("parts")[0]
+ .GetProperty("text")
+ .GetString();
+
+ return text ?? throw new Exception("No response text from Gemini.");
+ }
+ }
+}
diff --git a/AI/IAiClient.cs b/AI/IAiClient.cs
new file mode 100644
index 0000000..26e3d45
--- /dev/null
+++ b/AI/IAiClient.cs
@@ -0,0 +1,7 @@
+namespace Fitness_core_BE.AI
+{
+ public interface IAiClient
+ {
+ Task GenerateJsonAsync(string systemPrompt, object payload);
+ }
+}
diff --git a/AI/MealPlanOrchestrator.cs b/AI/MealPlanOrchestrator.cs
new file mode 100644
index 0000000..c3a728c
--- /dev/null
+++ b/AI/MealPlanOrchestrator.cs
@@ -0,0 +1,213 @@
+using Fitness_core_BE.Helpers;
+using System.Text.Json;
+
+namespace Fitness_core_BE.AI
+{
+ public class MealPlanOrchestrator
+ {
+ private readonly IAiClient _aiClient;
+ private readonly ModificationGuard _guard;
+
+ public MealPlanOrchestrator( IAiClient aiClient, ModificationGuard guard)
+ {
+ _aiClient = aiClient;
+ _guard = guard;
+ }
+
+ public async Task