<?xml version="1.0"?>
<doc>
    <assembly>
        <name>Jupiter.Market.Elastic</name>
    </assembly>
    <members>
        <member name="M:Jupiter.Market.Elastic.Titles.Helpers.GeoHelper.ValidateAndRepairGeometry(NetTopologySuite.Geometries.Geometry,System.Boolean,Microsoft.Extensions.Logging.ILogger)">
            <summary>
            Validates and repairs a geometry if possible, ensuring Elasticsearch geo_shape compliance.
            </summary>
            <param name="geom">The geometry to validate and repair.</param>
            <param name="validateWgs84Bounds">If true, validates coordinates are within WGS84 bounds (only use for WGS84 geometries).</param>
            <param name="logger">Optional logger for warnings.</param>
            <returns>A valid geometry, or null if the geometry cannot be repaired.</returns>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Helpers.GeoHelper.EnsureElasticsearchCompliance(NetTopologySuite.Geometries.Geometry,System.Boolean,Microsoft.Extensions.Logging.ILogger)">
            <summary>
            Ensures geometry meets Elasticsearch geo_shape requirements.
            </summary>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Helpers.GeoHelper.EnsureElasticsearchPolygonCompliance(NetTopologySuite.Geometries.Polygon,System.Boolean,Microsoft.Extensions.Logging.ILogger)">
            <summary>
            Ensures a polygon meets Elasticsearch requirements (orientation, no self-intersections, valid coordinates).
            </summary>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Helpers.GeoHelper.ValidateCoordinateBounds(NetTopologySuite.Geometries.Geometry,Microsoft.Extensions.Logging.ILogger)">
            <summary>
            Validates that all coordinates are within valid WGS84 bounds.
            </summary>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Models.ElasticsearchSettings">
            <summary>
            Configuration settings for Elasticsearch connection and indexing behavior.
            </summary>
            <remarks>
            These settings are typically loaded from appsettings.json under the "Elasticsearch" section.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ElasticsearchSettings.CloudId">
            <summary>
            Gets or sets the Elasticsearch Cloud ID for cloud-based deployments.
            </summary>
            <remarks>
            Format: "deployment-name:base64-encoded-data"
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ElasticsearchSettings.ApiKey">
            <summary>
            Gets or sets the API key for Elasticsearch authentication.
            </summary>
            <remarks>
            This should be a base64-encoded API key with appropriate index permissions.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ElasticsearchSettings.IndexName">
            <summary>
            Gets or sets the name of the Elasticsearch index to use.
            </summary>
            <example>jupiter-titles</example>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ElasticsearchSettings.DeleteIndex">
            <summary>
            Gets or sets a value indicating whether to delete the existing index before creating a new one.
            </summary>
            <remarks>
            WARNING: Setting this to true will permanently delete all existing data in the index.
            Use with caution, typically only in development or for full re-indexing scenarios.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ElasticsearchSettings.CreateIndex">
            <summary>
            Gets or sets a value indicating whether to create the index if it doesn't exist.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ElasticsearchSettings.BatchSize">
            <summary>
            Gets or sets the number of documents to process in each batch operation.
            </summary>
            <remarks>
            Typical values: 500-5000. Higher values = faster indexing but more memory usage.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ElasticsearchSettings.Mode">
            <summary>
            Gets or sets the indexing mode (full, incremental, planning, missing).
            </summary>
            <remarks>
            Valid values: "full", "incremental", "planning", "missing"
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ElasticsearchSettings.QueryJson">
            <summary>
            Gets or sets the Elasticsearch query JSON for the elastic-query command.
            </summary>
            <remarks>
            This should contain a valid Elasticsearch query DSL JSON that will be used
            to filter documents when using the 'elastic-query' command.
            </remarks>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Models.PostgresSettings">
            <summary>
            Configuration settings for PostgreSQL database connection.
            </summary>
            <remarks>
            These settings are typically loaded from appsettings.json under the "Postgres" section.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.PostgresSettings.ConnectionString">
            <summary>
            Gets or sets the PostgreSQL connection string.
            </summary>
            <example>Host=localhost;Database=jupiter;Username=user;Password=pass;SSL Mode=Require;</example>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.PostgresSettings.SemaphoreLimit">
            <summary>
            Gets or sets the maximum number of concurrent database connections allowed.
            </summary>
            <remarks>
            Default is 5. Adjust based on database server capacity and application load.
            Higher values allow more parallel queries but consume more database resources.
            </remarks>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Models.IndexerSettings">
            <summary>
            Configuration settings for the indexer service behavior.
            </summary>
            <remarks>
            These settings are typically loaded from appsettings.json under the "Indexer" section.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.IndexerSettings.OffsetFilePath">
            <summary>
            Gets or sets the file path for storing the last processed title number (offset tracking).
            </summary>
            <remarks>
            This file enables resumable indexing by tracking progress. Default: "./offset.log"
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.IndexerSettings.LogProgressCounter">
            <summary>
            Gets or sets how frequently to log progress (every N messages processed).
            </summary>
            <remarks>
            For example, if set to 100, progress will be logged every 100 messages.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.IndexerSettings.MaxDegreeOfParallelism">
            <summary>
            Gets or sets the maximum number of concurrent Service Bus message processing operations.
            </summary>
            <remarks>
            Default is 10. Controls how many titles can be indexed in parallel.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.IndexerSettings.NormalQueueProcessingStartTime">
            <summary>
            Gets or sets the hour (0-23) when normal queue processing should start.
            </summary>
            <remarks>
            Default is 20 (8 PM). High-priority queue runs 24/7, normal queue runs during this window.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.IndexerSettings.NormalQueueProcessingEndTime">
            <summary>
            Gets or sets the hour (0-23) when normal queue processing should end.
            </summary>
            <remarks>
            Default is 8 (8 AM). Creates a processing window from NormalQueueProcessingStartTime to this hour.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.IndexerSettings.ManualFilePath">
            <summary>
            Gets or sets the file path containing title IDs for manual reprocessing.
            </summary>
            <remarks>
            File should contain one title ID (GUID) per line.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.IndexerSettings.MaxQueryMetricsCount">
            <summary>
            Gets or sets the maximum number of query metrics to retain in memory.
            </summary>
            <remarks>
            Default is 50. When query metrics exceed this count, the least frequently used metrics are removed.
            Higher values track more queries but consume more memory.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.IndexerSettings.QueryMetricsCleanupInterval">
            <summary>
            Gets or sets how frequently to clean up query metrics (every N messages processed).
            </summary>
            <remarks>
            Default is 10000. Cleanup runs after processing this many messages to prevent unbounded memory growth.
            </remarks>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Models.IndexMessageStatus">
            <summary>
            Represents the status of a message processed during indexing operations.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.IndexMessageStatus.IsSuccess">
            <summary>
            Gets or sets a value indicating whether the operation was successful.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.IndexMessageStatus.Message">
            <summary>
            Gets or sets a descriptive message about the operation result.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.IndexMessageStatus.MessageAction">
            <summary>
            Gets or sets the action taken for the message (e.g., Completed, DeadLettered).
            </summary>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Models.ServiceBusOffset">
            <summary>
            Tracks the last processed Service Bus message for resumable operations.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ServiceBusOffset.LastModified">
            <summary>
            Gets or sets the last modified date processed.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ServiceBusOffset.LastCreated">
            <summary>
            Gets or sets the last created date processed.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ServiceBusOffset.LastId">
            <summary>
            Gets or sets the last processed title ID.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ServiceBusOffset.InitialNullModifiedDateProcessed">
            <summary>
            Gets or sets a value indicating whether the initial null modified date phase has been processed.
            </summary>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Models.ServiceBusSettings">
            <summary>
            Configuration settings for Azure Service Bus integration.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ServiceBusSettings.ConnectionString">
            <summary>
            Gets or sets the Service Bus connection string.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ServiceBusSettings.TopicName">
            <summary>
            Gets or sets the name of the Service Bus topic for title updates.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ServiceBusSettings.SubscriptionName">
            <summary>
            Gets or sets the name of the normal priority subscription.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ServiceBusSettings.HighPrioritySubscriptionName">
            <summary>
            Gets or sets the name of the high-priority subscription.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ServiceBusSettings.ServiceBusLastRunDateFilePath">
            <summary>
            Gets or sets the file path for tracking the last processed Service Bus message.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ServiceBusSettings.DealTopicName">
            <summary>
            Gets or sets the name of the Service Bus topic for deal updates.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.ServiceBusSettings.MaxAutoLockRenewalDurationMinutes">
            <summary>
            Gets or sets the maximum duration for which the message lock will be renewed automatically.
            Default is 10 minutes. This prevents MessageLockLost exceptions for long-running message processing.
            </summary>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Models.TitleReference">
            <summary>
            Represents the base reference information for a property title retrieved from PostgreSQL.
            </summary>
            <remarks>
            This class is used as an intermediate data transfer object when retrieving title data
            from the database before mapping to the full Title object for Elasticsearch indexing.
            </remarks>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Models.Title">
            <summary>
            Represents a complete property title document for Elasticsearch indexing in the <see cref="!:xref:Elastic.TitlesIndex"/>.
            </summary>
            <remarks>
            <para>This is the root document type indexed in Elasticsearch. It contains all information
            about a property title including:</para>
            <list type="bullet">
            <item>Basic title information (number, area, tenure) from <see cref="!:xref:Table.Title"/></item>
            <item>Address details from <see cref="!:xref:Table.AddressGiven"/></item>
            <item>Historical sales data from <see cref="!:xref:Table.Sale"/></item>
            <item>Ownership information from <see cref="!:xref:Table.Ownership"/></item>
            <item>Buildings from <see cref="!:xref:Table.Building"/> and UPRNs from <see cref="!:xref:Table.Uprn"/> (nested hierarchy)</item>
            <item>Planning applications from <see cref="!:xref:Table.PlanningApplication"/></item>
            <item>Land constraints from <see cref="!:xref:Table.Constraint"/></item>
            <item>Lease information from <see cref="!:xref:Table.Lease"/></item>
            <item>Geospatial data (polygon geometry and centroid) from <see cref="!:xref:Table.TitleGeometry"/></item>
            </list>
            <para>The class uses <see cref="N:Nest"/> attributes to define Elasticsearch field mappings.</para>
            <para>See also: <see href="../articles/technical/data-model.md">Data Model</see></para>
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.Title.Id">
            <summary>
            Gets or sets the unique identifier for the title.
            </summary>
            <remarks>
            This GUID serves as the Elasticsearch document ID and primary key.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.Title.Number">
            <summary>
            Gets or sets the official title number (e.g., "DN123456").
            </summary>
            <remarks>
            This is the Land Registry title number and is indexed as full-text searchable.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.Title.BuildingFootprint">
            <summary>
            Gets the total building footprint in square feet by summing all building footprints.
            </summary>
            <remarks>
            This is a calculated property that aggregates footprint areas from all buildings on the title.
            Returns 0 if there are no buildings.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.Title.SiteCoverage">
            <summary>
            Gets the site coverage percentage (building footprint / total site area * 100).
            </summary>
            <remarks>
            This calculated property indicates what percentage of the site is covered by buildings.
            Returns null if building footprint or area is not available or area is zero.
            </remarks>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.Uprn.Postcode">
            <summary>Postcode as structured feature (aligned with <see cref="P:Jupiter.Market.Elastic.Titles.Models.Address.Postcode"/>).</summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.UseClassItem.Description">
            <summary>Generic planning use class description from NewPlanningUseCode (e.g. "General industry").</summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.UseClassItem.ClassificationDescription">
            <summary>Specific AddressBase/VOA description from Classification (e.g. "Recycling Site").</summary>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Models.TitleIdOnly">
            <summary>
            Represents a minimal projection of a title for queueing and indexing operations.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.TitleIdOnly.Id">
            <summary>
            Gets or sets the unique identifier for the title.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.TitleIdOnly.ModifiedDate">
            <summary>
            Gets or sets the last modified date of the title record.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.TitleIdOnly.CreatedDate">
            <summary>
            Gets or sets the created date of the title record.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.TitleIdOnly.Priority">
            <summary>
            Gets or sets the processing priority for the title (e.g., High, Medium).
            </summary>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Models.MessageTitle">
            <summary>
            Represents a message payload for Service Bus title update events.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.MessageTitle.Id">
            <summary>
            Gets or sets the unique identifier for the title.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.MessageTitle.Priority">
            <summary>
            Gets or sets the processing priority for the message (default: Medium).
            </summary>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Models.TitleNumberOnly">
            <summary>
            Represents a projection containing only the title number for lightweight queries.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Models.TitleNumberOnly.Number">
            <summary>
            Gets or sets the official title number (e.g., "DN123456").
            </summary>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Services.DataService">
            <summary>
            Provides data access services for retrieving and processing title information from PostgreSQL.
            Handles connection management, query execution, retry logic, and performance tracking.
            </summary>
            <remarks>
            <para>This service uses Dapper for data access, Polly for retry policies, and NetTopologySuite for geospatial data.
            All queries support cancellation and optional retry logic for transient failures.</para>
            <para>See also: <see href="../articles/technical/data-retrieval.md">Data Retrieval Guide</see></para>
            </remarks>
        </member>
        <member name="F:Jupiter.Market.Elastic.Titles.Services.DataService.QueryMetrics">
            <summary>
            Gets the concurrent dictionary tracking performance metrics for all executed queries.
            </summary>
            <remarks>
            Metrics are tracked by query key and include min, max, average execution times and run counts.
            </remarks>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.DataService.CleanupOldMetrics(System.Int32)">
            <summary>
            Cleans up query metrics that haven't been used recently to prevent unbounded memory growth.
            Keeps metrics for queries that have been run recently or have significant data.
            </summary>
            <param name="maxMetrics">Maximum number of query metrics to retain. Defaults to 50.</param>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Services.DataService.QueryPerformanceMetrics">
            <summary>
            Tracks performance metrics for database queries including execution times and run counts.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Services.DataService.QueryPerformanceMetrics.MinDurationMs">
            <summary>
            Gets or sets the minimum query execution time in milliseconds.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Services.DataService.QueryPerformanceMetrics.MaxDurationMs">
            <summary>
            Gets or sets the maximum query execution time in milliseconds.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Services.DataService.QueryPerformanceMetrics.TotalDurationMs">
            <summary>
            Gets or sets the total accumulated execution time in milliseconds.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Services.DataService.QueryPerformanceMetrics.RunCount">
            <summary>
            Gets or sets the total number of times this query has been executed.
            </summary>
        </member>
        <member name="P:Jupiter.Market.Elastic.Titles.Services.DataService.QueryPerformanceMetrics.AverageDurationMs">
            <summary>
            Gets the average query execution time in milliseconds.
            </summary>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.DataService.QueryPerformanceMetrics.Update(System.Int64)">
            <summary>
            Updates the metrics with a new query execution duration.
            </summary>
            <param name="duration">The execution duration in milliseconds.</param>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.DataService.InitAsync">
            <summary>
            Initializes the DataService by setting up the PostgreSQL connection pool and verifying PostGIS connectivity.
            </summary>
            <returns>A task representing the asynchronous initialization operation.</returns>
            <exception cref="T:System.Exception">Thrown when initialization fails after retries.</exception>
            <remarks>
            This method:
            - Creates an NpgsqlDataSource with NetTopologySuite support for geospatial data
            - Configures type mappings (numeric to double)
            - Verifies PostGIS is available by executing a version query
            - Retries up to 3 times with exponential backoff (2s, 4s, 6s) for Azure PostgreSQL readiness
            </remarks>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.DataService.GetConnectionAsync">
            <summary>
            Opens and returns a new PostgreSQL connection from the connection pool.
            </summary>
            <returns>An open NpgsqlConnection ready for use.</returns>
            <exception cref="T:System.Exception">Thrown when connection cannot be established.</exception>
            <remarks>
            Connections are managed by the NpgsqlDataSource pool and should be disposed after use.
            </remarks>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.DataService.RunQueryAsync``1(System.String,System.Object,System.Boolean,System.Threading.CancellationToken,System.String)">
            <summary>
            Executes a SQL query and returns the results as a collection of typed objects.
            </summary>
            <typeparam name="T">The type to map query results to.</typeparam>
            <param name="query">The SQL query to execute.</param>
            <param name="parameters">Anonymous object containing query parameters.</param>
            <param name="useRetry">If true, applies Polly retry policy for transient failures.</param>
            <param name="cancellationToken">Cancellation token for the operation.</param>
            <param name="queryKey">Optional key for performance tracking.</param>
            <returns>A collection of objects of type T.</returns>
            <exception cref="T:Npgsql.NpgsqlException">Thrown when query fails after retries (if enabled).</exception>
            <exception cref="T:System.TimeoutException">Thrown when query times out after retries (if enabled).</exception>
            <remarks>
            When useRetry is true, the query will be retried up to 3 times with exponential backoff (2s, 4s, 8s)
            for transient PostgreSQL exceptions and timeouts. Command timeout is set to 90 seconds.
            </remarks>
            <example>
            <code>
            var sales = await RunQueryAsync&lt;Jupiter.Market.Elastic.Titles.Models.Sale&gt;(
                "SELECT * FROM market.\"Sale\" WHERE \"TitleId\" = @TitleId",
                new { TitleId = titleId },
                useRetry: true,
                cancellationToken
            );
            </code>
            </example>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.DataService.RunQueryByKeyAsync``1(System.String,System.Object,System.Boolean,System.Threading.CancellationToken)">
            <summary>
            Executes a pre-defined query from QueryConstants and tracks its performance metrics.
            </summary>
            <typeparam name="T">The type to map query results to.</typeparam>
            <param name="queryKey">The key identifying the query in QueryConstants.Queries dictionary.</param>
            <param name="parameters">Anonymous object containing query parameters.</param>
            <param name="useRetry">If true, applies Polly retry policy for transient failures.</param>
            <param name="cancellationToken">Cancellation token for the operation.</param>
            <returns>A collection of objects of type T.</returns>
            <exception cref="T:System.ArgumentException">Thrown when queryKey is not found in <see cref="F:Jupiter.Market.Elastic.Titles.Constants.QueryConstants.Queries"/>.</exception>
            <exception cref="T:Npgsql.NpgsqlException">Thrown when query fails after retries (if enabled).</exception>
            <remarks>
            This method automatically tracks query performance metrics (min, max, average execution time, run count)
            in the QueryMetrics dictionary. Metrics are updated in a thread-safe manner.
            </remarks>
            <example>
            <code>
            var sales = await RunQueryByKeyAsync&lt;Jupiter.Market.Elastic.Titles.Models.Sale&gt;(
                "sales",
                new { TitleId = titleId },
                useRetry: true,
                cancellationToken
            );
            </code>
            </example>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.DataService.GetUprnsByTitleIdsAsync(System.Guid,System.Threading.CancellationToken)">
            <summary>
            Retrieves all UPRNs (Unique Property Reference Numbers) for a title with comprehensive enrichment data.
            </summary>
            <param name="titleId">The unique identifier of the title.</param>
            <param name="cancellationToken">Cancellation token for the operation.</param>
            <returns>A dictionary mapping building IDs to lists of enriched UPRN objects.</returns>
            <remarks>
            <para>This method executes a complex CTE query that:</para>
            <list type="bullet">
            <item>Identifies all UPRNs associated with the title and their parent buildings from <see cref="!:xref:Table.Uprn"/></item>
            <item>Aggregates rateable values from multiple assessments from <see cref="!:xref:Table.RateAssessment"/></item>
            <item>Selects the most reliable size data based on source priority (VOA > EPC > RM > ZP > AB)</item>
            <item>Selects the most reliable room counts based on source priority</item>
            <item>Retrieves the latest EPC (Energy Performance Certificate) ratings from <see cref="!:xref:Table.EnergyPerformanceCertificate"/></item>
            <item>Aggregates VOA classifications as JSON from <see cref="!:xref:Table.Classification"/></item>
            <item>Aggregates planning use classes as JSON</item>
            <item>Concatenates floor levels in sorted order</item>
            </list>
            <para>The returned dictionary groups UPRNs by their parent building ID. UPRNs without buildings
            are assigned to a zero GUID building ID.</para>
            <para>See also: <see href="../articles/technical/data-retrieval.md">Data Retrieval Guide</see></para>
            </remarks>
            <example>
            <code>
            var uprnsByBuilding = await GetUprnsByTitleIdsAsync(titleId, cancellationToken);
            foreach (var (buildingId, uprns) in uprnsByBuilding)
            {
                Console.WriteLine($"Building {buildingId} has {uprns.Count} UPRNs");
            }
            </code>
            </example>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.DataService.GetTitleReferenceByIdAsync(System.Guid,System.Threading.CancellationToken)">
            <summary>
            Retrieves the base title reference information including area measurements and address details.
            </summary>
            <param name="titleId">The unique identifier of the title.</param>
            <param name="cancellationToken">Cancellation token for the operation.</param>
            <returns>A <see cref="T:Jupiter.Market.Elastic.Titles.Models.TitleReference"/> object containing base title information, or null if not found.</returns>
            <remarks>
            <para>This method retrieves core title information from <see cref="!:xref:Table.Title"/> including:</para>
            <list type="bullet">
            <item>Title number and areas (square meters, square feet, acres)</item>
            <item>Tenure type (Freehold, Leasehold)</item>
            <item>Property category and types</item>
            <item>Complete address components from <see cref="!:xref:Table.AddressGiven"/> (building number, street, town, county, postcode)</item>
            </list>
            <para>The method handles flexible PropertyType mapping, supporting List&lt;string&gt;, string[],
            or single string values from the database.</para>
            <para>See also: <see href="../articles/technical/data-retrieval.md">Data Retrieval Guide</see></para>
            </remarks>
            <example>
            <code>
            var titleRef = await GetTitleReferenceByIdAsync(titleId, cancellationToken);
            if (titleRef != null)
            {
                Console.WriteLine($"Title: {titleRef.Number}, Area: {titleRef.AreaInSquareFeet} sq ft");
            }
            </code>
            </example>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Services.ElasticIndexService">
            <summary>
            Provides services for managing Elasticsearch index operations including document indexing,
            index creation/deletion, data validation, and efficient hash-based change detection for upserts.
            </summary>
            <remarks>
            <para>This service uses the NEST client library to interact with Elasticsearch Cloud.
            It handles index lifecycle management, bulk operations, and maintains synchronization
            between PostgreSQL and the <see cref="!:xref:Elastic.TitlesIndex"/>.</para>
            <para>Efficient upserts are performed using a hash-based change detection strategy:
            a deterministic hash is computed for each document and stored in Elasticsearch. Only documents
            with missing or changed hashes are re-indexed, avoiding unnecessary updates and per-document GETs.</para>
            <para>See also: <see href="../articles/technical/architecture.md">Architecture Overview</see></para>
            </remarks>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.ElasticIndexService.InitAsync">
            <summary>
            Initializes the Elasticsearch client with cloud connection settings and default mappings.
            </summary>
            <returns>A task representing the asynchronous initialization operation.</returns>
            <remarks>
            <para>This method configures the NEST client with:</para>
            <list type="bullet">
            <item>Cloud connection pool using CloudId and API key authentication</item>
            <item>Default index name from settings</item>
            <item>Debug mode for detailed logging</item>
            <item>Custom field name inference (camelCase)</item>
            <item>Default mapping for <see cref="T:Jupiter.Market.Elastic.Titles.Models.Title"/> type</item>
            </list>
            <para>See also: <see href="../articles/technical/configuration.md">Configuration Guide</see></para>
            </remarks>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.ElasticIndexService.UpsertIndex(Jupiter.Market.Elastic.Titles.Models.Title)">
            <summary>
            Upserts (inserts or updates) a single title document in the <see cref="!:xref:Elastic.TitlesIndex"/> using hash-based change detection.
            Also sends a Deal update message to the Service Bus with a randomized priority ("High" or "Medium") after a successful upsert.
            </summary>
            <param name="title">The <see cref="T:Jupiter.Market.Elastic.Titles.Models.Title"/> object to index.</param>
            <returns>A <see cref="T:Nest.IndexResponse"/> if the document was indexed, or null if no changes were detected.</returns>
            <remarks>
            <para>
            This method computes a deterministic hash for the document and delegates to <see cref="M:Jupiter.Market.Elastic.Titles.Services.ElasticIndexService.BulkUpsertTitlesAsync(System.Collections.Generic.List{Jupiter.Market.Elastic.Titles.Models.Title})"/>.
            It avoids per-document GETs and deep JSON comparisons by using a hash field (<c>DocHash</c>) stored in Elasticsearch.
            If the hash matches the existing document, no re-indexing is performed.
            </para>
            <para>
            After a successful upsert, a message is sent to the Deal Service Bus topic to notify the Deal service of the update.
            The message includes the Deal ID and a randomized priority ("High" or "Medium").
            </para>
            <para>See also: <see href="../articles/technical/data-model.md">Data Model</see></para>
            </remarks>
            <example>
            <code>
            var response = await UpsertIndex(title);
            if (response != null)
            {
                _logger.LogInformation($"Indexed title {title.Id}");
            }
            else
            {
                _logger.LogInformation($"No changes detected for title {title.Id}, skipping re-index.");
            }
            // A Deal update message is also sent to the Service Bus with randomized priority.
            </code>
            </example>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.ElasticIndexService.BulkUpsertTitlesAsync(System.Collections.Generic.List{Jupiter.Market.Elastic.Titles.Models.Title})">
            <summary>
            Bulk upserts title documents using hash-based change detection.
            </summary>
            <param name="titles">List of <see cref="T:Jupiter.Market.Elastic.Titles.Models.Title"/> documents to upsert.</param>
            <returns>A <see cref="T:Nest.BulkResponse"/> from Elasticsearch, or null if no documents required indexing.</returns>
            <remarks>
            <para>
            This method computes a deterministic hash (DocHash) for each document using a canonical JSON representation.
            It then fetches existing hashes from Elasticsearch in a single ids query (source filtering only for ID and DocHash).
            Only documents whose hash is missing or different are included in the bulk index operation.
            This avoids unnecessary re-indexing and eliminates per-document GETs or deep JSON comparisons.
            </para>
            <para>
            The hash is stored in the <c>DocHash</c> field of each document in Elasticsearch.
            </para>
            </remarks>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.ElasticIndexService.DeleteFromIndex(System.Guid)">
            <summary>
            Deletes a title document from the <see cref="!:xref:Elastic.TitlesIndex"/>.
            </summary>
            <param name="titleId">The unique identifier of the title to delete.</param>
            <returns>A task representing the asynchronous delete operation.</returns>
            <remarks>
            <para>This method silently handles exceptions to prevent failures when deleting non-existent documents.
            Used to clean up stale documents when titles are removed from <see cref="!:xref:Table.Title"/>.</para>
            </remarks>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.ElasticIndexService.QueryAndSendToServiceBusAsync(System.Threading.CancellationToken)">
            <summary>
            Queries Elasticsearch using the query from configuration and sends matching document IDs to Service Bus.
            </summary>
            <param name="cancellationToken">Cancellation token for the operation.</param>
            <returns>A task representing the asynchronous operation.</returns>
            <remarks>
            <para>This method:</para>
            <list type="number">
            <item>Parses the QueryJson from ElasticsearchSettings configuration</item>
            <item>Executes a scroll query against the Elasticsearch index</item>
            <item>Streams document IDs in batches to Service Bus via ServiceBusMessageService</item>
            <item>Logs progress periodically</item>
            </list>
            <para>The query can potentially match millions of documents and uses Elasticsearch scroll API
            for efficient streaming without loading all results into memory.</para>
            </remarks>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Services.IMessageActions">
            <summary>
            Defines actions for handling Azure Service Bus messages in the indexing workflow.
            </summary>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.IMessageActions.CompleteMessageAsync(Azure.Messaging.ServiceBus.ServiceBusReceivedMessage,System.Threading.CancellationToken)">
            <summary>
            Completes the specified Service Bus message, removing it from the queue.
            </summary>
            <param name="message">The message to complete.</param>
            <param name="cancellationToken">A cancellation token.</param>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.IMessageActions.AbandonMessageAsync(Azure.Messaging.ServiceBus.ServiceBusReceivedMessage,System.Collections.Generic.IDictionary{System.String,System.Object},System.Threading.CancellationToken)">
            <summary>
            Abandons the specified message, making it available for reprocessing.
            </summary>
            <param name="message">The message to abandon.</param>
            <param name="propertiesToModify">Optional properties to modify on the message.</param>
            <param name="cancellationToken">A cancellation token.</param>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.IMessageActions.DeadLetterMessageAsync(Azure.Messaging.ServiceBus.ServiceBusReceivedMessage,System.String,System.String,System.Threading.CancellationToken)">
            <summary>
            Moves the specified message to the dead-letter queue with a reason and optional description.
            </summary>
            <param name="message">The message to dead-letter.</param>
            <param name="deadLetterReason">The reason for dead-lettering.</param>
            <param name="deadLetterErrorDescription">An optional error description.</param>
            <param name="cancellationToken">A cancellation token.</param>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.IMessageActions.DeferMessageAsync(Azure.Messaging.ServiceBus.ServiceBusReceivedMessage,System.Collections.Generic.IDictionary{System.String,System.Object},System.Threading.CancellationToken)">
            <summary>
            Defers the specified message for later processing.
            </summary>
            <param name="message">The message to defer.</param>
            <param name="propertiesToModify">Optional properties to modify on the message.</param>
            <param name="cancellationToken">A cancellation token.</param>
        </member>
        <member name="T:Jupiter.Market.Elastic.Titles.Services.IndexerService">
            <summary>
            Orchestrates the indexing process for property titles, managing Service Bus message processing,
            data retrieval, and Elasticsearch indexing operations.
            </summary>
            <remarks>
            <para>This service coordinates between <see cref="T:Jupiter.Market.Elastic.Titles.Services.DataService"/> (PostgreSQL), <see cref="T:Jupiter.Market.Elastic.Titles.Services.ElasticIndexService"/> (Elasticsearch),
            and <see cref="T:Jupiter.Market.Elastic.Titles.Services.ServiceBusMessageService"/> (Azure Service Bus) to process title updates in real-time or batch mode.
            Supports high-priority and regular message processing with configurable time windows.</para>
            <para>See also: <see href="../articles/technical/architecture.md">Architecture Overview</see></para>
            </remarks>
        </member>
        <member name="M:Jupiter.Market.Elastic.Titles.Services.IndexerService.IndexTitleByIdAsync(System.Guid,System.Threading.CancellationToken)">
            <summary>
            Indexes a single title by retrieving all related data from PostgreSQL and upserting it into the <see cref="!:xref:Elastic.TitlesIndex"/>.
            </summary>
            <param name="titleId">The unique identifier of the title to index.</param>
            <param name="cancellationToken">Cancellation token for the operation.</param>
            <returns>
            A tuple containing:
            - bool: True if indexing succeeded, false otherwise
            - string: Error message if indexing failed, empty string if successful
            </returns>
            <remarks>
            <para>This method performs the following steps:</para>
            <list type="number">
            <item>Retrieves the base title reference from <see cref="!:xref:Table.Title"/></item>
            <item>Fetches all related data in parallel (sales from <see cref="!:xref:Table.Sale"/>, owners from <see cref="!:xref:Table.Ownership"/>, planning from <see cref="!:xref:Table.PlanningApplication"/>, buildings from <see cref="!:xref:Table.Building"/>, UPRNs from <see cref="!:xref:Table.Uprn"/>, geometry from <see cref="!:xref:Table.TitleGeometry"/>, leases from <see cref="!:xref:Table.Lease"/>, constraints from <see cref="!:xref:Table.Constraint"/>)</item>
            <item>Processes geometry data (coordinate transformation, centroid calculation)</item>
            <item>Builds the building-UPRN hierarchy</item>
            <item>Constructs the complete <see cref="T:Jupiter.Market.Elastic.Titles.Models.Title"/> object</item>
            <item>Upserts the document into Elasticsearch</item>
            </list>
            <para>If the title is not found in PostgreSQL, any existing Elasticsearch document is deleted
            and the method returns success (not an error condition).</para>
            <para>All data retrieval operations execute in parallel for optimal performance.</para>
            <para>See also: <see href="../articles/technical/title-construction.md">Title Construction Process</see></para>
            </remarks>
            <example>
            <code>
            var (success, errorMessage) = await IndexTitleByIdAsync(titleId, cancellationToken);
            if (success)
            {
                _logger.LogInformation("Successfully indexed title {TitleId}", titleId);
            }
            else
            {
                _logger.LogError("Failed to index title {TitleId}: {Error}", titleId, errorMessage);
            }
            </code>
            </example>
        </member>
        <member name="T:DocumentHash">
            <summary>
            Provides deterministic, canonical hash computation for documents using JSON serialization.
            </summary>
            <remarks>
            <para>
            The hash is computed using SHA-256 over a canonical JSON representation of the document:
            <list type="bullet">
            <item>Property names are camelCase</item>
            <item>Null values are ignored</item>
            <item>Enums are serialized as camelCase strings</item>
            <item>Property order is stable</item>
            <item>No indentation or extra whitespace</item>
            </list>
            This is used for efficient change detection in Elasticsearch upserts.
            </para>
            </remarks>
        </member>
        <member name="F:DocumentHash.Options">
            <summary>
            The canonical JSON serializer options for hash computation.
            </summary>
        </member>
        <member name="M:DocumentHash.Compute``1(``0)">
            <summary>
            Computes a deterministic SHA-256 hash for the given document using canonical JSON serialization.
            </summary>
            <typeparam name="T">The type of the document.</typeparam>
            <param name="doc">The document to hash.</param>
            <returns>A lowercase hex string representing the SHA-256 hash of the canonical JSON.</returns>
        </member>
    </members>
</doc>
