diff --git a/MeshWeaver.slnx b/MeshWeaver.slnx index 39c9077646..102a42525b 100644 --- a/MeshWeaver.slnx +++ b/MeshWeaver.slnx @@ -13,16 +13,6 @@ - - - - - - - - - - @@ -65,20 +55,12 @@ - - - - - - - - @@ -93,7 +75,6 @@ - @@ -130,11 +111,9 @@ - - @@ -146,7 +125,6 @@ - diff --git a/memex/Memex.Portal.Monolith/Memex.Portal.Monolith.csproj b/memex/Memex.Portal.Monolith/Memex.Portal.Monolith.csproj index 9fd931adeb..8709689e1e 100644 --- a/memex/Memex.Portal.Monolith/Memex.Portal.Monolith.csproj +++ b/memex/Memex.Portal.Monolith/Memex.Portal.Monolith.csproj @@ -35,7 +35,6 @@ references no sample. --> - diff --git a/memex/Memex.Portal.Monolith/appsettings.json b/memex/Memex.Portal.Monolith/appsettings.json index 60464fe594..4f1a3a1e0a 100644 --- a/memex/Memex.Portal.Monolith/appsettings.json +++ b/memex/Memex.Portal.Monolith/appsettings.json @@ -23,12 +23,10 @@ "Assemblies": [ // 🚨 FIRST: the in-mesh compile reference set is composed from the installed modules in // list order, so a module whose content compiles against MeshWeaver.Import must follow it. - "MeshWeaver.Import.dll", "MeshWeaver.Blazor.Radzen.dll", "MeshWeaver.Blazor.Analysis.dll", "MeshWeaver.Blazor.GoogleMaps.dll", "MeshWeaver.Speech.dll", - "MeshWeaver.OgCard.dll", "MeshWeaver.Social.dll", "MeshWeaver.Hosting.Grpc.dll" ] diff --git a/memex/MeshModulesPublish.targets b/memex/MeshModulesPublish.targets index 581b8d1fac..03fa6327c3 100644 --- a/memex/MeshModulesPublish.targets +++ b/memex/MeshModulesPublish.targets @@ -71,7 +71,6 @@ - - - - - - - - - - acc_{acceptanceId}"); - } - - private static void RenderSection(StringBuilder sb, ReinsuranceSection section, string acceptanceId) - { - string sectionId = SanitizeId(section.Id); - string sanitizedAcceptanceId = SanitizeId(acceptanceId); - - // Build section content - var sectionContent = new StringBuilder(); - sectionContent.Append($"{section.Name ?? section.LineOfBusiness ?? section.Id}
"); - - if (!string.IsNullOrEmpty(section.LineOfBusiness) && section.LineOfBusiness != section.Name) - { - sectionContent.Append($"LoB: {section.LineOfBusiness}
"); - } - - sectionContent.Append($"Attach: {section.Attach:N0}
"); - sectionContent.Append($"Limit: {section.Limit:N0}
"); - - if (section.AggAttach.HasValue && section.AggAttach.Value > 0) - { - sectionContent.Append($"AAD: {section.AggAttach.Value:N0}
"); - } - - if (section.AggLimit.HasValue && section.AggLimit.Value > 0) - { - sectionContent.Append($"AAL: {section.AggLimit.Value:N0}"); - } - - // Create node - sb.AppendLine($" sec_{sectionId}[\"{sectionContent}\"]"); - sb.AppendLine($" style sec_{sectionId} fill:{SectionColor},color:{SectionTextColor},stroke:#333,stroke-width:1px"); - sb.AppendLine($" class sec_{sectionId} leftAlign"); - sb.AppendLine($" acc_{sanitizedAcceptanceId} --> sec_{sectionId}"); - } - - private static string SanitizeId(string id) - { - // Replace characters that might cause issues in Mermaid IDs - return id.Replace("-", "_").Replace(" ", "_").Replace(".", "_"); - } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/LayoutAreas/RiskMapLayoutArea.cs b/samples/Insurance/MeshWeaver.Insurance.Domain/LayoutAreas/RiskMapLayoutArea.cs deleted file mode 100644 index 9fe533e6a0..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/LayoutAreas/RiskMapLayoutArea.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System.Reactive.Linq; -using System.Reactive.Subjects; -using MeshWeaver.Data; -using MeshWeaver.Maps; -using MeshWeaver.Insurance.Domain.LayoutAreas.Shared; -using MeshWeaver.Insurance.Domain.Services; -using MeshWeaver.Layout; -using MeshWeaver.Layout.Composition; -using MeshWeaver.Messaging; -using Microsoft.Extensions.DependencyInjection; - -namespace MeshWeaver.Insurance.Domain.LayoutAreas; - -/// -/// Layout area for displaying a map of property risks. -/// -public static class RiskMapLayoutArea -{ - /// - /// Renders a map view of property risks for a specific pricing. - /// - public static IObservable RiskMap(LayoutAreaHost host, RenderingContext _) - { - var pricingId = host.Hub.Address.Id; - - return host.Workspace.GetStream()! - .SelectMany(risks => - { - var riskList = risks?.ToList() ?? new List(); - var geocodedRisks = riskList.Where(r => r.GeocodedLocation?.Latitude != null && r.GeocodedLocation?.Longitude != null).ToList(); - - if (!riskList.Any()) - { - return Observable.Return(Controls.Stack - .WithView(PricingLayoutShared.BuildToolbar(pricingId, "RiskMap")) - .WithView(Controls.Markdown("# Risk Map\n\n*No risks loaded. Import or add risks to begin.*"))); - } - - if (!geocodedRisks.Any()) - { - return Observable.Return(Controls.Stack - .WithView(PricingLayoutShared.BuildToolbar(pricingId, "RiskMap")) - .WithView(Controls.Markdown($"# Risk Map\n\n*No geocoded risks found. {riskList.Count} risk(s) available but none have valid coordinates.*")) - .WithView(GeocodingArea)); - } - - var mapControl = BuildMapControl(geocodedRisks); - - return Observable.Using( - () => new ReplaySubject(1), - riskDetailsSubject => - { - riskDetailsSubject.OnNext(null); - var mapControlWithClick = mapControl.WithClickAction(ctx => riskDetailsSubject.OnNext(ctx.Payload?.ToString())); - - return Observable.Return( - Controls.Stack - .WithView(PricingLayoutShared.BuildToolbar(pricingId, "RiskMap")) - .WithView(Controls.Title("Risk Map", 2)) - .WithView(mapControlWithClick) - .WithView(GeocodingArea) - .WithView(Controls.Title("Risk Details", 3)) - .WithView((h, c) => riskDetailsSubject - .SelectMany(id => id == null ? - Observable.Return(Controls.Html("Click marker to see details.")) : RenderRiskDetails(host.Hub, id)) - ) - ); - } - ); - }); - } - - private static IObservable GeocodingArea(LayoutAreaHost host, RenderingContext ctx) - { - var svc = host.Hub.ServiceProvider.GetRequiredService(); - return svc.Progress.Select(p => p is null - ? (UiControl)Controls.Button("Geocode").WithClickAction(ClickGeocoding) - : Controls.Progress($"Processing {p.CurrentRiskName}: {p.ProcessedRisks} of {p.TotalRisks}", - p.TotalRisks == 0 ? 0 : (int)(100.0 * p.ProcessedRisks / p.TotalRisks))); - } - - private static Task ClickGeocoding(UiActionContext obj) - { - // Show initial progress - obj.Host.UpdateArea(obj.Area, Controls.Progress("Starting geocoding...", 0)); - - // Reactive — Subscribe instead of await; click handler stays sync. - obj.Host.Hub.Observe(new GeocodingRequest(), o => o.WithTarget(obj.Hub.Address)) - .Subscribe( - response => - { - var resultMessage = response.Message?.Success == true - ? $"✅ Geocoding Complete: {response.Message.GeocodedCount} locations geocoded successfully." - : $"❌ Geocoding Failed: {response.Message?.Error}"; - obj.Host.UpdateArea(obj.Area, Controls.Markdown($"**{resultMessage}**")); - }, - ex => - { - obj.Host.UpdateArea(obj.Area, Controls.Markdown($"**Geocoding Failed**: {ex.Message}")); - }); - return Task.CompletedTask; - } - - private static IObservable RenderRiskDetails(IMessageHub hub, string id) - { - return hub.GetWorkspace() - .GetStream(new EntityReference(nameof(PropertyRisk), id))! - .Select(r => BuildRiskDetailsMarkdown(r.Value as PropertyRisk)); - } - - private static UiControl BuildRiskDetailsMarkdown(PropertyRisk? risk) - { - if (risk is null) - return Controls.Html("Risk not found"); - - return Controls.Stack - .WithView(Controls.Markdown("## Risk Details")) - .WithView(Controls.Markdown($"**ID:** {risk.Id}")) - .WithView(Controls.Markdown($"**Location:** {risk.LocationName ?? "N/A"}")) - .WithView(Controls.Markdown($"**Address:** {risk.GeocodedLocation?.FormattedAddress ?? risk.Address ?? "N/A"}")) - .WithView(Controls.Markdown($"**City:** {risk.City ?? "N/A"}")) - .WithView(Controls.Markdown($"**State:** {risk.State ?? "N/A"}")) - .WithView(Controls.Markdown($"**Country:** {risk.Country ?? "N/A"}")) - .WithView(Controls.Markdown($"**Currency:** {risk.Currency ?? "N/A"}")) - .WithView(Controls.Markdown($"**TSI Building:** {risk.TsiBuilding:N0}")) - .WithView(Controls.Markdown($"**TSI Content:** {risk.TsiContent:N0}")) - .WithView(Controls.Markdown($"**TSI BI:** {risk.TsiBi:N0}")) - .WithView(Controls.Markdown($"**Latitude:** {risk.GeocodedLocation?.Latitude:F6}")) - .WithView(Controls.Markdown($"**Longitude:** {risk.GeocodedLocation?.Longitude:F6}")); - } - - private static MapControl BuildMapControl(IReadOnlyCollection risks) - { - var riskList = risks.Where(r => r.GeocodedLocation?.Latitude is not null && r.GeocodedLocation?.Longitude is not null).ToList(); - - // Find center point - LatLng center; - if (riskList.Any()) - { - var avgLat = riskList.Average(r => r.GeocodedLocation!.Latitude!.Value); - var avgLng = riskList.Average(r => r.GeocodedLocation!.Longitude!.Value); - center = new LatLng(avgLat, avgLng); - } - else - { - center = new LatLng(0, 0); - } - - // Create markers - var markers = riskList.Select(r => new MapMarker - { - Position = new LatLng(r.GeocodedLocation!.Latitude!.Value, r.GeocodedLocation.Longitude!.Value), - Title = ((r.LocationName ?? r.Address) + " " + (r.City ?? "")).Trim(), - Id = r.Id, - Data = r - }).ToList(); - - var mapOptions = new MapOptions - { - Center = center, - Zoom = riskList.Any() ? 6 : 2, - MapTypeId = "roadmap", - ZoomControl = true, - MapTypeControl = true, - StreetViewControl = false, - FullscreenControl = true - }; - - return new MapControl() - { - Options = mapOptions, - Markers = markers, - Id = "risk-map" - }.WithStyle(style => style.WithHeight("500px").WithWidth("80%")); - } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/LayoutAreas/Shared/PricingLayoutShared.cs b/samples/Insurance/MeshWeaver.Insurance.Domain/LayoutAreas/Shared/PricingLayoutShared.cs deleted file mode 100644 index f00adbec96..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/LayoutAreas/Shared/PricingLayoutShared.cs +++ /dev/null @@ -1,27 +0,0 @@ -using MeshWeaver.Layout; - -namespace MeshWeaver.Insurance.Domain.LayoutAreas.Shared; - -public static class PricingLayoutShared -{ - public static UiControl BuildToolbar(string pricingId, string active) - { - string Item(string key, string icon, string text) - { - var href = $"/pricing/{pricingId}/{key}"; - var activeClass = key == active ? "font-weight:600;text-decoration:underline;" : "opacity:.75;"; - return $"{icon}{text}"; - } - - var html = $@""; - - return Controls.Html(html); - } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/LayoutAreas/SubmissionLayoutArea.cs b/samples/Insurance/MeshWeaver.Insurance.Domain/LayoutAreas/SubmissionLayoutArea.cs deleted file mode 100644 index 8e2fcfbe4b..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/LayoutAreas/SubmissionLayoutArea.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Reactive.Linq; -using MeshWeaver.ContentCollections; -using MeshWeaver.Insurance.Domain.LayoutAreas.Shared; -using MeshWeaver.Layout; -using MeshWeaver.Layout.Composition; -using Microsoft.Extensions.DependencyInjection; -using static MeshWeaver.ContentCollections.ContentCollectionsExtensions; - -namespace MeshWeaver.Insurance.Domain.LayoutAreas; - -/// -/// Layout area for displaying submission details for a pricing. -/// Uses the generic Collection layout area with area/'Collection', Id:'{collection}/{path}' -/// -public static class SubmissionLayoutArea -{ - /// - /// Renders the submission details for a specific pricing using the generic Collection layout area. - /// - public static IObservable Submission(LayoutAreaHost host, RenderingContext _) - { - var pricingId = host.Hub.Address.Id; - - return host.Workspace.GetStream()! - .Select(pricings => - { - var pricing = pricings?.FirstOrDefault(); - if (pricing != null) - { - // Get the collection configuration, creating a localized version if needed - var localizedCollectionName = GetLocalizedCollectionName("Submissions", pricingId); - var contentService = host.Hub.ServiceProvider.GetRequiredService(); - - // Get the collection configuration - var collectionConfig = contentService.GetCollectionConfig(localizedCollectionName); - - var fileBrowser = new FileBrowserControl(localizedCollectionName); - if (collectionConfig != null) - fileBrowser = fileBrowser - .WithCollectionConfiguration(collectionConfig) - .CreatePath(); - - return Controls.Stack - .WithView(PricingLayoutShared.BuildToolbar(pricingId, "Submission")) - .WithView(Controls.Title($"Submission - {pricing.InsuredName}", 1)) - .WithView(fileBrowser); - } - return Controls.Stack - .WithView(PricingLayoutShared.BuildToolbar(pricingId, "Submission")) - .WithView(Controls.Markdown($"# Submission\n\n*Pricing '{pricingId}' not found.*")); - }); - } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/MeshWeaver.Insurance.Domain.csproj b/samples/Insurance/MeshWeaver.Insurance.Domain/MeshWeaver.Insurance.Domain.csproj deleted file mode 100644 index 1405be8d78..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/MeshWeaver.Insurance.Domain.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - net10.0 - enable - enable - true - $(NoWarn);CS1591 - - - - - - - - - - - - - - - - diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/Pricing.cs b/samples/Insurance/MeshWeaver.Insurance.Domain/Pricing.cs deleted file mode 100644 index cb7a06380f..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/Pricing.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using MeshWeaver.Domain; - -namespace MeshWeaver.Insurance.Domain; - -/// -/// Represents an insurance pricing entity with dimension-based classification. -/// -[Display(GroupName = "Structure")] -public record Pricing -{ - /// - /// Unique identifier for the pricing record. - /// - [Key] - public required string Id { get; init; } - - /// - /// The date when the insurance coverage begins. - /// - public DateTime? InceptionDate { get; init; } - - /// - /// The date when the insurance coverage ends. - /// - public DateTime? ExpirationDate { get; init; } - - /// - /// The underwriting year for this pricing. - /// - public int? UnderwritingYear { get; init; } - - /// - /// Line of business classification (dimension). - /// - [Dimension] - public string? LineOfBusiness { get; init; } - - /// - /// Country code or name (dimension). - /// - [Dimension] - public string? Country { get; init; } - - /// - /// Name of the insured party. - /// - public string? InsuredName { get; init; } - - /// - /// Name of the broker handling this pricing. - /// - public string? BrokerName { get; init; } - - /// - /// Name of the primary insurance company. - /// - public string? PrimaryInsurance { get; init; } - - /// - /// Currency code for the premium. - /// - public string? Currency { get; init; } - - /// - /// Current status of the pricing (e.g., Draft, Quoted, Bound). - /// - public string? Status { get; init; } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/PropertyRisk.cs b/samples/Insurance/MeshWeaver.Insurance.Domain/PropertyRisk.cs deleted file mode 100644 index d47e9884d2..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/PropertyRisk.cs +++ /dev/null @@ -1,201 +0,0 @@ -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using MeshWeaver.Domain; - -namespace MeshWeaver.Insurance.Domain; - -/// -/// Represents a property risk within an insurance pricing. -/// Contains location details, values, and dimensions for property insurance underwriting. -/// -[Display(GroupName = "Risk")] -public record PropertyRisk -{ - /// - /// Unique identifier for the property risk record. - /// Synonyms: Plant code, Plant ID, Site Code, Asset ID, Code. - /// - [Key] - public required string Id { get; init; } - - /// - /// Associated pricing header id. - /// - [Browsable(false)] - public string? PricingId { get; init; } - - /// - /// Source row number in the worksheet (1-based). - /// - public int? SourceRow { get; init; } - - /// - /// Originating file name of the import. - /// - public string? SourceFile { get; init; } - - /// - /// Resolved geocoding for the address. - /// - [Browsable(false)] - public GeocodedLocation? GeocodedLocation { get; init; } - - /// - /// Human-friendly site or facility name. - /// Synonyms: Plant Description, Site Name, Location Name. - /// - public string? LocationName { get; init; } - - /// - /// Country; typically ISO code or name (dimension). - /// Synonyms: Country Code, Country. - /// - [Dimension] - public string? Country { get; init; } - - /// - /// Street address. - /// Synonyms: Property Address, Address. - /// - public string? Address { get; init; } - - /// - /// State/region/province. - /// Synonyms: State/Province, Region. - /// - public string? State { get; init; } - - /// - /// County/district. - /// Synonyms: District, County. - /// - public string? County { get; init; } - - /// - /// Postal/ZIP code. - /// Synonyms: ZIP, Postcode. - /// - public string? ZipCode { get; init; } - - /// - /// City or town. - /// - public string? City { get; init; } - - /// - /// Base currency for the risk. - /// Synonyms: Currency, Curr., Curr, CCY. - /// - [Dimension] - public string? Currency { get; init; } - - /// - /// Sum insured for buildings. - /// Synonyms: Buildings, Building Value, TSI Building(s). - /// - public double TsiBuilding { get; init; } - - /// - /// Currency for building TSI when provided per-row. - /// - public string? TsiBuildingCurrency { get; init; } - - /// - /// Sum insured for contents. - /// Synonyms: Stock, Fixtures, Fittings, IT Equipment, Equipment. - /// - public double TsiContent { get; init; } - - /// - /// Currency for contents TSI when provided per-row. - /// - public string? TsiContentCurrency { get; init; } - - /// - /// Business Interruption TSI. - /// Synonyms: BI, Business Interruption, Gross Profit. - /// - public double TsiBi { get; init; } - - /// - /// Currency for BI TSI when provided per-row. - /// - public string? TsiBiCurrency { get; init; } - - /// - /// Account identifier. - /// Synonyms: Account #, Account No. - /// - public string? AccountNumber { get; init; } - - /// - /// Occupancy scheme / taxonomy name. - /// - public string? OccupancyScheme { get; init; } - - /// - /// Occupancy code within the scheme. - /// - public string? OccupancyCode { get; init; } - - /// - /// Construction classification scheme. - /// - public string? ConstructionScheme { get; init; } - - /// - /// Construction code within scheme. - /// - public string? ConstructionCode { get; init; } - - /// - /// Original build year (YYYY). - /// - public int? BuildYear { get; init; } - - /// - /// Major upgrade/renovation year (YYYY). - /// - public int? UpgradeYear { get; init; } - - /// - /// Number of above-ground stories/floors. - /// - public int? NumberOfStories { get; init; } - - /// - /// Automatic sprinkler protection present. - /// - public bool? Sprinklers { get; init; } -} - -/// -/// Geocoded location information for a property risk. -/// -public record GeocodedLocation -{ - /// - /// Latitude coordinate. - /// - public double? Latitude { get; init; } - - /// - /// Longitude coordinate. - /// - public double? Longitude { get; init; } - - /// - /// Formatted address from geocoding service. - /// - public string? FormattedAddress { get; init; } - - /// - /// Place ID from geocoding service. - /// - public string? PlaceId { get; init; } - - /// - /// Status of geocoding operation. - /// - public string? Status { get; init; } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/README.md b/samples/Insurance/MeshWeaver.Insurance.Domain/README.md deleted file mode 100644 index 68c5a87e8c..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# MeshWeaver.Insurance.Domain - -## Overview -MeshWeaver.Insurance.Domain defines the domain model, data configuration, layout areas, and services for a sample insurance pricing application. It demonstrates how to build a full business domain on the MeshWeaver framework with dimension-based data, reactive UI layouts, and geocoding integration. - -## Features -- Domain entities: `Pricing`, `PropertyRisk`, `ReinsuranceAcceptance`, `ReinsuranceSection` -- Reference data dimensions: `LineOfBusiness`, `Country`, `LegalEntity`, `Currency` -- Layout areas for pricing catalog, pricing overview, property risks, risk map, submissions, reinsurance structure, and import configuration -- In-memory pricing service with geocoding support (Google Maps) -- Excel import configuration for ingesting submission data -- Content collection support for file-based submission attachments - -## Usage -```csharp -hub.ConfigureInsuranceApplication(); // Root hub with catalog + dimensions -hub.ConfigureSinglePricingApplication(); // Per-pricing hub (address: pricing/company/year) -``` - -## Related Projects -- [MeshWeaver.Insurance.SampleData](../MeshWeaver.Insurance.SampleData/) -- Excel sample data generator -- [MeshWeaver.Layout](../../../src/MeshWeaver.Layout/README.md) -- Layout framework -- [MeshWeaver.Import](../../../src/MeshWeaver.Import/README.md) -- Data import diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/ReinsuranceAcceptance.cs b/samples/Insurance/MeshWeaver.Insurance.Domain/ReinsuranceAcceptance.cs deleted file mode 100644 index 7300dabf2e..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/ReinsuranceAcceptance.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace MeshWeaver.Insurance.Domain; - -/// -/// Represents the reinsurance acceptance with financial terms and coverage sections. -/// -[Display(GroupName = "Structure")] -public record ReinsuranceAcceptance -{ - /// - /// Gets or initializes the unique acceptance identifier. - /// - [Key] - public required string Id { get; init; } - - /// - /// Gets or initializes the pricingId this acceptance belongs to. - /// - public string? PricingId { get; init; } - - /// - /// Gets or initializes the acceptance name or description. - /// - public string? Name { get; init; } - - /// - /// Gets or initializes the cession percentage. - /// - public double Cession { get; init; } - - /// - /// Gets or initializes the share percentage. - /// - public double Share { get; init; } - - /// - /// Gets or initializes the collection of reinsurance sections (layers). - /// - public IReadOnlyCollection? Sections { get; init; } - - /// - /// Gets or initializes the Estimated Premium Income (EPI). - /// - public double EPI { get; init; } - - /// - /// Gets or initializes the rate. - /// - public double Rate { get; init; } - - /// - /// Gets or initializes the commission percentage. - /// - public double Commission { get; init; } - - /// - /// Gets or initializes the brokerage percentage. - /// - public double Brokerage { get; init; } - - /// - /// Gets or initializes the tax percentage. - /// - public double Tax { get; init; } - - /// - /// Gets or initializes the reinstatement premium. - /// - public double ReinstPrem { get; init; } - - /// - /// Gets or initializes the no claims bonus percentage. - /// - public double NoClaimsBonus { get; init; } - - /// - /// Gets or initializes the profit commission percentage. - /// - public double ProfitComm { get; init; } - - - /// - /// Gets or initializes the minimum and deposit premium. - /// - public double MDPrem { get; init; } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/ReinsuranceSection.cs b/samples/Insurance/MeshWeaver.Insurance.Domain/ReinsuranceSection.cs deleted file mode 100644 index 42c21572cc..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/ReinsuranceSection.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace MeshWeaver.Insurance.Domain; - -/// -/// Represents a reinsurance coverage section with layer structure and financial terms. -/// -[Display(GroupName = "Structure")] -public record ReinsuranceSection -{ - /// - /// Gets or initializes the unique section identifier. - /// - [Key] - public required string Id { get; init; } - - /// - /// Gets or initializes the acceptanceId this section belongs to. - /// - public string? AcceptanceId { get; init; } - - /// - /// Gets or initializes the section name or description. - /// - public string? Name { get; init; } - - /// - /// Gets or initializes the section type (e.g., "Fire Damage", "Natural Catastrophe", "Business Interruption"). - /// - public string? LineOfBusiness { get; init; } - - /// - /// Gets or initializes the attachment point. - /// - public decimal Attach { get; init; } - - /// - /// Gets or initializes the layer limit. - /// - public decimal Limit { get; init; } - - /// - /// Gets or initializes the aggregate attachment point (annual aggregate deductible). - /// - public decimal? AggAttach { get; init; } - - /// - /// Gets or initializes the aggregate limit (annual aggregate limit). - /// - public decimal? AggLimit { get; init; } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/SampleDataProvider.cs b/samples/Insurance/MeshWeaver.Insurance.Domain/SampleDataProvider.cs deleted file mode 100644 index ea5457fcbc..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/SampleDataProvider.cs +++ /dev/null @@ -1,282 +0,0 @@ -namespace MeshWeaver.Insurance.Domain; - -/// -/// Provides sample data for insurance dimensions and entities. -/// -public static class SampleDataProvider -{ - /// - /// Gets sample line of business records. - /// - public static IEnumerable GetLinesOfBusiness() - { - return new[] - { - new LineOfBusiness - { - Id = "PROP", - Name = "Property", - Description = "Property insurance covering buildings, contents, and business interruption" - }, - new LineOfBusiness - { - Id = "CAS", - Name = "Casualty", - Description = "Casualty insurance covering liability and workers compensation" - }, - new LineOfBusiness - { - Id = "MARINE", - Name = "Marine", - Description = "Marine and cargo insurance" - }, - new LineOfBusiness - { - Id = "AVIATION", - Name = "Aviation", - Description = "Aviation and aerospace insurance" - }, - new LineOfBusiness - { - Id = "ENERGY", - Name = "Energy", - Description = "Energy sector insurance including oil & gas" - } - }; - } - - /// - /// Gets sample country records. - /// - public static IEnumerable GetCountries() - { - return new[] - { - new Country - { - Id = "US", - Name = "United States", - Alpha3Code = "USA", - Region = "North America" - }, - new Country - { - Id = "GB", - Name = "United Kingdom", - Alpha3Code = "GBR", - Region = "Europe" - }, - new Country - { - Id = "DE", - Name = "Germany", - Alpha3Code = "DEU", - Region = "Europe" - }, - new Country - { - Id = "FR", - Name = "France", - Alpha3Code = "FRA", - Region = "Europe" - }, - new Country - { - Id = "JP", - Name = "Japan", - Alpha3Code = "JPN", - Region = "Asia" - }, - new Country - { - Id = "CN", - Name = "China", - Alpha3Code = "CHN", - Region = "Asia" - }, - new Country - { - Id = "AU", - Name = "Australia", - Alpha3Code = "AUS", - Region = "Oceania" - }, - new Country - { - Id = "CA", - Name = "Canada", - Alpha3Code = "CAN", - Region = "North America" - }, - new Country - { - Id = "CH", - Name = "Switzerland", - Alpha3Code = "CHE", - Region = "Europe" - }, - new Country - { - Id = "SG", - Name = "Singapore", - Alpha3Code = "SGP", - Region = "Asia" - } - }; - } - - /// - /// Gets sample legal entity records. - /// - public static IEnumerable GetLegalEntities() - { - return new[] - { - new LegalEntity - { - Id = "MW-US", - Name = "MeshWeaver Insurance US Inc.", - CountryOfIncorporation = "US", - EntityType = "Corporation" - }, - new LegalEntity - { - Id = "MW-UK", - Name = "MeshWeaver Insurance UK Ltd.", - CountryOfIncorporation = "GB", - EntityType = "Limited Company" - }, - new LegalEntity - { - Id = "MW-EU", - Name = "MeshWeaver Insurance Europe AG", - CountryOfIncorporation = "CH", - EntityType = "Corporation" - }, - new LegalEntity - { - Id = "MW-ASIA", - Name = "MeshWeaver Insurance Asia Pte. Ltd.", - CountryOfIncorporation = "SG", - EntityType = "Private Limited" - } - }; - } - - /// - /// Gets sample currency records. - /// - public static IEnumerable GetCurrencies() - { - return new[] - { - new Currency - { - Id = "USD", - Name = "US Dollar", - Symbol = "$", - DecimalPlaces = 2 - }, - new Currency - { - Id = "EUR", - Name = "Euro", - Symbol = "€", - DecimalPlaces = 2 - }, - new Currency - { - Id = "GBP", - Name = "British Pound", - Symbol = "£", - DecimalPlaces = 2 - }, - new Currency - { - Id = "JPY", - Name = "Japanese Yen", - Symbol = "¥", - DecimalPlaces = 0 - }, - new Currency - { - Id = "CHF", - Name = "Swiss Franc", - Symbol = "CHF", - DecimalPlaces = 2 - }, - new Currency - { - Id = "AUD", - Name = "Australian Dollar", - Symbol = "A$", - DecimalPlaces = 2 - }, - new Currency - { - Id = "CAD", - Name = "Canadian Dollar", - Symbol = "C$", - DecimalPlaces = 2 - } - }; - } - - /// - /// Gets sample pricing records for demonstration. - /// - public static IEnumerable GetSamplePricings() - { - return new[] - { - new Pricing - { - Id = "PRC-2024-001", - InsuredName = "Global Manufacturing Corp", - InceptionDate = new DateTime(2024, 1, 1), - ExpirationDate = new DateTime(2024, 12, 31), - UnderwritingYear = 2024, - LineOfBusiness = "PROP", - Country = "US", - Currency = "USD", - Status = "Bound" - }, - new Pricing - { - Id = "PRC-2024-002", - InsuredName = "European Logistics Ltd", - InceptionDate = new DateTime(2024, 3, 1), - ExpirationDate = new DateTime(2025, 2, 28), - UnderwritingYear = 2024, - LineOfBusiness = "PROP", - Country = "GB", - Currency = "GBP", - Status = "Quoted" - }, - new Pricing - { - Id = "PRC-2024-003", - InsuredName = "Tech Industries GmbH", - InceptionDate = new DateTime(2024, 6, 1), - ExpirationDate = new DateTime(2025, 5, 31), - UnderwritingYear = 2024, - LineOfBusiness = "PROP", - Country = "DE", - Currency = "EUR", - Status = "Draft" - }, - new Pricing - { - Id = "Microsoft/2026", - InsuredName = "Microsoft", - InceptionDate = new DateTime(2026, 1, 1), - ExpirationDate = new DateTime(2026, 12, 31), - UnderwritingYear = 2026, - LineOfBusiness = "PROP", - Country = "US", - Currency = "USD", - Status = "Bound" - } - }; - } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/Services/GoogleGeocodingService.cs b/samples/Insurance/MeshWeaver.Insurance.Domain/Services/GoogleGeocodingService.cs deleted file mode 100644 index 7cbdb234f3..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/Services/GoogleGeocodingService.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System.Collections.Concurrent; -using System.Net.Http.Json; -using System.Reactive.Subjects; -using MeshWeaver.Maps; -using MeshWeaver.Mesh.Threading; -using Microsoft.Extensions.Options; - -namespace MeshWeaver.Insurance.Domain.Services; - -/// -/// Google Maps-based geocoding service for property risks. -/// -public class GoogleGeocodingService( - IOptions googleConfig, - IoPoolRegistry? ioPoolRegistry = null) : IGeocodingService -{ - private readonly ReplaySubject progressSubject = InitializeProgress(); - private readonly object progressLock = new(); - private readonly HttpClient http = new(); - // The geocoding HTTP fan-out runs on the bounded Http I/O queue (pool), never inline / FromAsync. - private readonly IIoPool ioPool = ioPoolRegistry?.Get(IoPoolNames.Http) ?? IoPool.Unbounded; - - private static ReplaySubject InitializeProgress() - { - var ret = new ReplaySubject(1); - ret.OnNext(null); - return ret; - } - - public IObservable Progress => progressSubject; - - /// - /// Reactive geocoding — the HTTP fan-out runs inside the bounded Http I/O queue (pool); the - /// genuine async leaf is sealed in . Returns - /// (never Task). - /// - public IObservable GeocodeRisks(IReadOnlyCollection risks) - => ioPool.Invoke(ct => GeocodeRisksCoreAsync(risks, ct)); - - private async Task GeocodeRisksCoreAsync(IReadOnlyCollection risks, CancellationToken cancellationToken = default) - { - try - { - lock (progressLock) - { - progressSubject.OnNext(new GeocodingProgress(0, 0, null, "Starting geocoding...")); - } - - if (!risks.Any()) - { - lock (progressLock) - { - progressSubject.OnNext(new GeocodingProgress(0, 0, null, "No risks to geocode", true)); - } - return new GeocodingResponse - { - Success = true, - GeocodedCount = 0, - Error = "No risks found to geocode" - }; - } - - // Check Google Maps API key - if (string.IsNullOrEmpty(googleConfig.Value.ApiKey)) - { - var error = "Google Maps API key not configured"; - lock (progressLock) - { - progressSubject.OnNext(new GeocodingProgress(0, 0, null, "Configuration error", true)); - } - return new GeocodingResponse - { - Success = false, - GeocodedCount = 0, - Error = error - }; - } - - // Filter risks that need geocoding - var risksToGeocode = risks - .Where(r => r.GeocodedLocation?.Latitude == null || r.GeocodedLocation?.Longitude == null) - .ToList(); - - if (!risksToGeocode.Any()) - { - lock (progressLock) - { - progressSubject.OnNext(new GeocodingProgress(risks.Count, risks.Count, null, - "All risks already geocoded", true)); - } - return new GeocodingResponse - { - Success = true, - GeocodedCount = 0, - Error = null - }; - } - - lock (progressLock) - { - progressSubject.OnNext(new GeocodingProgress(risksToGeocode.Count, 0, null, "Initializing geocoding...")); - } - - var geocodedCount = 0; - var updatedRisks = new ConcurrentBag(); - var processedCount = 0; - - var parallelOptions = new ParallelOptions - { - MaxDegreeOfParallelism = Math.Min(Environment.ProcessorCount, 10), - CancellationToken = cancellationToken - }; - - await Parallel.ForEachAsync(risksToGeocode, parallelOptions, async (risk, ct) => - { - var riskName = risk.LocationName ?? risk.Address ?? $"Risk {risk.Id}"; - - try - { - var geocodedLocation = await GeocodeAsync(risk, ct); - - if (geocodedLocation.Latitude.HasValue && geocodedLocation.Longitude.HasValue) - { - // Update the risk with geocoded data - var updatedRisk = risk with { GeocodedLocation = geocodedLocation }; - updatedRisks.Add(updatedRisk); - Interlocked.Increment(ref geocodedCount); - } - else - { - // Still add the risk with the geocoding attempt result - updatedRisks.Add(risk with { GeocodedLocation = geocodedLocation }); - } - } - catch (Exception) - { - // Add the original risk unchanged - updatedRisks.Add(risk); - } - - // Update progress after processing each risk - var currentProcessed = Interlocked.Increment(ref processedCount); - lock (progressLock) - { - progressSubject.OnNext(new GeocodingProgress( - risksToGeocode.Count, - currentProcessed, - risk.Id, - $"Processing {currentProcessed}/{risksToGeocode.Count} risks..." - )); - } - }); - - // Final progress update - lock (progressLock) - { - progressSubject.OnNext(new GeocodingProgress( - risksToGeocode.Count, - risksToGeocode.Count, - null, - $"Completed processing {risksToGeocode.Count} risks", - true - )); - } - - return new GeocodingResponse - { - Success = true, - GeocodedCount = geocodedCount, - Error = null, - UpdatedRisks = updatedRisks.ToList() - }; - } - catch (Exception ex) - { - var error = $"Geocoding failed: {ex.Message}"; - return new GeocodingResponse - { - Success = false, - GeocodedCount = 0, - Error = error - }; - } - finally - { - lock (progressLock) - { - progressSubject.OnNext(null); - } - } - } - - private async Task GeocodeAsync(PropertyRisk risk, CancellationToken ct = default) - { - var query = BuildQuery(risk); - var url = $"https://maps.googleapis.com/maps/api/geocode/json?address={Uri.EscapeDataString(query)}&key={googleConfig.Value.ApiKey}"; - - var response = await http.GetFromJsonAsync(url, cancellationToken: ct); - if (response == null) - { - return new GeocodedLocation { Status = "NoResponse" }; - } - - if (response.status != "OK" || response.results == null || response.results.Length == 0) - { - return new GeocodedLocation { Status = response.status }; - } - - var r = response.results[0]; - return new GeocodedLocation - { - Latitude = r.geometry.location.lat, - Longitude = r.geometry.location.lng, - FormattedAddress = r.formatted_address, - PlaceId = r.place_id, - Status = response.status - }; - } - - private static string BuildQuery(PropertyRisk risk) - { - var parts = new[] { risk.LocationName, risk.Address, risk.City, risk.State, risk.ZipCode, risk.Country } - .Where(s => !string.IsNullOrWhiteSpace(s)); - return string.Join(", ", parts); - } - - private sealed class GoogleGeocodeResponse - { - public string status { get; set; } = string.Empty; - public GoogleGeocodeResult[]? results { get; set; } - } - - private sealed class GoogleGeocodeResult - { - public string formatted_address { get; set; } = string.Empty; - public string place_id { get; set; } = string.Empty; - public GoogleGeometry geometry { get; set; } = new(); - } - - private sealed class GoogleGeometry - { - public GoogleLocation location { get; set; } = new(); - } - - private sealed class GoogleLocation - { - public double lat { get; set; } - public double lng { get; set; } - } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/Services/IGeocodingService.cs b/samples/Insurance/MeshWeaver.Insurance.Domain/Services/IGeocodingService.cs deleted file mode 100644 index 4985e4b789..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/Services/IGeocodingService.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace MeshWeaver.Insurance.Domain.Services; - -/// -/// Service for geocoding property risks. -/// -public interface IGeocodingService -{ - /// - /// Observable stream of geocoding progress. - /// - IObservable Progress { get; } - - /// - /// Geocodes a collection of property risks. Reactive — returns - /// (never Task); the HTTP leaf runs inside the bounded Http I/O queue. - /// - IObservable GeocodeRisks(IReadOnlyCollection risks); -} - -/// -/// Progress information for geocoding operations. -/// -public record GeocodingProgress( - int TotalRisks, - int ProcessedRisks, - string? CurrentRiskId, - string CurrentRiskName, - bool IsComplete = false -); diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/Services/IPricingService.cs b/samples/Insurance/MeshWeaver.Insurance.Domain/Services/IPricingService.cs deleted file mode 100644 index c6e7eb7c97..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/Services/IPricingService.cs +++ /dev/null @@ -1,44 +0,0 @@ -using MeshWeaver.Import.Configuration; - -namespace MeshWeaver.Insurance.Domain.Services; - -/// -/// Service for loading and managing insurance pricings. -/// -public interface IPricingService -{ - /// - /// Gets all pricing headers for the catalog. - /// - IReadOnlyCollection GetCatalog(); - - /// - /// Gets a single pricing header by ID. - /// - Task GetHeaderAsync(string id); - - /// - /// Gets all property risks for a specific pricing. - /// - Task> GetRisksAsync(string pricingId, CancellationToken ct = default); - - /// - /// Gets import configurations for a specific pricing. - /// - IAsyncEnumerable GetImportConfigurationsAsync(string pricingId); - - /// - /// Updates a pricing header. - /// - void UpdatePricingHeader(Pricing pricing); - - /// - /// Updates risks for a specific pricing. - /// - void UpdateRisks(string pricingId, IEnumerable updatedRisks); - - /// - /// Upserts an import configuration. - /// - void UpsertImportConfiguration(ExcelImportConfiguration configuration); -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Domain/Services/InMemoryPricingService.cs b/samples/Insurance/MeshWeaver.Insurance.Domain/Services/InMemoryPricingService.cs deleted file mode 100644 index adb35b1dbb..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Domain/Services/InMemoryPricingService.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Collections.Concurrent; -using MeshWeaver.Import.Configuration; - -namespace MeshWeaver.Insurance.Domain.Services; - -/// -/// In-memory implementation of the pricing service. -/// -public class InMemoryPricingService : IPricingService -{ - private readonly ConcurrentDictionary _pricings = new(); - private readonly ConcurrentDictionary> _risks = new(); - private readonly ConcurrentDictionary> _importConfigs = new(); - - public InMemoryPricingService() - { - // Initialize with sample data - foreach (var pricing in SampleDataProvider.GetSamplePricings()) - { - _pricings[pricing.Id!] = pricing; - _risks[pricing.Id!] = new List(); - _importConfigs[pricing.Id!] = new List(); - } - } - - public IReadOnlyCollection GetCatalog() - { - return _pricings.Values.ToList(); - } - - public Task GetHeaderAsync(string id) - { - _pricings.TryGetValue(id, out var pricing); - return Task.FromResult(pricing); - } - - public Task> GetRisksAsync(string pricingId, CancellationToken ct = default) - { - if (_risks.TryGetValue(pricingId, out var risks)) - { - return Task.FromResult>(risks.ToList()); - } - return Task.FromResult>(Array.Empty()); - } - - public async IAsyncEnumerable GetImportConfigurationsAsync(string pricingId) - { - if (_importConfigs.TryGetValue(pricingId, out var configs)) - { - foreach (var config in configs) - { - yield return config; - } - } - await Task.CompletedTask; - } - - public void UpdatePricingHeader(Pricing pricing) - { - if (pricing.Id != null) - { - _pricings[pricing.Id] = pricing; - } - } - - public void UpdateRisks(string pricingId, IEnumerable updatedRisks) - { - if (!_risks.ContainsKey(pricingId)) - { - _risks[pricingId] = new List(); - } - - var riskList = _risks[pricingId]; - foreach (var risk in updatedRisks) - { - var existing = riskList.FirstOrDefault(r => r.Id == risk.Id); - if (existing != null) - { - riskList.Remove(existing); - } - riskList.Add(risk); - } - } - - public void UpsertImportConfiguration(ExcelImportConfiguration configuration) - { - if (!_importConfigs.ContainsKey(configuration.Address)) - { - _importConfigs[configuration.Address] = new List(); - } - - var configList = _importConfigs[configuration.Address]; - var existing = configList.FirstOrDefault(c => c.Name == configuration.Name); - if (existing != null) - { - configList.Remove(existing); - } - configList.Add(configuration); - } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.SampleData/InspectExcel.cs b/samples/Insurance/MeshWeaver.Insurance.SampleData/InspectExcel.cs deleted file mode 100644 index a6dd45ff52..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.SampleData/InspectExcel.cs +++ /dev/null @@ -1,33 +0,0 @@ -using ClosedXML.Excel; - -namespace MeshWeaver.Insurance.SampleData; - -public class ExcelInspector -{ - public static void InspectFile(string filePath) - { - using var workbook = new XLWorkbook(filePath, new LoadOptions { RecalculateAllFormulas = false }); - var worksheet = workbook.Worksheets.First(); - - Console.WriteLine($"Worksheet name: {worksheet.Name}"); - Console.WriteLine($"Last row used: {worksheet.LastRowUsed()?.RowNumber()}"); - Console.WriteLine($"Last column used: {worksheet.LastColumnUsed()?.ColumnNumber()}"); - Console.WriteLine($"\nFirst 15 rows:"); - Console.WriteLine(new string('-', 150)); - - for (int row = 1; row <= Math.Min(15, worksheet.LastRowUsed()?.RowNumber() ?? 0); row++) - { - var cells = new List(); - for (int col = 1; col <= (worksheet.LastColumnUsed()?.ColumnNumber() ?? 8); col++) - { - var cell = worksheet.Cell(row, col); - var value = cell.Value.ToString() ?? ""; - cells.Add(value); - } - Console.WriteLine($"Row {row,2}: {string.Join(" | ", cells)}"); - } - - Console.WriteLine(new string('-', 150)); - Console.WriteLine($"\nFreeze panes: Row {worksheet.SheetView.FreezeRows}, Col {worksheet.SheetView.FreezeColumns}"); - } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.SampleData/MeshWeaver.Insurance.SampleData.csproj b/samples/Insurance/MeshWeaver.Insurance.SampleData/MeshWeaver.Insurance.SampleData.csproj deleted file mode 100644 index 30fb6030fe..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.SampleData/MeshWeaver.Insurance.SampleData.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - Exe - net10.0 - enable - enable - - - diff --git a/samples/Insurance/MeshWeaver.Insurance.SampleData/MicrosoftExcelGenerator.cs b/samples/Insurance/MeshWeaver.Insurance.SampleData/MicrosoftExcelGenerator.cs deleted file mode 100644 index e810088329..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.SampleData/MicrosoftExcelGenerator.cs +++ /dev/null @@ -1,185 +0,0 @@ -using ClosedXML.Excel; - -namespace MeshWeaver.Insurance.SampleData; - -public class MicrosoftExcelGenerator -{ - public static void GenerateMicrosoftData(string filePath) - { - using var workbook = new XLWorkbook(filePath); - var worksheet = workbook.Worksheets.First(); - - // Find the header row (look for a row that has column headers) - int headerRow = FindHeaderRow(worksheet); - int firstDataRow = headerRow + 1; - - // Note: We'll re-establish freeze panes after updating - - // Clear existing data rows (keep everything above and including header row) - var lastRow = worksheet.LastRowUsed()?.RowNumber() ?? headerRow; - if (lastRow >= firstDataRow) - { - worksheet.Rows(firstDataRow, lastRow).Delete(); - } - - var microsoftLocations = GetMicrosoftLocations(); - - int currentRow = firstDataRow; - foreach (var location in microsoftLocations) - { - worksheet.Cell(currentRow, 1).Value = location.Name; - worksheet.Cell(currentRow, 2).Value = location.Type; - worksheet.Cell(currentRow, 3).Value = location.Address; - worksheet.Cell(currentRow, 4).Value = location.City; - worksheet.Cell(currentRow, 5).Value = location.Country; - worksheet.Cell(currentRow, 6).Value = location.Employees; - worksheet.Cell(currentRow, 7).Value = location.Revenue; - worksheet.Cell(currentRow, 8).Value = location.OpenDate; - currentRow++; - } - - // Update totals in the first few rows if they exist - UpdateTotals(worksheet, headerRow, firstDataRow, currentRow - 1); - - // Set freeze panes (freeze at header row) - if (headerRow > 0) - { - worksheet.SheetView.FreezeRows(headerRow); - worksheet.SheetView.FreezeColumns(0); - } - - workbook.Save(); - } - - private static int FindHeaderRow(IXLWorksheet worksheet) - { - // Look for a row that contains typical header text - for (int row = 1; row <= 10; row++) - { - var firstCell = worksheet.Cell(row, 1).Value.ToString().ToLower(); - // Common header patterns - if (firstCell.Contains("name") || firstCell.Contains("location") || - firstCell.Contains("office") || firstCell == "name") - { - return row; - } - } - return 1; // Default to row 1 if not found - } - - private static void UpdateTotals(IXLWorksheet worksheet, int headerRow, int firstDataRow, int lastDataRow) - { - // Update totals in rows before the header (if they exist) - for (int row = 1; row < headerRow; row++) - { - var cell = worksheet.Cell(row, 1).Value.ToString().ToLower(); - if (cell.Contains("total") || cell.Contains("sum")) - { - // Update numeric totals for columns 6 (Employees) and 7 (Revenue) - var employeesCell = worksheet.Cell(row, 6); - var revenueCell = worksheet.Cell(row, 7); - - if (firstDataRow <= lastDataRow) - { - employeesCell.FormulaA1 = $"=SUM({worksheet.Cell(firstDataRow, 6).Address}:{worksheet.Cell(lastDataRow, 6).Address})"; - revenueCell.FormulaA1 = $"=SUM({worksheet.Cell(firstDataRow, 7).Address}:{worksheet.Cell(lastDataRow, 7).Address})"; - } - } - } - } - - private static List GetMicrosoftLocations() - { - return new List - { - // North America - new("Microsoft Campus - Redmond", "Headquarters", "1 Microsoft Way", "Redmond, WA", "United States", 50000, 125000000, new DateTime(1986, 2, 26)), - new("Microsoft Silicon Valley Campus", "Office", "1065 La Avenida", "Mountain View, CA", "United States", 2500, 8500000, new DateTime(2015, 7, 1)), - new("Microsoft New York", "Office", "11 Times Square", "New York, NY", "United States", 3200, 12000000, new DateTime(2015, 3, 15)), - new("Microsoft Cambridge", "Research Lab", "1 Memorial Drive", "Cambridge, MA", "United States", 850, 2100000, new DateTime(2008, 9, 1)), - new("Microsoft Vancouver", "Office", "725 Granville St", "Vancouver, BC", "Canada", 750, 2800000, new DateTime(2018, 5, 1)), - new("Microsoft Toronto", "Office", "81 Bay Street", "Toronto, ON", "Canada", 1200, 4200000, new DateTime(2012, 11, 1)), - new("Microsoft Atlanta Data Center", "Data Center", "6789 Peachtree Rd", "Atlanta, GA", "United States", 180, 950000, new DateTime(2019, 3, 15)), - new("Microsoft Chicago", "Sales Point", "200 E Randolph St", "Chicago, IL", "United States", 680, 2400000, new DateTime(2016, 8, 20)), - new("Microsoft San Antonio Data Center", "Data Center", "10000 Interstate 10 W", "San Antonio, TX", "United States", 220, 1100000, new DateTime(2020, 1, 10)), - new("Microsoft Miami", "Sales Point", "1001 Brickell Bay Dr", "Miami, FL", "United States", 420, 1600000, new DateTime(2017, 4, 5)), - - // Europe - new("Microsoft UK Headquarters", "Office", "2 Kingdom Street", "London", "United Kingdom", 3800, 15000000, new DateTime(2004, 5, 1)), - new("Microsoft Ireland", "Office", "One Microsoft Place", "Dublin", "Ireland", 2200, 7800000, new DateTime(2003, 10, 1)), - new("Microsoft France", "Office", "39 Quai du Président Roosevelt", "Paris", "France", 1800, 6500000, new DateTime(1983, 7, 1)), - new("Microsoft Germany", "Office", "Konrad-Zuse-Straße 1", "Munich", "Germany", 2800, 9200000, new DateTime(1983, 3, 1)), - new("Microsoft Netherlands", "Office", "Evert van de Beekstraat 354", "Amsterdam", "Netherlands", 980, 3400000, new DateTime(1989, 6, 1)), - new("Microsoft Switzerland", "Office", "Richtistrasse 3", "Zurich", "Switzerland", 720, 2900000, new DateTime(1988, 11, 1)), - new("Microsoft Sweden", "Office", "Kungsbron 2", "Stockholm", "Sweden", 650, 2200000, new DateTime(1985, 4, 1)), - new("Microsoft Dublin Data Center", "Data Center", "Parkwest Business Park", "Dublin", "Ireland", 150, 680000, new DateTime(2009, 6, 1)), - new("Microsoft Amsterdam Data Center", "Data Center", "Science Park 140", "Amsterdam", "Netherlands", 190, 820000, new DateTime(2014, 9, 1)), - new("Microsoft Spain", "Office", "Calle de Josefa Valcárcel 42", "Madrid", "Spain", 890, 3100000, new DateTime(1992, 2, 1)), - - // Asia-Pacific - new("Microsoft 中国 - 北京", "Office", "丹棱街5号", "北京", "China", 4200, 18000000, new DateTime(1995, 5, 1)), - new("Microsoft 中国 - 上海", "Office", "紫竹科学园区", "上海", "China", 3100, 12500000, new DateTime(2003, 9, 1)), - new("Microsoft 中国 - 深圳", "Office", "科技南路16号", "深圳", "China", 1800, 7200000, new DateTime(2012, 3, 1)), - new("Microsoft 日本 - 東京", "Office", "港区港南 2-16-3", "東京", "Japan", 2500, 9800000, new DateTime(1986, 2, 1)), - new("Microsoft 日本 - 大阪", "Sales Point", "大阪市北区梅田3-3-10", "大阪", "Japan", 680, 2600000, new DateTime(2010, 7, 1)), - new("Microsoft Singapore", "Office", "1 Marina Boulevard", "Singapore", "Singapore", 1200, 5400000, new DateTime(1990, 8, 1)), - new("Microsoft Singapore Data Center", "Data Center", "25 Tai Seng Street", "Singapore", "Singapore", 140, 720000, new DateTime(2013, 11, 1)), - new("Microsoft India - Bangalore", "Office", "RMZ Infinity Tower E", "Bangalore", "India", 8500, 16000000, new DateTime(1990, 12, 1)), - new("Microsoft India - Hyderabad", "Office", "iLabs Centre", "Hyderabad", "India", 6200, 11500000, new DateTime(1998, 3, 1)), - new("Microsoft India - Noida", "Sales Point", "Express Trade Towers", "Noida", "India", 950, 3200000, new DateTime(2016, 1, 1)), - new("Microsoft Australia - Sydney", "Office", "1 Epping Road", "Sydney", "Australia", 1800, 6200000, new DateTime(1983, 11, 1)), - new("Microsoft South Korea - 서울", "Office", "강남구 테헤란로 152", "서울", "South Korea", 1100, 4800000, new DateTime(1989, 1, 1)), - new("Microsoft Taiwan - 台北", "Office", "信義路五段7號", "台北", "Taiwan", 780, 2900000, new DateTime(1989, 10, 1)), - new("Microsoft Hong Kong - 香港", "Office", "太古坊德宏大廈", "香港", "Hong Kong", 620, 2400000, new DateTime(1991, 5, 1)), - new("Microsoft Malaysia - Kuala Lumpur", "Sales Point", "Level 26, Menara 3 Petronas", "Kuala Lumpur", "Malaysia", 380, 1400000, new DateTime(2008, 4, 1)), - new("Microsoft Philippines - Manila", "Sales Point", "8th Floor, 6750 Ayala Avenue", "Manila", "Philippines", 420, 1600000, new DateTime(2006, 9, 1)), - new("Microsoft Thailand - กรุงเทพมหานคร", "Sales Point", "589 Sukhumvit Road", "กรุงเทพมหานคร", "Thailand", 310, 1200000, new DateTime(2011, 6, 1)), - new("Microsoft Indonesia - Jakarta", "Sales Point", "Wisma Mulia, Sudirman", "Jakarta", "Indonesia", 280, 1050000, new DateTime(2013, 2, 1)), - new("Microsoft Vietnam - Hồ Chí Minh", "Sales Point", "Bitexco Financial Tower", "Hồ Chí Minh", "Vietnam", 240, 890000, new DateTime(2015, 8, 1)), - - // Middle East & Africa - new("Microsoft UAE - Dubai", "Office", "Dubai Internet City", "Dubai", "United Arab Emirates", 680, 2800000, new DateTime(2003, 5, 1)), - new("Microsoft South Africa - Johannesburg", "Office", "3012 William Nicol Drive", "Johannesburg", "South Africa", 580, 2100000, new DateTime(1992, 9, 1)), - new("Microsoft Israel - Tel Aviv", "Office", "94 Em HaMoshavot Road", "Tel Aviv", "Israel", 1200, 4500000, new DateTime(1989, 6, 1)), - new("Microsoft Egypt - القاهرة", "Sales Point", "Smart Village, Building B141", "القاهرة", "Egypt", 320, 1150000, new DateTime(2007, 3, 1)), - new("Microsoft Qatar Data Center", "Data Center", "Doha Technology Park", "Doha", "Qatar", 110, 580000, new DateTime(2018, 5, 1)), - - // South America - new("Microsoft Brazil - São Paulo", "Office", "Avenida Presidente Juscelino Kubitschek 1830", "São Paulo", "Brazil", 1400, 5200000, new DateTime(1989, 3, 1)), - new("Microsoft Argentina - Buenos Aires", "Sales Point", "Ing. Enrique Butty 240", "Buenos Aires", "Argentina", 380, 1400000, new DateTime(1994, 7, 1)), - new("Microsoft Colombia - Bogotá", "Sales Point", "Carrera 7 No. 71-21", "Bogotá", "Colombia", 290, 1080000, new DateTime(2010, 11, 1)), - new("Microsoft Chile - Santiago", "Sales Point", "Av. Andrés Bello 2711", "Santiago", "Chile", 240, 920000, new DateTime(2012, 4, 1)), - new("Microsoft Mexico - Ciudad de México", "Office", "Paseo de la Reforma 505", "Ciudad de México", "Mexico", 850, 3300000, new DateTime(1991, 8, 1)), - - // Additional Data Centers - new("Microsoft Iowa Data Center", "Data Center", "3131 114th Street", "West Des Moines, IA", "United States", 165, 780000, new DateTime(2007, 4, 1)), - new("Microsoft Virginia Data Center", "Data Center", "44801 Gloucester Parkway", "Ashburn, VA", "United States", 210, 980000, new DateTime(2010, 9, 1)), - new("Microsoft Wyoming Data Center", "Data Center", "3401 E Lincolnway", "Cheyenne, WY", "United States", 125, 620000, new DateTime(2012, 6, 1)), - new("Microsoft Arizona Data Center", "Data Center", "1950 N Alma School Road", "Chandler, AZ", "United States", 185, 850000, new DateTime(2015, 2, 1)), - new("Microsoft Amsterdam 2 Data Center", "Data Center", "Kabelweg 37", "Amsterdam", "Netherlands", 145, 690000, new DateTime(2017, 10, 1)), - new("Microsoft Finland Data Center", "Data Center", "Metsäläntie 7", "Helsinki", "Finland", 95, 480000, new DateTime(2018, 12, 1)), - new("Microsoft Norway Data Center", "Data Center", "Drammensvegen 264", "Oslo", "Norway", 102, 520000, new DateTime(2019, 8, 1)), - new("Microsoft South Africa Data Center", "Data Center", "Longkloof Studios", "Cape Town", "South Africa", 88, 420000, new DateTime(2020, 3, 1)), - new("Microsoft Australia Data Center", "Data Center", "51 Bourke Road", "Melbourne", "Australia", 118, 580000, new DateTime(2014, 5, 1)), - new("Microsoft New Zealand - Auckland", "Sales Point", "157 Lambton Quay", "Auckland", "New Zealand", 180, 680000, new DateTime(2009, 9, 1)), - - // Additional Asian Locations - new("Microsoft 中国 - 广州", "Sales Point", "天河区珠江新城", "广州", "China", 720, 2800000, new DateTime(2015, 6, 1)), - new("Microsoft 中国 - 成都", "Sales Point", "高新区天府大道", "成都", "China", 580, 2200000, new DateTime(2017, 9, 1)), - new("Microsoft 中国 - 杭州", "Sales Point", "西湖区文三路", "杭州", "China", 640, 2500000, new DateTime(2016, 4, 1)), - new("Microsoft 日本 - 名古屋", "Sales Point", "中村区名駅4-7-1", "名古屋", "Japan", 280, 1100000, new DateTime(2014, 3, 1)), - new("Microsoft 日本 - 福岡", "Sales Point", "博多区博多駅前2-1-1", "福岡", "Japan", 195, 780000, new DateTime(2016, 11, 1)), - }; - } - - private record MicrosoftLocation( - string Name, - string Type, - string Address, - string City, - string Country, - int Employees, - decimal Revenue, - DateTime OpenDate - ); -} diff --git a/samples/Insurance/MeshWeaver.Insurance.SampleData/Program.cs b/samples/Insurance/MeshWeaver.Insurance.SampleData/Program.cs deleted file mode 100644 index 14822892f3..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.SampleData/Program.cs +++ /dev/null @@ -1,38 +0,0 @@ -using MeshWeaver.Insurance.SampleData; - -var excelPath = Path.Combine( - AppDomain.CurrentDomain.BaseDirectory, - "..", "..", "..", "..", - "Files", "Microsoft", "2026", "Microsoft.xlsx" -); - -if (args.Length > 0 && args[0] == "inspect") -{ - Console.WriteLine($"Inspecting Excel file: {excelPath}"); - if (!File.Exists(excelPath)) - { - Console.WriteLine($"Error: File not found at {excelPath}"); - return 1; - } - ExcelInspector.InspectFile(excelPath); -} -else -{ - Console.WriteLine($"Updating Excel file: {excelPath}"); - if (!File.Exists(excelPath)) - { - Console.WriteLine($"Error: File not found at {excelPath}"); - return 1; - } - - // Create backup - var backupPath = excelPath.Replace(".xlsx", $".backup.{DateTime.Now:yyyyMMdd_HHmmss}.xlsx"); - File.Copy(excelPath, backupPath, true); - Console.WriteLine($"Backup created: {backupPath}"); - - MicrosoftExcelGenerator.GenerateMicrosoftData(excelPath); - Console.WriteLine("Successfully updated Microsoft.xlsx with 65+ locations including Chinese and Japanese offices!"); - Console.WriteLine("Preserved: header rows with totals and freeze panes"); -} - -return 0; diff --git a/samples/Insurance/MeshWeaver.Insurance.SampleData/README.md b/samples/Insurance/MeshWeaver.Insurance.SampleData/README.md deleted file mode 100644 index 8793a1d60c..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.SampleData/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# MeshWeaver.Insurance.SampleData - -## Overview -MeshWeaver.Insurance.SampleData is a console application that generates and updates sample Excel files used by the Insurance domain. It populates property risk data (65+ global office locations) into structured `.xlsx` worksheets while preserving header rows, totals, and freeze panes. - -## Features -- Generates realistic property risk location data for sample insurance submissions -- Supports inspecting existing Excel files to review their structure -- Creates timestamped backups before modifying files -- Uses ClosedXML for Excel manipulation - -## Usage -```bash -# Update the sample Microsoft.xlsx with generated data -dotnet run --project samples/Insurance/MeshWeaver.Insurance.SampleData - -# Inspect an existing Excel file -dotnet run --project samples/Insurance/MeshWeaver.Insurance.SampleData -- inspect -``` - -## Related Projects -- [MeshWeaver.Insurance.Domain](../MeshWeaver.Insurance.Domain/) -- Insurance domain model and services diff --git a/samples/Insurance/MeshWeaver.Insurance.Test/InsuranceTestBase.cs b/samples/Insurance/MeshWeaver.Insurance.Test/InsuranceTestBase.cs deleted file mode 100644 index d813422338..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Test/InsuranceTestBase.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Reactive.Linq; -using System.Text.Json; -using System.Text.Json.Nodes; -using MeshWeaver.Data; -using MeshWeaver.Hosting.Monolith.TestBase; -using MeshWeaver.Insurance.Domain; -using MeshWeaver.Mesh; -using MeshWeaver.Messaging; -using Xunit; - -namespace MeshWeaver.Insurance.Test; - -public abstract class InsuranceTestBase(ITestOutputHelper output) : MonolithMeshTestBase(output) -{ - protected override MeshBuilder ConfigureMesh(MeshBuilder builder) - { - return base.ConfigureMesh(builder) - .ConfigureHub(c => c - .AddData() - ) - .InstallAssemblies(typeof(InsuranceApplicationAttribute).Assembly.Location); - } - - protected IObservable> GetPropertyRisks(Address address) - { - var hub = Mesh; - return hub.Observe( - new GetDataRequest(new CollectionReference(nameof(PropertyRisk))), - o => o.WithTarget(address)) - .Select(risksResp => - (IReadOnlyCollection)((risksResp?.Message?.Data as IEnumerable)? - .Select(x => x as PropertyRisk ?? (x as JsonObject)?.Deserialize(hub.JsonSerializerOptions)) - .Where(x => x != null) - .Cast() - .ToList() - ?? [])); - } - - protected IObservable> GetPricings() - { - var hub = Mesh; - return hub.Observe( - new GetDataRequest(new CollectionReference(nameof(Pricing))), - o => o.WithTarget(InsuranceApplicationAttribute.Address)) - .Select(pricingsResp => - (IReadOnlyCollection)((pricingsResp.Message.Data as InstanceCollection)? - .Instances.Values - .Select(x => x as Pricing ?? (x as JsonObject)?.Deserialize(hub.JsonSerializerOptions)) - .Where(x => x != null) - .Cast() - .ToList() - ?? [])); - } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Test/MeshWeaver.Insurance.Test.csproj b/samples/Insurance/MeshWeaver.Insurance.Test/MeshWeaver.Insurance.Test.csproj deleted file mode 100644 index d09e260601..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Test/MeshWeaver.Insurance.Test.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - net10.0 - enable - enable - true - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - - - PreserveNewest - - - diff --git a/samples/Insurance/MeshWeaver.Insurance.Test/MicrosoftImportTests.cs b/samples/Insurance/MeshWeaver.Insurance.Test/MicrosoftImportTests.cs deleted file mode 100644 index 6ae9a5f734..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Test/MicrosoftImportTests.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System.Reactive.Linq; -using MeshWeaver.ContentCollections; -using MeshWeaver.Data; -using MeshWeaver.Import; -using MeshWeaver.Import.Configuration; -using MeshWeaver.Insurance.Domain; -using MeshWeaver.Insurance.Domain.Services; -using MeshWeaver.Mesh; -using MeshWeaver.Messaging; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace MeshWeaver.Insurance.Test; - -public class MicrosoftImportTests(ITestOutputHelper output) : InsuranceTestBase(output) -{ - private readonly string _testFilesPath = Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "Files", "Microsoft", "2026"); - private const string MicrosoftPricingId = "Microsoft/2026"; - - protected override MeshBuilder ConfigureMesh(MeshBuilder builder) - { - // Ensure test directory exists - Directory.CreateDirectory(_testFilesPath); - - return base.ConfigureMesh(builder) - .ConfigureServices(services => services - .AddSingleton() - ) - .ConfigureHub(c => c - .AddContentCollections() - .AddFileSystemContentCollection($"Submissions@{MicrosoftPricingId}", _ => _testFilesPath) - .AddImport() - .AddData(data => data.AddSource(source => source.WithType())) - ); - } - private static readonly ExcelImportConfiguration Config = new() - { - Name = "Microsoft.xlsx", - Address = $"pricing/{MicrosoftPricingId}", - TypeName = nameof(PropertyRisk), // Auto-generate entity builder for PropertyRisk - //WorksheetName = "Locations", // Adjust based on actual worksheet name - DataStartRow = 7, // Assuming row 1 is headers - TotalRowMarkers = ["Total", "Grand Total"], - TotalRowScanAllCells = true, - TotalRowMatchExact = false, - Mappings = - [ - new () { TargetProperty = "Id", Kind = MappingKind.Direct, SourceColumns = new List { "C" } }, - new() - { - TargetProperty = "LocationName", - Kind = MappingKind.Direct, - SourceColumns = ["D"] - }, - new() { TargetProperty = "PricingId", Kind = MappingKind.Constant, ConstantValue = MicrosoftPricingId }, - - // Address fields - new() - { - TargetProperty = "Address", - Kind = MappingKind.Direct, - SourceColumns = ["E"] - }, - new() { TargetProperty = "Country", Kind = MappingKind.Direct, SourceColumns = new List { "B" } }, - new() - { - TargetProperty = "TsiBuilding", - Kind = MappingKind.Direct, - SourceColumns = ["H"] - }, - new() - { - TargetProperty = "TsiContent", - Kind = MappingKind.Direct, - SourceColumns = ["G", "I", "J", "K", "L", "M", "N", "O", "P"] - }, - ], - Allocations = [new() { TargetProperty = "TsiBi", WeightColumns = ["Q"] }], - IgnoreRowExpressions = - [ - "Id == null", // Skip rows without an ID - "Address == null" - ] - }; - - [Fact(Timeout = 30000)] - public async Task Import_Microsoft_File_WithConfiguration() - { - // Skip test if file doesn't exist - var fullPath = Path.Combine(_testFilesPath, "Microsoft.xlsx"); - if (!File.Exists(fullPath)) - throw new FileNotFoundException(fullPath); - - // Arrange - Create import configuration with TypeName - - // Act - Import using ImportRequest with Configuration - var importRequest = new ImportRequest(new CollectionSource($"Submissions@{MicrosoftPricingId}", "Microsoft.xlsx")) - { - Configuration = Config - }; - - var importResponse = await Mesh.Observe( - importRequest, - o => o.WithTarget(Mesh.Address)) - .Should().Within(20.Seconds()).Emit(); - - // Assert - importResponse.Should().NotBeNull(); - importResponse.Message.Log.Status.Should().Be(ActivityStatus.Succeeded, - $"import should succeed. Errors: {string.Join(", ", importResponse.Message.Log.Errors().Select(e => e.Message))}"); - - // Verify data was imported by querying the workspace - var workspace = Mesh.ServiceProvider.GetRequiredService(); - var risks = await workspace.GetObservable() - .Should().Within(10.Seconds()).Match(x => x.Count > 0); - - risks.Should().NotBeEmpty("import should return at least one risk"); - risks.All(r => r.PricingId == MicrosoftPricingId).Should().BeTrue("all risks should have PricingId set to Microsoft/2026"); - risks.All(r => !string.IsNullOrWhiteSpace(r.Id)).Should().BeTrue("all risks should have an Id"); - - // Verify source tracking - risks.All(r => r.SourceRow > 0).Should().BeTrue("all risks should have SourceRow set"); - risks.All(r => r.SourceFile == "Microsoft.xlsx").Should().BeTrue("all risks should track source file"); - - // Output summary - Output.WriteLine($"Successfully imported {risks.Count} property risks"); - if (risks.Any()) - { - var first = risks.First(); - Output.WriteLine($"Sample risk: Id={first.Id}, Location={first.LocationName}, Country={first.Country}"); - } - } - - [Fact(Timeout = 30000)] - public async Task Import_Microsoft_WithAllocation() - { - // Skip test if file doesn't exist - var fullPath = Path.Combine(_testFilesPath, "Microsoft.xlsx"); - if (!File.Exists(fullPath)) - throw new FileNotFoundException(fullPath); - - // Act - Import using ImportRequest with Configuration - var importRequest = new ImportRequest(new CollectionSource($"Submissions@{MicrosoftPricingId}", "Microsoft.xlsx")) - { - Configuration = Config - }; - - var importResponse = await Mesh.Observe( - importRequest, - o => o.WithTarget(Mesh.Address)) - .Should().Within(20.Seconds()).Emit(); - - // Assert - importResponse.Should().NotBeNull(); - importResponse.Message.Log.Status.Should().Be(ActivityStatus.Succeeded); - - // Verify data was imported - var workspace = Mesh.ServiceProvider.GetRequiredService(); - var risks = await workspace.GetObservable() - .Should().Within(10.Seconds()).Match(x => x.Count > 0); - risks.Should().NotBeEmpty(); - - // Verify allocation worked - sum of allocated TsiBi should equal proportional distribution - var rowsWithWeights = risks - .Select(r => new { Building = r.TsiBuilding, Content = r.TsiContent, Bi = r.TsiBi }) - .ToList(); - - var weights = rowsWithWeights.Select(x => x.Building + x.Content).ToList(); - - // All non-zero weights should have proportional BI allocation - var factors = rowsWithWeights - .Zip(weights, (x, w) => new { x.Bi, W = w }) - .Where(p => p.W > 0) - .Select(p => p.Bi / p.W) - .ToList(); - - if (factors.Any()) - { - (factors.Max() - factors.Min()).Should().BeLessThan(0.0001, "allocation should be proportional"); - } - - Output.WriteLine($"Imported {risks.Count} risks with proportional BI allocation"); - } - - [Fact(Timeout = 30000)] - public async Task Import_Microsoft_UsingSumMapping() - { - // Skip test if file doesn't exist - var fullPath = Path.Combine(_testFilesPath, "Microsoft.xlsx"); - if (!File.Exists(fullPath)) - throw new FileNotFoundException(fullPath); - - // Demonstrate Sum mapping - combining multiple columns - - // Act - Import using ImportRequest with Configuration - var importRequest = new ImportRequest(new CollectionSource($"Submissions@{MicrosoftPricingId}", "Microsoft.xlsx")) - { - Configuration = Config - }; - - var importResponse = await Mesh.Observe( - importRequest, - o => o.WithTarget(Mesh.Address)) - .Should().Within(20.Seconds()).Emit(); - - // Assert - importResponse.Should().NotBeNull(); - importResponse.Message.Log.Status.Should().Be(ActivityStatus.Succeeded); - - // Verify data was imported - var workspace = Mesh.ServiceProvider.GetRequiredService(); - var risks = await workspace.GetObservable() - .Should().Within(10.Seconds()).Match(x => x.Count > 0); - risks.Should().NotBeEmpty(); - Output.WriteLine($"Imported {risks.Count} risks using direct mapping"); - } -} diff --git a/samples/Insurance/MeshWeaver.Insurance.Test/PricingCatalogTests.cs b/samples/Insurance/MeshWeaver.Insurance.Test/PricingCatalogTests.cs deleted file mode 100644 index 9c2e161578..0000000000 --- a/samples/Insurance/MeshWeaver.Insurance.Test/PricingCatalogTests.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System.Reactive.Linq; -using System.Text.Json; -using MeshWeaver.Data; -using MeshWeaver.Insurance.Domain; -using MeshWeaver.Insurance.Domain.Services; -using MeshWeaver.Layout; -using MeshWeaver.Mesh; -using MeshWeaver.Messaging; -using Microsoft.Extensions.DependencyInjection; - -namespace MeshWeaver.Insurance.Test; - -public class PricingCatalogTests(ITestOutputHelper output) : InsuranceTestBase(output) -{ - protected override MeshBuilder ConfigureMesh(MeshBuilder builder) - { - return base.ConfigureMesh(builder) - .ConfigureServices(services => services - .AddSingleton() - ); - } - - protected override MessageHubConfiguration ConfigureClient(MessageHubConfiguration configuration) - { - return base.ConfigureClient(configuration).AddLayoutClient(); - } - - [Fact(Timeout = 30000)] - public async Task GetPricingCatalog_ShouldReturnPricings() - { - // Act - Get the pricing catalog from the Insurance hub - var pricings = await GetPricings().Should().Match(p => p.Count > 0, "catalog should contain sample pricings"); - - // Assert - Verify that the catalog contains pricings - pricings.Should().NotBeNull("catalog should not be null"); - pricings.Should().NotBeEmpty("catalog should contain sample pricings"); - - // Verify that pricings have required fields - pricings.All(p => !string.IsNullOrWhiteSpace(p.Id)).Should().BeTrue("all pricings should have an Id"); - pricings.All(p => !string.IsNullOrWhiteSpace(p.InsuredName)).Should().BeTrue("all pricings should have an InsuredName"); - pricings.All(p => !string.IsNullOrWhiteSpace(p.Status)).Should().BeTrue("all pricings should have a Status"); - - // Output summary - Output.WriteLine($"Successfully retrieved {pricings.Count} pricings from catalog"); - foreach (var pricing in pricings) - { - Output.WriteLine($" - {pricing.Id}: {pricing.InsuredName} ({pricing.Status}) - {pricing.LineOfBusiness}/{pricing.Country}"); - } - } - - [Fact(Timeout = 30000)] - public async Task GetPricingCatalog_ShouldHaveValidDimensions() - { - // Act - var pricings = await GetPricings().Should().Match(p => p.Count > 0); - - // Assert - Verify dimension fields are populated - pricings.Should().NotBeEmpty(); - - pricings.All(p => !string.IsNullOrWhiteSpace(p.LineOfBusiness)).Should().BeTrue("all pricings should have a LineOfBusiness"); - pricings.All(p => !string.IsNullOrWhiteSpace(p.Country)).Should().BeTrue("all pricings should have a Country"); - pricings.All(p => !string.IsNullOrWhiteSpace(p.Currency)).Should().BeTrue("all pricings should have a Currency"); - - // Output dimension information - Output.WriteLine("Pricing dimensions:"); - Output.WriteLine($" Lines of Business: {string.Join(", ", pricings.Select(p => p.LineOfBusiness).Distinct())}"); - Output.WriteLine($" Countries: {string.Join(", ", pricings.Select(p => p.Country).Distinct())}"); - Output.WriteLine($" Currencies: {string.Join(", ", pricings.Select(p => p.Currency).Distinct())}"); - } - - [Fact(Timeout = 30000)] - public async Task GetPricingCatalog_ShouldHaveValidDates() - { - // Act - var pricings = await GetPricings().Should().Match(p => p.Count > 0); - - // Assert - pricings.Should().NotBeEmpty(); - - foreach (var pricing in pricings) - { - pricing.InceptionDate.Should().NotBeNull( - $"pricing {pricing.Id} should have an inception date"); - pricing.ExpirationDate.Should().NotBeNull( - $"pricing {pricing.Id} should have an expiration date"); - - if (pricing.InceptionDate.HasValue && pricing.ExpirationDate.HasValue) - { - pricing.ExpirationDate.Value.Should().BeAfter(pricing.InceptionDate.Value, - $"pricing {pricing.Id} expiration date should be after inception date"); - } - - pricing.UnderwritingYear.Should().NotBeNull( - $"pricing {pricing.Id} should have an underwriting year"); - pricing.UnderwritingYear!.Value.Should().BeGreaterThan(2000, - $"pricing {pricing.Id} should have a valid underwriting year"); - } - - Output.WriteLine($"All {pricings.Count} pricings have valid dates"); - } - - [Fact(Timeout = 30000)] - public async Task PricingHub_ShouldStartSuccessfully() - { - // This test verifies that the pricing hub initializes correctly - // by successfully retrieving the catalog without errors - - // Act - var pricings = await GetPricings().Should().Match(p => p.Count > 0, "hub should start and return catalog"); - - // Assert - Hub started if we can get data - pricings.Should().NotBeNull("hub should start and return catalog"); - - // Verify the hub is accessible - Mesh.Should().NotBeNull("mesh should be initialized"); - Mesh.Address.Should().NotBeNull("mesh should have an address"); - - Output.WriteLine($"Pricing hub started successfully"); - Output.WriteLine($"Hub Address: {Mesh.Address}"); - Output.WriteLine($"Retrieved {pricings.Count} pricings from catalog"); - } - - [Fact(Timeout = 30000)] - public async Task GetPricingCatalog_UsingLayoutAreaReference_ShouldReturnPricingsControl() - { - // Arrange - var reference = new LayoutAreaReference("Pricings"); - var client = GetClient(); - var workspace = client.GetWorkspace(); - - // Act - Get the remote stream using LayoutAreaReference - var stream = workspace.GetRemoteStream( - InsuranceApplicationAttribute.Address, - reference - ); - - // Get the control from the stream - var control = (await stream.GetControlStream(reference.Area!) - .Should().Within(10.Seconds()).Match(x => x != null))!; - - // Assert - control.Should().NotBeNull("layout area should return a control"); - - // Output control information - Output.WriteLine($"Received control type: {control.GetType().Name}"); - Output.WriteLine($"Successfully retrieved Pricings layout area using GetRemoteStream"); - } -} diff --git a/samples/Northwind/MeshWeaver.Northwind.Application/AnnualReportConfiguration.cs b/samples/Northwind/MeshWeaver.Northwind.Application/AnnualReportConfiguration.cs deleted file mode 100644 index ada6e8af01..0000000000 --- a/samples/Northwind/MeshWeaver.Northwind.Application/AnnualReportConfiguration.cs +++ /dev/null @@ -1,65 +0,0 @@ -using MeshWeaver.Application.Styles; -using MeshWeaver.Domain; -using MeshWeaver.Layout.Composition; -using MeshWeaver.Utils; - -namespace MeshWeaver.Northwind.Application; - -/// -/// Provides configuration methods for the annual report layout. -/// -public static class AnnualReportConfiguration -{ - /// - /// Adds the annual report sections to the layout. - /// - /// The layout definition to which the annual report sections will be added. - /// The updated layout definition with the annual report sections. - public static LayoutDefinition AddAnnualReport( - this LayoutDefinition layout - ) => layout - .AddAnnualReportSummary() - .AddSalesOverview() - .AddSalesComparison() - .AddProductOverview() - .AddRevenue() - .AddDiscountSummary() - .AddDiscountVsRevenue() - .AddDiscountPercentage() - .AddEmployeesOverview() - .AddOrdersOverview() - .AddSalesOverview() - ; - - - private const string SummaryDocument = "AnnualReportSummary.md"; - - - private static IEnumerable<(string DocumentName, Icon Icon)> AnnualReportDocuments => - ThisAssembly.GetManifestResourceNames() - .Where(r => r.StartsWith(DocumentFolder)) - .Select(filePath => filePath.Substring(DocumentFolder.Length + 1)) - .Except(SummaryDocument.RepeatOnce()) - .Select(docName => new { DocumentName = docName, IconAndOrder = GetIconAndOrder(docName), }) - .OrderBy(x => x.IconAndOrder.Order) - .Select(x => (x.DocumentName, x.IconAndOrder.Icon)); - - private static string DocumentFolder => - $"{ThisAssembly.GetName().Name}.Markdown.AnnualReport"; - - private static readonly IReadOnlyDictionary DocumentIcons = new Dictionary() - { - { "SalesAnalysis.md", (FluentIcons.Money(), 10) }, - { "TopSalesRepresentatives.md", (FluentIcons.PersonAccounts(), 20) }, - { "TopProductsOverview.md", (FluentIcons.ShoppingBag(), 30) }, - { "DiscountsAnalysis.md", (FluentIcons.ShoppingBagPercent(), 40) }, - { "OrdersReview.md", (FluentIcons.BoxCheckmark(), 50) }, - { "TopClientsOverview.md", (FluentIcons.CreditCardPerson(), 60) }, - }; - - private static (Icon Icon, int Order) GetIconAndOrder(string documentName) - => DocumentIcons.TryGetValue(documentName, out var icon) ? icon : (FluentIcons.Document(), int.MaxValue); - - private static System.Reflection.Assembly ThisAssembly - => typeof(AnnualReportConfiguration).Assembly; -} diff --git a/samples/Northwind/MeshWeaver.Northwind.Application/AnnualReportExtensions.cs b/samples/Northwind/MeshWeaver.Northwind.Application/AnnualReportExtensions.cs deleted file mode 100644 index 2b1e002527..0000000000 --- a/samples/Northwind/MeshWeaver.Northwind.Application/AnnualReportExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using MeshWeaver.Layout; -using MeshWeaver.Layout.Composition; - -namespace MeshWeaver.Northwind.Application -{ - /// - /// Provides extension methods for the annual report. - /// - public static class AnnualReportExtensions - { - /// - /// Gets the year from the query string parameters of the layout area. - /// - /// The layout area host. - /// The year as an integer. If the year is not specified or invalid, returns the current year. - public static int Year(this LayoutAreaHost layoutArea) - { - var yearString = layoutArea.GetQueryStringParamValue(nameof(Year)); - - if (!string.IsNullOrEmpty(yearString) && int.TryParse(yearString, out var year)) - { - return year; - } - - return 2025; - } - } -} diff --git a/samples/Northwind/MeshWeaver.Northwind.Application/AnnualReportSummaryArea.cs b/samples/Northwind/MeshWeaver.Northwind.Application/AnnualReportSummaryArea.cs deleted file mode 100644 index 16a21014bc..0000000000 --- a/samples/Northwind/MeshWeaver.Northwind.Application/AnnualReportSummaryArea.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Reactive.Linq; -using MeshWeaver.Application.Styles; -using MeshWeaver.Layout; -using MeshWeaver.Layout.Composition; -using static MeshWeaver.Northwind.Application.LayoutTemplates; - -namespace MeshWeaver.Northwind.Application; - -/// -/// Provides methods to add and render the annual report summary area. -/// -[Display(GroupName = "Dashboards", Order = 20)] -public static class AnnualReportSummaryArea -{ - /// - /// Adds the annual report summary view to the layout definition. - /// - /// The layout definition. - /// The updated layout definition with the annual report summary view. - public static LayoutDefinition AddAnnualReportSummary(this LayoutDefinition layout) - => - layout - .WithView(nameof(AnnualReportSummary), AnnualReportSummary, area => area.WithGroup("Dashboards")) - ; - - /// - /// Renders the annual report summary area. - /// - /// The layout area host. - /// The rendering context. - /// An object representing the rendered annual report summary area. - public static UiControl AnnualReportSummary(this LayoutAreaHost layoutArea, RenderingContext context) - => Controls.LayoutGrid - .WithView(ValueBoxes, skin => skin.WithXs(12)) - .WithView(SummaryCharts, skin => skin.WithXs(12)) - .WithSkin(skin => skin.WithSpacing(5)) - ; - - /// - /// Creates an observable sequence of value boxes for the annual report summary. - /// - /// The layout area host. - /// The rendering context. - /// An observable sequence of value boxes. - private static IObservable ValueBoxes(LayoutAreaHost layoutArea, RenderingContext context) - { - var currentYear = layoutArea.Year(); - var previousYear = currentYear - 1; - return layoutArea.SummaryItems() - .Select(items => items.ToDictionary(x => x.Year)) - .Select(items => - { - var current = items[currentYear]; - var previous = items.TryGetValue(previousYear, out var item) ? item : null; - - return Controls.LayoutGrid - .WithView(Sales(current, previous), skin => skin.WithXs(6).WithSm(3)) - .WithView(Products(current, previous), skin => skin.WithXs(6).WithSm(3)) - .WithView(Orders(current, previous), skin => skin.WithXs(6).WithSm(3)) - .WithView(Customers(current, previous), skin => skin.WithXs(6).WithSm(3)) - ; - }) - ; - } - - /// - /// Renders the summary charts for the annual report. - /// - /// The layout area host. - /// The rendering context. - /// An object representing the rendered summary charts. - public static UiControl SummaryCharts(LayoutAreaHost layoutArea, RenderingContext context) - => Controls.LayoutGrid - .WithView( - Controls.Stack.WithView(Controls.H2("Top Clients")).WithView(TopClientsArea.TopClients) - .WithVerticalGap(10), - skin => skin.WithXs(12).WithSm(6)) - .WithView( - Controls.Stack.WithView(Controls.H2("Top products")).WithView(TopProductsArea.TopProducts) - .WithVerticalGap(10), - skin => skin.WithXs(12).WithSm(6)) - ; - - private static IObservable> SummaryItems(this LayoutAreaHost layoutArea) - => layoutArea.GetNorthwindDataCubeData() - .Select(d => - d.GroupBy(x => x.OrderYear) - .Select(g => new SummaryItem - { - Year = g.Key, - Customers = g.Select(x => x.Customer).Distinct().Count(), - Products = g.Sum(x => x.Quantity), - Sales = g.Sum(x => x.Amount), - Orders = g.DistinctBy(x => x.OrderId).Count() - }) - .ToArray() - ) - ; - - private static UiControl Customers(SummaryItem current, SummaryItem? previous) => - ValueBox( - "Customers", - FluentIcons.Person(), - current.Customers.ToSuffixFormat(), - previous is not null ? GrowthPercentage(current.Customers, previous.Customers) : null - ); - - private static UiControl Sales(SummaryItem current, SummaryItem? previous) => - ValueBox( - "Sales", - FluentIcons.WalletCreditCard(), - "$" + current.Sales.ToSuffixFormat(), - previous is not null ? GrowthPercentage(current.Sales, previous.Sales) : null - ); - - private static UiControl Products(SummaryItem current, SummaryItem? previous) => - ValueBox( - "Products", - FluentIcons.Box(), - current.Products.ToSuffixFormat(), - previous is not null ? GrowthPercentage(current.Products, previous.Products) : null - ); - - private static UiControl Orders(SummaryItem current, SummaryItem? previous) => - ValueBox( - "Orders", - FluentIcons.Checkmark(), - current.Orders.ToSuffixFormat(), - previous is not null ? GrowthPercentage(current.Orders, previous.Orders) : null - ); -} - -/// -/// Represents a summary item for the annual report. -/// -public record SummaryItem -{ - /// - /// Gets the year of the summary item. - /// - public int Year { get; init; } - /// - /// Gets the number of customers in the summary item. - /// - public int Customers { get; init; } - /// - /// Gets the number of products in the summary item. - /// - public int Products { get; init; } - /// - /// Gets the total sales in the summary item. - /// - public double Sales { get; init; } - /// - /// Gets the number of orders in the summary item. - /// - public int Orders { get; init; } -} diff --git a/samples/Northwind/MeshWeaver.Northwind.Application/CategoryToolbarArea.cs b/samples/Northwind/MeshWeaver.Northwind.Application/CategoryToolbarArea.cs deleted file mode 100644 index 63b4c66612..0000000000 --- a/samples/Northwind/MeshWeaver.Northwind.Application/CategoryToolbarArea.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Reactive.Linq; -using MeshWeaver.Data; -using MeshWeaver.Domain; -using MeshWeaver.Layout; -using MeshWeaver.Layout.Composition; -using MeshWeaver.Northwind.Domain; - -namespace MeshWeaver.Northwind.Application; - -/// -/// Represents a toolbar for category selection with a specific category. -/// -/// The category ID. -public record CategoryToolbar([property: Dimension] int Category); - -/// -/// Provides extension methods for creating category selection toolbars. -/// -public static class CategoryToolbarArea -{ - /// - /// Creates a category selection toolbar for the specified layout area and rendering context. - /// - /// The layout area host. - /// The rendering context. - /// An object representing the category selection toolbar. - public static UiControl CategoryToolbar(this LayoutAreaHost layoutArea, RenderingContext context) - { - var _ = context; - return Controls.Toolbar.WithView((_, _) => - layoutArea.GetAvailableCategories() - .Select(categories => - Template.Bind(new CategoryToolbar(0), - tb => - Controls.Select( - tb.Category, - categories.Select>(c => - new Option(c.CategoryId, c.CategoryName)) - .Prepend(new Option(0, "All Categories")) - .Cast