<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="https://shazwazza.com/rss/xslt"?>
<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>Shazwazza</title>
    <link>https://shazwazza.com/</link>
    <description>My blog which is pretty much just all about coding</description>
    <generator>Articulate, blogging built on Umbraco</generator>
    <image>
      <url>/media/0libq25y/frog.png?rmode=max&amp;v=1da0e911f4e6890</url>
      <title>Shazwazza</title>
      <link>https://shazwazza.com/</link>
    </image>
    <item>
      <guid isPermaLink="false">1334</guid>
      <link>https://shazwazza.com/post/an-examine-fix-for-umbraco-index-corruption/</link>
      <category>Examine</category>
      <title>An Examine fix for Umbraco index corruption</title>
      <description>&lt;p&gt;A new Examine version 3.3.0 has been released to address a long awaited &lt;a href="https://github.com/umbraco/Umbraco-CMS/issues/16163"&gt;bug fix for Umbraco websites&lt;/a&gt; that use the &lt;a href="https://github.com/Shazwazza/Examine/blob/release/3.0/src/Examine.Lucene/Directories/SyncedFileSystemDirectoryFactory.cs"&gt;&lt;code&gt;SyncedFileSystemDirectoryFactory&lt;/code&gt;&lt;/a&gt; which is the default setting for Umbraco CMS.&lt;/p&gt;

&lt;p&gt;The bug typically means that indexes cannot be used and log entries such as &lt;code&gt;Lucene.Net.Index.CorruptIndexException: invalid deletion count: 2 vs docCount=1&lt;/code&gt; are present.&lt;/p&gt;

&lt;h2&gt;Understanding the problem:&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;SyncedFileSystemDirectoryFactory&lt;/code&gt; directory is there to avoid performance implications of rebuilding indexes on startup when a site is moved to another worker in Azure. The reason an index rebuild would occur is because in Azure, Lucene files need to work off of the local 'fast drive' (C:\%temp%), not the default/shared network 'slow drive' (D:), and whenever a site is moved, or spawned on a new worker in Azure, the local 'fast drive' is empty, meaning no indexes exist. The &lt;code&gt;SyncedFileSystemDirectoryFactory&lt;/code&gt; attempts to work around this challenge by continually synchronizing a copy of the indexes from the 'fast drive' to the 'slow drive' so that when a site is moved to another worker, it can sync (restore) from the 'slow drive' back to the 'fast drive' in order to avoid the index rebuild overhead.&lt;/p&gt;

&lt;p&gt;The problem with &lt;code&gt;SyncedFileSystemDirectoryFactory&lt;/code&gt; is that this implementation doesn't take into account what happens if the index files in your main storage ('slow drive') become corrupted which can happen for a number of reasons - misconfiguration, network latency, process termination, etc... &lt;/p&gt;

&lt;h2&gt;Understanding the solution:&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;SyncedFileSystemDirectoryFactory&lt;/code&gt; has been updated to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check the health of the main index if it exists ('slow drive').&lt;/li&gt;
&lt;li&gt;Check the health of the local index if it exists ('fast drive').&lt;/li&gt;
&lt;li&gt;If the main index is unhealthy or doesn't exist and the local index is healthy, it will synchronize the local index to the main index. This can occur only if a site hasn't moved to a new worker.&lt;/li&gt;
&lt;li&gt;If the main index is unhealthy and the local index doesn't exist or is unhealthy, then it will delete the main (corrupted) index.&lt;/li&gt;
&lt;li&gt;Once health checks are done, the index from main is always synced to local. If the main index was deleted due to corruption, this will mean that the local index is empty and an index rebuild will occur.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This change will attempt to keep any healthy index that is available (main vs local), but if nothing can be read, the indexes will be deleted and an &lt;strong&gt;index rebuild will occur&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There's also a new option to fix a corrupted index but this is not enabled by default since it can mean a loss of documents.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Shazwazza/Examine/blob/release/3.0/src/Examine.Lucene/Directories/SyncedFileSystemDirectoryFactory.cs"&gt;Source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Shazwazza/Examine/blob/release/3.0/src/Examine.Test/Examine.Lucene/Directories/SyncedFileSystemDirectoryFactoryTests.cs"&gt;Tests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Understanding the rebuilding overhead&lt;/h2&gt;

&lt;p&gt;The performance overhead of index rebuilding is due to the Umbraco database queries that need to be executed in order to populate the indexes. The only reason &lt;code&gt;SyncedFileSystemDirectoryFactory&lt;/code&gt; exists is to prevent this overhead when hosting in &lt;a href="https://azure.microsoft.com/en-us/products/app-service"&gt;Azure App Service&lt;/a&gt; (which is what &lt;a href="https://umbraco.com/products/umbraco-cloud/"&gt;Umbraco Cloud&lt;/a&gt; uses), and it can only be used on your Umbraco primary node. It does not prevent index rebuilding overhead for non-primary nodes when load balancing or scaling out because the main network 'slow drive' is shared between all workers and an index can only be read/written to be a single process. &lt;/p&gt;

&lt;p&gt;This means that it's only useful if you are hosting in Azure App Service without any load balancing while keeping in mind that it does not always prevent index rebuilds (see above).&lt;/p&gt;

&lt;p&gt;The index rebuilding overhead can be dramatic when load balancing or scaling out, for example: If you scale out to +5 nodes in a load balancing setup, that means that 5x nodes will be performing index rebuilds around the same time, this means that your DB is going to get hammered by queries to build all of those new indexes. The performance hit isn't the index building - it is the DB queries and this can lead to DB locks and lead to the dreaded SQL Lock Timeout issue in the Umbraco back office. Plus, if search is critical to your front-end, than for a while after your site has started up, there won't be any index which means there won't be any search until the background processing is done. &lt;/p&gt;

&lt;p&gt;... Many of these reasons is why &lt;a href="https://examinex.online/"&gt;ExamineX&lt;/a&gt; was created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reliably and centrally persisted indexes means nothing is out of sync between nodes.&lt;/li&gt;
&lt;li&gt;No index rebuilding when your site is moved or scaled = no rebuilding overhead.&lt;/li&gt;
&lt;li&gt;Prevents SQL Timeout locks due to DB rebuilding queries.&lt;/li&gt;
&lt;li&gt;Very easy to setup and seamlessly changes your Lucene based indexes to &lt;a href="https://azure.microsoft.com/en-us/products/ai-services/ai-search"&gt;Azure&lt;/a&gt;/&lt;a href="https://www.elastic.co/"&gt;Elastic&lt;/a&gt; search indexes.&lt;/li&gt;
&lt;li&gt;Ideal when hosting Umbraco on Azure Web Apps (or Umbraco Cloud) and a perfect solution for load balancing and scaling.&lt;/li&gt;
&lt;li&gt;Automatically index Umbraco media file content without the need for additional indexes with support for PDFs, Microsoft Office documents and more.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;(coming soon)&lt;/em&gt; Automatically generate Umbraco media image descriptions, tags, locations, and more using AI allowing your editors to quickly find the media/images they need.&lt;/li&gt;
&lt;/ul&gt;</description>
      <pubDate>Wed, 31 Jul 2024 17:49:54 Z</pubDate>
      <a10:updated>2024-07-31T17:49:54Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">1332</guid>
      <link>https://shazwazza.com/post/configuring-a-suggester-with-examinex-and-azure-ai-search/</link>
      <category>Examine</category>
      <title>Configuring a Suggester with ExamineX and Azure AI Search</title>
      <description>&lt;p&gt;We recently had a customer ask about integrating the &lt;a href="https://learn.microsoft.com/en-us/azure/search/index-add-suggesters"&gt;Azure AI Search suggester&lt;/a&gt; API with &lt;a href="https://examinex.online/"&gt;ExamineX&lt;/a&gt; and this turns out to be quite straight forward :)&lt;/p&gt;

&lt;p&gt;First thing is to ensure that the fields you want to use the Suggester for are configured correctly:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cs
// Configuration for the External Index for the City and Country fields.
// The Azure AI Search Suggester requires fields to be 'string fields only'
// and using the Standard Analyzer (default on the External Index)
services.Configure&amp;lt;AzureSearchIndexOptions&amp;gt;(
    Constants.UmbracoIndexes.ExternalIndexName,
    options =&amp;gt;
    {
         options.FieldDefinitions = new FieldDefinitionCollection(
            new FieldDefinition("City", AzureSearchFieldDefinitionTypes.FullText),
            new FieldDefinition("Country", AzureSearchFieldDefinitionTypes.FulText));
    });
&lt;/code&gt;
Next up, is to create the Suggester which is done as part of the &lt;a href="https://examinex.online/customization#events"&gt;CreatingOrUpdatingIndex&lt;/a&gt; event:&lt;/p&gt;

&lt;p&gt;```cs
externalIndex.CreatingOrUpdatingIndex += AzureIndex&lt;em&gt;CreatingOrUpdatingIndex&lt;/em&gt;ScoringProfile;&lt;/p&gt;

&lt;p&gt;private void AzureIndex&lt;em&gt;CreatingOrUpdatingIndex&lt;/em&gt;Suggester(object sender, CreatingOrUpdatingIndexEventArgs e)
{
    switch (e.EventType)
    {
        case IndexModifiedEventType.Creating:
        case IndexModifiedEventType.Rebuilding:
            var index = e.AzureSearchIndexDefinition;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;        var suggester = new SearchSuggester(
            "sg",
            new[] { "Country", "City" });

        index.Suggesters.Add(suggester);

        break;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;}
```
When data has been indexed for these fields, using the Suggester API is simple. For example, lets assume that the following ValueSets were indexed:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cs
externalIndex.IndexItems(new[]
{
    ValueSet.FromObject(id1, new { City = "Calgary", Country = "Canada" }),
    ValueSet.FromObject(id2, new { City = "Sydney", Country = "Australia" }),
    ValueSet.FromObject(id3, new { City = "Copenhagen", Country = "Denmark" }),
    ValueSet.FromObject(id4, new { City = "Vienna", Country = "Austria" }),
});
&lt;/code&gt;
Then to get suggestions for "Au":&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cs
// cast to AzureSearchIndex to expose underlying Azure Search APIs
var azureSearchIndex = (AzureSearchIndex)externalIndex;
var result = await  azureSearchIndex.IndexClient.SuggestAsync&amp;lt;ExamineDocument&amp;gt;(
    "Au",
    "sg");
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The result will contain 2x entries, one for the Australia and one for the Austria documents. Super easy!&lt;/p&gt;

&lt;p&gt;One thing to watch out for is if your index is not configured to use the Standard Analyzer by default (i.e. like the Internal Index in Umbraco). In that case, you'll need to define a custom field type with that indexer:&lt;/p&gt;

&lt;p&gt;```cs
services.Configure&lt;AzureSearchIndexOptions&gt;(
    Constants.UmbracoIndexes.ExternalIndexName,
    options =&gt;
    {
        // This adds a custom field type that uses the Standard Analyzer
        options.IndexValueTypesFactory = new Dictionary&lt;string, IAzureSearchFieldValueTypeFactory&gt;
        {
            ["standard"] = new AzureSearchFieldValueTypeFactory(s =&gt;
                new AzureSearchFieldValueType(s, SearchFieldDataType.String, LexicalAnalyzerName.StandardLucene.ToString()))
        };&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    // Set these field types to the one defined above
    options.FieldDefinitions = new FieldDefinitionCollection(
        new FieldDefinition("City", "standard"),
        new FieldDefinition("Country", "standard"));
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;```
Happy Searching!&lt;/p&gt;</description>
      <pubDate>Wed, 01 May 2024 14:51:52 Z</pubDate>
      <a10:updated>2024-05-01T14:51:52Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">1248</guid>
      <link>https://shazwazza.com/post/searching-with-ipublishedcontentquery-in-umbraco/</link>
      <category>Umbraco</category>
      <category>Examine</category>
      <title>Searching with IPublishedContentQuery in Umbraco</title>
      <description>&lt;p&gt;I recently realized that I don’t think Umbraco’s APIs on &lt;em&gt;IPublishedContentQuery&lt;/em&gt; are documented so hopefully this post may inspire some docs to be written or at least guide some folks on some functionality they may not know about.&lt;/p&gt;
&lt;p&gt;A long while back even in Umbraco v7 &lt;em&gt;UmbracoHelper&lt;/em&gt; was split into different components and &lt;em&gt;UmbracoHelper&lt;/em&gt; just wrapped these. One of these components was called &lt;em&gt;ITypedPublishedContentQuery&lt;/em&gt; and in v8 is now called &lt;em&gt;IPublishedContentQuery&lt;/em&gt;, and this component is responsible for executing queries for content and media on the front-end in razor templates. In v8 a lot of methods were removed or obsoleted from &lt;em&gt;UmbracoHelper&lt;/em&gt; so that it wasn’t one gigantic object and tries to steer developers to use these sub components directly instead. For example if you try to access &lt;em&gt;UmbracoHelper.ContentQuery&lt;/em&gt; you’ll see that has been deprecated saying:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Inject and use an instance of IPublishedContentQuery in the constructor for using it in classes or get it from Current.PublishedContentQuery in views&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and the &lt;em&gt;UmbracoHelper.Search&lt;/em&gt; methods from v7 have been removed and now only exist on &lt;em&gt;IPublishedContentQuery&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;There are &lt;a rel="noopener" href="https://our.umbraco.com/apidocs/v8/csharp/api/Umbraco.Web.IPublishedContentQuery.html" target="_blank"&gt;API docs for IPublishedContentQuery&lt;/a&gt; which are a bit helpful, at least will tell you what all available methods and parameters are. The main one’s I wanted to point out are the &lt;strong&gt;Search&lt;/strong&gt; methods.&lt;/p&gt;
&lt;h2&gt;Strongly typed search responses&lt;/h2&gt;
&lt;p&gt;When you use Examine directly to search you will get an Examine &lt;em&gt;ISearchResults&lt;/em&gt; object back which is more or less raw data. It’s possible to work with that data but most people want to work with some strongly typed data and at the very least in Umbraco with &lt;em&gt;IPublishedContent&lt;/em&gt;. That is pretty much what &lt;a rel="noopener" href="https://our.umbraco.com/apidocs/v8/csharp/api/Umbraco.Web.IPublishedContentQuery.html#Umbraco_Web_IPublishedContentQuery_Search_System_String_System_String_System_String_" target="_blank"&gt;IPublishedContentQuery.Search&lt;/a&gt; methods are solving. Each of these methods will return an &lt;em&gt;IEnumerable&amp;lt;PublishedSearchResult&amp;gt;&lt;/em&gt; and each &lt;a rel="noopener" href="https://our.umbraco.com/apidocs/v8/csharp/api/Umbraco.Core.Models.PublishedContent.PublishedSearchResult.html" target="_blank"&gt;PublishedSearchResult&lt;/a&gt; contains an &lt;em&gt;IPublishedContent&lt;/em&gt; instance along with a &lt;em&gt;Score&lt;/em&gt; value. A quick example in razor:&lt;/p&gt;
&lt;pre&gt;&lt;code class="lang-csharp"&gt;@inherits Umbraco.Web.Mvc.UmbracoViewPage
@using Current = Umbraco.Web.Composing.Current;
@{
    var search = Current.PublishedContentQuery.Search(Request.QueryString["query"]);
}

&amp;lt;div&amp;gt;
    &amp;lt;h3&amp;gt;Search Results&amp;lt;/h3&amp;gt;
    &amp;lt;ul&amp;gt;
        @foreach (var result in search)
        {
            &amp;lt;li&amp;gt;
                Id: @result.Content.Id
                &amp;lt;br/&amp;gt;
                Name: @result.Content.Name
                &amp;lt;br /&amp;gt;
                Score: @result.Score
            &amp;lt;/li&amp;gt;
        }
    &amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The ordering of this search is by Score so the highest score is first. This makes searching very easy while the underlying mechanism is still Examine. The &lt;em&gt;IPublishedContentQuery.Search &lt;/em&gt;methods make working with the results a bit nicer.&lt;/p&gt;
&lt;h2&gt;Paging results&lt;/h2&gt;
&lt;p&gt;You may have noticed that there’s a few overloads and optional parameters to these search methods too. 2 of the overloads support paging parameters and these take care of all of the quirks with Lucene paging for you. I wrote &lt;a rel="noopener" href="/post/paging-with-examine/" target="_blank"&gt;a previous post about paging with Examine&lt;/a&gt; and you need to make sure you do that correctly else you’ll end up iterating over possibly tons of search results which can have performance problems. To expand on the above example with paging is super easy:&lt;/p&gt;
&lt;pre&gt;&lt;code class="lang-csharp"&gt;@inherits Umbraco.Web.Mvc.UmbracoViewPage
@using Current = Umbraco.Web.Composing.Current;
@{
    var pageSize = 10;
    var pageIndex = int.Parse(Request.QueryString["page"]);
    var search = Current.PublishedContentQuery.Search(
        Request.QueryString["query"],
        pageIndex * pageSize,   // skip
        pageSize,               // take
        out var totalRecords);
}

&amp;lt;div&amp;gt;
    &amp;lt;h3&amp;gt;Search Results&amp;lt;/h3&amp;gt;
    &amp;lt;ul&amp;gt;
        @foreach (var result in search)
        {
            &amp;lt;li&amp;gt;
                Id: @result.Content.Id
                &amp;lt;br/&amp;gt;
                Name: @result.Content.Name
                &amp;lt;br /&amp;gt;
                Score: @result.Score
            &amp;lt;/li&amp;gt;
        }
    &amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Simple search with cultures&lt;/h2&gt;
&lt;p&gt;Another optional parameter you might have noticed is the culture parameter. The docs state this about the culture parameter:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When the &lt;code&gt;culture&lt;/code&gt; is not specified or is *, all cultures are searched. To search for only invariant documents and fields use null. When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents. While enumerating results, the ambient culture is changed to be the searched culture.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What this is saying is that if you aren’t using culture variants in Umbraco then don’t worry about it. But if you are, you will also generally not have to worry about it either! What?! By default the simple Search method will use the “ambient” (aka ‘Current’) culture to search and return data. So if you are currently browsing your “fr-FR” culture site this method will automatically only search for your data in your French culture but will also search on any invariant (non-culture) data. And as a bonus, the IPublishedContent returned also uses this ambient culture so any values you retrieve from the content item without specifying the culture will just be the ambient/default culture.&lt;/p&gt;
&lt;p&gt;So why is there a “culture” parameter? It’s just there in case you want to search on a specific culture instead of relying on the ambient/current one.&lt;/p&gt;
&lt;h2&gt;Search with IQueryExecutor&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;IQueryExecutor &lt;/em&gt;is the resulting object created when creating a query with the Examine fluent API. This means you can build up any complex Examine query you want, even with raw Lucene, and then pass this query to one of the &lt;em&gt;IPublishedContentQuery.Search&lt;/em&gt; overloads and you’ll get all the goodness of the above queries. There’s also paging overloads with &lt;em&gt;IQueryExecutor&lt;/em&gt; too. To further expand on the above example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="lang-csharp"&gt;@inherits Umbraco.Web.Mvc.UmbracoViewPage
@using Current = Umbraco.Web.Composing.Current;
@{
    // Get the external index with error checking
    if (ExamineManager.Instance.TryGetIndex(
        Constants.UmbracoIndexes.ExternalIndexName, out var index))
    {
        throw new InvalidOperationException(
            $"No index found with name {Constants.UmbracoIndexes.ExternalIndexName}");
    }

    // build an Examine query
    var query = index.GetSearcher().CreateQuery()
        .GroupedOr(new [] { "pageTitle", "pageContent"},
            Request.QueryString["query"].MultipleCharacterWildcard());


    var pageSize = 10;
    var pageIndex = int.Parse(Request.QueryString["page"]);
    var search = Current.PublishedContentQuery.Search(
        query,                  // pass the examine query in!
        pageIndex * pageSize,   // skip
        pageSize,               // take
        out var totalRecords);
}

&amp;lt;div&amp;gt;
    &amp;lt;h3&amp;gt;Search Results&amp;lt;/h3&amp;gt;
    &amp;lt;ul&amp;gt;
        @foreach (var result in search)
        {
            &amp;lt;li&amp;gt;
                Id: @result.Content.Id
                &amp;lt;br/&amp;gt;
                Name: @result.Content.Name
                &amp;lt;br /&amp;gt;
                Score: @result.Score
            &amp;lt;/li&amp;gt;
        }
    &amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The base interface of the fluent parts of Examine’s queries are &lt;em&gt;IQueryExecutor&lt;/em&gt; so you can just pass in your query to the method and it will work.&lt;/p&gt;
&lt;h2&gt;Recap&lt;/h2&gt;
&lt;p&gt;The &lt;a rel="noopener" href="https://our.umbraco.com/apidocs/v8/csharp/api/Umbraco.Web.IPublishedContentQuery.html#Umbraco_Web_IPublishedContentQuery_Search_IQueryExecutor_" target="_blank"&gt;IPublishedContentQuery.Search overloads are listed in the API docs&lt;/a&gt;, they are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Search(String term, String culture, String indexName)&lt;/li&gt;
&lt;li&gt;Search(String term, Int32 skip, Int32 take, out Int64 totalRecords, String culture, String indexName)&lt;/li&gt;
&lt;li&gt;Search(IQueryExecutor query)&lt;/li&gt;
&lt;li&gt;Search(IQueryExecutor query, Int32 skip, Int32 take, out Int64 totalRecords)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Should you always use this instead of using Examine directly? As always it just depends on what you are doing. If you need a ton of flexibility with your search results than maybe you want to use Examine’s search results directly but if you want simple and quick access to IPublishedContent results, then these methods will work great.&lt;/p&gt;
&lt;p&gt;Does this all work with &lt;a rel="noopener" href="https://examinex.online/" target="_blank"&gt;ExamineX&lt;/a&gt; ? Absolutely!! One of the best parts of ExamineX is that it’s completely seamless. ExamineX is just an index implementation of Examine itself so all Examine APIs and therefore all Umbraco APIs that use Examine will ‘just work’.&lt;/p&gt;</description>
      <pubDate>Thu, 23 Mar 2023 15:10:02 Z</pubDate>
      <a10:updated>2023-03-23T15:10:02Z</a10:updated>
    </item>
  </channel>
</rss>