< Summary

Information
Class: OzonOrdersWeb.Extensions.ApiClientsExtensions
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Extensions/ApiClientsExtensions.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 580
Coverable lines: 580
Total lines: 713
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 186
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AddApiClients(...)0%347821860%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Extensions/ApiClientsExtensions.cs

#LineLine coverage
 1using Microsoft.Extensions.Caching.Memory;
 2using Microsoft.Extensions.Http;
 3using Microsoft.Extensions.Logging;
 4using Microsoft.Extensions.Options;
 5using OzonDomains.Models;
 6using OzonRepositories.Data;
 7using PartsInfo.Cache;
 8using PartsInfo.HttpUtils;
 9using Polly;
 10using Polly.Extensions.Http;
 11using Services.AiAssistant;
 12using Services.ApiServices;
 13using Services.ApiServices._1CApi;
 14using Services.ApiServices._1CApi.OData;
 15using Services.ApiServices._1CApi.Stock;
 16using Services.ApiServices.ArmtekApi;
 17using Services.ApiServices.ArmtekEtp;
 18using Services.ApiServices.AvdApi;
 19using OzonOrdersWeb.TransactionServices;
 20using MikadoCartService = OzonOrdersWeb.TransactionServices.MikadoCartService;
 21using Services.ApiServices.BergApi;
 22using Services.ApiServices.ForumAutoApi;
 23using Services.ApiServices.FidApi;
 24using Services.ApiServices.InterpartsEngine;
 25using Services.ApiServices.FavoritPartsApi;
 26using Services.ApiServices.ApiSupplierRequestLog;
 27using Services.ApiServices.ApiSupplierIntegration;
 28using Services.LabelServices;
 29using Services.OzonHandoverServices;
 30using Services.ApiServices.MlAutoApi;
 31using Services.ApiServices.ShateMApi;
 32using Services.ApiServices.MotexApi;
 33using Services.ApiServices.MoskvorechieApi;
 34using Services.ApiServices.ProfitLigaApi;
 35using Services.ApiServices.NikeiApi;
 36using Services.ApiServices.MikadoApi;
 37using Services.ApiServices.ZZapApi;
 38using Services.ApiServices.JapartsApi;
 39using Services.ApiServices.DropBoxApi;
 40using Services.ApiServices.InterpartsPriceApi;
 41using Services.ApiServices.OpenRouterApi;
 42using Services.ApiServices.PartsnavigatorApi;
 43using Services.ApiServices.OzonApi;
 44using Services.ApiServices.StudioApi;
 45using Services.ApiServices.TecDocApi;
 46using Services.ApiServices.TelegramBotApi;
 47using Services.ApiServices.YandexApi;
 48using Services.BitrixStockServices;
 49using Services.DataServices;
 50
 51namespace OzonOrdersWeb.Extensions;
 52
 53public static class ApiClientsExtensions
 54{
 55    public static void AddApiClients(this IServiceCollection services, IConfiguration configuration)
 56    {
 057        services.AddTransient<BitrixStockRunHttpLoggingHandler>();
 058        services.AddSingleton<ApiSupplierRequestLogStore>();
 059        services.AddHostedService<ApiSupplierRequestLogPersistenceService>();
 060        services.AddSingleton<InterpartsPriceDiagnosticsStore>();
 61        // Настройка HttpClient с Polly политиками для всех API клиентов
 062        services.AddHttpClient("OneCOData")
 063            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 64
 065        services.AddHttpClient("OzonApi")
 066            .AddHttpMessageHandler<BitrixStockRunHttpLoggingHandler>()
 067            .AddPolicyHandler(BaseApiClient.GetRetryPolicy())
 068            .AddPolicyHandler(BaseApiClient.GetCircuitBreakerPolicy());
 69
 70        // OzonApiReturns — только Retry, без Circuit Breaker (чтобы загрузка возвратов не блокировалась при открытом ко
 071        services.AddHttpClient("OzonApiReturns")
 072            .AddHttpMessageHandler<BitrixStockRunHttpLoggingHandler>()
 073            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 74
 75        // OzonApiBitrix — массовая загрузка остатков на BitrixStock, без Circuit Breaker
 076        services.AddHttpClient("OzonApiBitrix")
 077            .AddHttpMessageHandler<BitrixStockRunHttpLoggingHandler>()
 078            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 79
 80        // Скачивание готового файла этикетки по file_url (файловое хранилище Ozon, не api-seller).
 081        services.AddHttpClient(OzonLabelPrintService.LabelDownloadHttpClientName,
 082                client => client.Timeout = TimeSpan.FromSeconds(30))
 083            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 84
 085        services.AddHttpClient(OzonHandoverPhotoStorage.HttpClientName,
 086                client => client.Timeout = TimeSpan.FromSeconds(30))
 087            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 88
 089        services.AddHttpClient("YandexApi")
 090            .AddPolicyHandler(BaseApiClient.GetYandexRetryPolicy())
 091            .AddPolicyHandler(BaseApiClient.GetCircuitBreakerPolicy());
 92
 093        services.AddHttpClient("TecDocApi")
 094            .AddPolicyHandler(BaseApiClient.GetRetryPolicy())
 095            .AddPolicyHandler(BaseApiClient.GetCircuitBreakerPolicy());
 96
 097        services.AddHttpClient("InterpartsPriceApi")
 098            .AddPolicyHandler(BaseApiClient.GetRetryPolicy())
 099            .AddPolicyHandler(BaseApiClient.GetCircuitBreakerPolicy());
 100
 0101        services.AddTransient<OzonJsonDataBuilder>(serviceProvider =>
 0102        {
 0103            var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
 0104            var emailService = serviceProvider.GetService<SlqStudio.Application.Services.EmailService.IEmailService>();
 0105            var clientId = configuration.GetConnectionString("ClientId");
 0106            var apiKey = configuration.GetConnectionString("ApiKey");
 0107
 0108            var jsonDataBuilder = new OzonJsonDataBuilder(httpClientFactory, emailService);
 0109            if (clientId != null && apiKey != null)
 0110                jsonDataBuilder.Init(clientId, apiKey);
 0111            return jsonDataBuilder;
 0112        });
 113
 0114        services.AddTransient<YandexDataManager>(serviceProvider =>
 0115        {
 0116            var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
 0117            var emailService = serviceProvider.GetService<SlqStudio.Application.Services.EmailService.IEmailService>();
 0118            var apiKey = configuration.GetConnectionString("YandexApiKey");
 0119
 0120            var yandexDataManager = new YandexDataManager(httpClientFactory, emailService);
 0121            if (apiKey != null) yandexDataManager.SetClient(apiKey);
 0122            return yandexDataManager;
 0123        });
 124
 0125        services.AddSingleton(new InterpartsPriceApiConfig
 0126        {
 0127            Secret123 = configuration.GetConnectionString("InterpartsPriceApiSecret123") ?? string.Empty,
 0128            BaseUrl = configuration["InterpartsPriceApi:BaseUrl"] ?? "http://price-api.interparts.ru/search.aspx"
 0129        });
 130
 0131        services.AddTransient<InterpartsPriceApiClient>(serviceProvider =>
 0132        {
 0133            var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
 0134            var emailService = serviceProvider.GetService<SlqStudio.Application.Services.EmailService.IEmailService>();
 0135            return new InterpartsPriceApiClient(httpClientFactory, emailService);
 0136        });
 137
 0138        services.AddTransient<InterpartsPriceApiDataManager>();
 0139        services.AddScoped<InterpartsCurrencyRateService>();
 140
 0141        services.AddSingleton(new DropboxApiClient(
 0142            configuration.GetConnectionString("DropBoxRefreshToken") ?? string.Empty,
 0143            configuration.GetConnectionString("DropBoxAppKey") ?? string.Empty,
 0144            configuration.GetConnectionString("DropBoxAppSecret") ?? string.Empty));
 145
 0146        services.AddTransient<TecDocApiClient>(serviceProvider =>
 0147        {
 0148            var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
 0149            var emailService = serviceProvider.GetRequiredService<SlqStudio.Application.Services.EmailService.IEmailServ
 0150            var tecDocBaseUrl = configuration["TecDocApi:BaseUrl"] ?? "http://localhost:8000/";
 0151            return new TecDocApiClient(httpClientFactory, emailService, tecDocBaseUrl);
 0152        });
 0153        services.AddTransient<TecDocDataManager>();
 154
 155
 156        // OData 1C Fresh интеграция
 0157        var odataConfig = new OneCODataConfig
 0158        {
 0159            BaseUrl = configuration["OneCOData:BaseUrl"] ?? string.Empty,
 0160            User = configuration["OneCOData:User"] ?? string.Empty,
 0161            Password = configuration["OneCOData:Password"] ?? string.Empty,
 0162            ResolverMode = Enum.TryParse<Services.ApiServices._1CApi.Nomenclature.NomenclatureResolverMode>(
 0163                configuration["OneCNomenclature:ResolverMode"], true, out var resolverMode)
 0164                ? resolverMode
 0165                : Services.ApiServices._1CApi.Nomenclature.NomenclatureResolverMode.On
 0166        };
 0167        services.AddSingleton(odataConfig);
 168
 0169        services.AddSingleton<OneCODataHttpClient>(serviceProvider =>
 0170        {
 0171            var factory = serviceProvider.GetRequiredService<IHttpClientFactory>();
 0172            var cfg = serviceProvider.GetRequiredService<OneCODataConfig>();
 0173            return new OneCODataHttpClient(factory, cfg,
 0174                serviceProvider.GetService<Microsoft.Extensions.Logging.ILogger<OneCODataHttpClient>>());
 0175        });
 176
 0177        services.AddSingleton<IOneCODataClient>(serviceProvider => serviceProvider.GetRequiredService<OneCODataHttpClien
 178
 0179        services.AddSingleton<IOneCODataReferenceService>(serviceProvider =>
 0180        {
 0181            var client = serviceProvider.GetRequiredService<OneCODataHttpClient>();
 0182            var cache = serviceProvider.GetRequiredService<IMemoryCache>();
 0183            var logger = serviceProvider.GetRequiredService<ILogger<OneCODataReferenceService>>();
 0184            return new OneCODataReferenceService(client, cache, logger);
 0185        });
 186
 0187        services.AddSingleton<IOneCNomenclatureCreationLock, OneCNomenclatureCreationLock>();
 0188        services.AddSingleton<IOneCNomenclatureMapStore, OneCNomenclatureMapStore>();
 189        // Модуль поиска номенклатуры: снимок карты и справочник брендов держатся в памяти (Singleton), БД читают через 
 0190        services.AddSingleton<Services.ApiServices._1CApi.Nomenclature.INomenclatureSnapshotSource>(sp =>
 0191            (Services.ApiServices._1CApi.Nomenclature.INomenclatureSnapshotSource)sp.GetRequiredService<IOneCNomenclatur
 0192        services.AddSingleton<Services.ApiServices._1CApi.Nomenclature.IProducerDirectoryProvider>(sp =>
 0193            new Services.ApiServices._1CApi.Nomenclature.ProducerRegistry(sp.GetRequiredService<IServiceScopeFactory>())
 0194        services.AddSingleton<Services.ApiServices._1CApi.Nomenclature.ProducerValueResolver>(sp =>
 0195            new Services.ApiServices._1CApi.Nomenclature.ProducerValueResolver(
 0196                sp.GetRequiredService<IOneCODataClient>(),
 0197                sp.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.IProducerDirectoryProvider>()));
 0198        services.AddSingleton<Services.ApiServices._1CApi.Nomenclature.NomenclatureMatcher>(sp =>
 0199            new Services.ApiServices._1CApi.Nomenclature.NomenclatureMatcher(
 0200                sp.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.INomenclatureIndexProvider>(),
 0201                sp.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.IProducerDirectoryProvider>()));
 0202        services.AddSingleton<Services.ApiServices._1CApi.Nomenclature.INomenclatureIndexProvider>(sp =>
 0203            new Services.ApiServices._1CApi.Nomenclature.NomenclatureIndexProvider(
 0204                sp.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.INomenclatureSnapshotSource>(),
 0205                sp.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.IProducerDirectoryProvider>()));
 0206        services.AddSingleton<Services.ApiServices._1CApi.Nomenclature.INomenclatureNotificationSink,
 0207            OzonOrdersWeb.WebServices.SystemAlerts.NomenclatureTelegramNotifier>();
 0208        services.AddSingleton<IOneCNomenclatureService>(serviceProvider =>
 0209        {
 0210            var client = serviceProvider.GetRequiredService<OneCODataHttpClient>();
 0211            var logger = serviceProvider.GetRequiredService<ILogger<OneCNomenclatureService>>();
 0212            var creationLock = serviceProvider.GetRequiredService<IOneCNomenclatureCreationLock>();
 0213            var map = serviceProvider.GetRequiredService<IOneCNomenclatureMapStore>();
 0214            var cfg = serviceProvider.GetRequiredService<OneCODataConfig>();
 0215            var signals = serviceProvider.GetRequiredService<Services.SystemMonitoringServices.IsolatedSystemMonitoringS
 0216            var matcher = serviceProvider.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.NomenclatureMatche
 0217            var producerValues = serviceProvider.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.ProducerVal
 0218            var indexProvider = serviceProvider.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.INomenclatur
 0219            var notifications = serviceProvider.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.INomenclatur
 0220            return new OneCNomenclatureService(client, logger, creationLock, map, cfg, signals, matcher, producerValues,
 0221        });
 0222        services.AddTransient<OneCNomenclatureMapSyncJob>();
 223
 0224        services.AddTransient<OneCTransferManager>(serviceProvider =>
 0225        {
 0226            var refService = serviceProvider.GetRequiredService<IOneCODataReferenceService>();
 0227            var odataClient = serviceProvider.GetRequiredService<OneCODataHttpClient>();
 0228            var nomenclatureService = serviceProvider.GetRequiredService<IOneCNomenclatureService>();
 0229            var warehouseMappingDataServices = serviceProvider.GetRequiredService<WarehouseMappingDataServices>();
 0230            var ordersDataServices = serviceProvider.GetRequiredService<OrdersDataServices>();
 0231            var logger = serviceProvider.GetRequiredService<ILogger<OneCTransferManager>>();
 0232            var signals = serviceProvider.GetRequiredService<Services.SystemMonitoringServices.IsolatedSystemMonitoringS
 0233            var failureReporter = serviceProvider.GetRequiredService<Services.OneCServices.IOneCPostingFailureReporter>(
 0234            return new OneCTransferManager(refService, odataClient, nomenclatureService, warehouseMappingDataServices, o
 0235        });
 236
 0237        services.AddTransient<OneCReceiptManager>(serviceProvider =>
 0238        {
 0239            var refService = serviceProvider.GetRequiredService<IOneCODataReferenceService>();
 0240            var odataClient = serviceProvider.GetRequiredService<OneCODataHttpClient>();
 0241            var nomenclatureService = serviceProvider.GetRequiredService<IOneCNomenclatureService>();
 0242            var logger = serviceProvider.GetRequiredService<ILogger<OneCReceiptManager>>();
 0243            var signals = serviceProvider.GetRequiredService<Services.SystemMonitoringServices.IsolatedSystemMonitoringS
 0244            var failureReporter = serviceProvider.GetRequiredService<Services.OneCServices.IOneCPostingFailureReporter>(
 0245            return new OneCReceiptManager(refService, odataClient, nomenclatureService, logger, signals, failureReporter
 0246        });
 247
 0248        services.AddTransient<Services.OneCServices.IOneCPostingFailureReporter, Services.OneCServices.OneCPostingFailur
 0249        services.AddTransient<OneCExpenseManager>();
 250
 0251        services.AddTransient<OneCDocumentViewService>();
 252
 0253        services.AddTransient<OneCDataManager>(serviceProvider =>
 0254        {
 0255            var refService = serviceProvider.GetRequiredService<IOneCODataReferenceService>();
 0256            var odataClient = serviceProvider.GetRequiredService<OneCODataHttpClient>();
 0257            var logger = serviceProvider.GetRequiredService<ILogger<OneCDataManager>>();
 0258            return new OneCDataManager(refService, odataClient, logger);
 0259        });
 0260        services.AddTransient<OneCReceiptWarehouseHistoryResolver>();
 261
 0262        services.AddTransient<IOneCStockAvailabilityService, OneCStockAvailabilityService>();
 263
 264        // Раздел «Остатки 1С по складам». Singleton: сервис держит семафоры, которые не дают
 265        // параллельным открытиям одного склада запустить несколько тяжёлых обходов регистра.
 266        // Зависимости тоже Singleton, а к БД ходит через IServiceScopeFactory внутри map-store.
 0267        services.AddSingleton<IOneCWarehouseStockService, OneCWarehouseStockService>();
 0268        services.AddTransient<IOneCWarehouseStockExcelExporter, OneCWarehouseStockExcelExporter>();
 269
 270        // Armtek Web-Services API — поставщик автозапчастей (read-only интеграция)
 0271        services.AddHttpClient("ArmtekApi")
 0272            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 273
 0274        var armtekConfig = new ArmtekConfig
 0275        {
 0276            BaseUrl = configuration["Armtek:BaseUrl"] ?? "https://ws.armtek.ru",
 0277            User = configuration["Armtek:User"] ?? string.Empty,
 0278            Password = configuration["Armtek:Password"] ?? string.Empty,
 0279            DefaultVkorg = configuration["Armtek:DefaultVkorg"] ?? "4000"
 0280        };
 0281        services.AddSingleton(armtekConfig);
 282
 0283        services.AddSingleton<ArmtekHttpClient>(serviceProvider =>
 0284        {
 0285            var factory = serviceProvider.GetRequiredService<IHttpClientFactory>();
 0286            var cfg = serviceProvider.GetRequiredService<ArmtekConfig>();
 0287            return new ArmtekHttpClient(factory, cfg, serviceProvider.GetRequiredService<ApiSupplierRequestLogStore>(),
 0288                dailyQuotaGuard: serviceProvider.GetRequiredService<ArmtekDailyQuotaGuard>());
 0289        });
 290
 0291        services.AddTransient<ArmtekDataManager>(serviceProvider =>
 0292        {
 0293            var client = serviceProvider.GetRequiredService<ArmtekHttpClient>();
 0294            var cfg = serviceProvider.GetRequiredService<ArmtekConfig>();
 0295            var logger = serviceProvider.GetRequiredService<ILogger<ArmtekDataManager>>();
 0296            var resolver = new ArmtekBrandResolver(
 0297                client,
 0298                serviceProvider.GetRequiredService<EtProducerRepository>(),
 0299                serviceProvider.GetRequiredService<IMemoryCache>(),
 0300                "Armtek");
 0301            return new ArmtekDataManager(client, cfg, logger, resolver);
 0302        });
 303
 0304        services.AddHttpClient("ArmtekByApi")
 0305            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0306        services.AddSingleton<ArmtekDailyQuotaGuard>();
 0307        services.AddSingleton<ArmtekStoreDirectory>();
 0308        services.AddHostedService<ArmtekStoreDirectoryHostedService>();
 0309        services.AddSingleton(new ArmtekByConfig
 0310        {
 0311            BaseUrl = configuration["ArmtekBy:BaseUrl"] ?? "https://ws.armtek.ru",
 0312            User = configuration["ArmtekBy:User"] ?? string.Empty,
 0313            Password = configuration["ArmtekBy:Password"] ?? string.Empty,
 0314            DefaultVkorg = configuration["ArmtekBy:DefaultVkorg"] ?? "2000"
 0315        });
 0316        services.AddSingleton<ArmtekByHttpClient>();
 0317        services.AddTransient<ArmtekByDataManager>(serviceProvider =>
 0318        {
 0319            var client = serviceProvider.GetRequiredService<ArmtekByHttpClient>();
 0320            var cfg = serviceProvider.GetRequiredService<ArmtekByConfig>();
 0321            var logger = serviceProvider.GetRequiredService<ILogger<ArmtekDataManager>>();
 0322            var resolver = new ArmtekBrandResolver(
 0323                client,
 0324                serviceProvider.GetRequiredService<EtProducerRepository>(),
 0325                serviceProvider.GetRequiredService<IMemoryCache>(),
 0326                "ArmtekBy");
 0327            return new ArmtekByDataManager(client, cfg, logger, resolver);
 0328        });
 329
 330        // Личный кабинет Armtek (etp.armtek.ru): взаиморасчёты и фактуры. Cookie сессии ведёт сам ArmtekEtpClient,
 331        // поэтому общий обработчик фабрики их не хранит и редиректы не проходит (редирект = страница входа).
 0332        var armtekEtpConfig = new ArmtekEtpConfig
 0333        {
 0334            Enabled = configuration.GetValue("ArmtekEtp:Enabled", false),
 0335            BaseUrl = configuration["ArmtekEtp:BaseUrl"] ?? "https://etp.armtek.ru",
 0336            Login = configuration["ArmtekEtp:Login"] is { Length: > 0 } etpLogin ? etpLogin : configuration["ArmtekBy:Us
 0337            Password = configuration["ArmtekEtp:Password"] is { Length: > 0 } etpPassword ? etpPassword : configuration[
 0338            Vkorg = configuration["ArmtekEtp:Vkorg"] is { Length: > 0 } etpVkorg ? etpVkorg : configuration["ArmtekBy:De
 0339            Kunnr = configuration["ArmtekEtp:Kunnr"],
 0340            KunnrRg = configuration["ArmtekEtp:KunnrRg"],
 0341            LookbackDays = configuration.GetValue("ArmtekEtp:LookbackDays", 45),
 0342            InvoiceRefreshDays = configuration.GetValue("ArmtekEtp:InvoiceRefreshDays", 30),
 0343            OrderReportExtraDays = configuration.GetValue("ArmtekEtp:OrderReportExtraDays", 60),
 0344            InvoiceLoadLimit = configuration.GetValue("ArmtekEtp:InvoiceLoadLimit", 150),
 0345            InvoiceRequestDelayMs = configuration.GetValue("ArmtekEtp:InvoiceRequestDelayMs", 300)
 0346        };
 0347        services.AddSingleton(armtekEtpConfig);
 0348        services.AddHttpClient(ArmtekEtpConfig.HttpClientName, c => c.Timeout = TimeSpan.FromSeconds(60))
 0349            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
 0350            {
 0351                UseCookies = false,
 0352                AllowAutoRedirect = false,
 0353                AutomaticDecompression = System.Net.DecompressionMethods.All
 0354            });
 0355        services.AddTransient<ArmtekEtpClient>();
 356
 0357        services.AddHttpClient("ShateMApi")
 0358            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0359        services.AddSingleton(new ShateMConfig
 0360        {
 0361            BaseUrl = configuration["ShateM:BaseUrl"] ?? "https://api.shate-m.by",
 0362            Login = configuration["ShateM:Login"] ?? string.Empty,
 0363            Password = configuration["ShateM:Password"] ?? string.Empty
 0364        });
 0365        services.AddSingleton<ShateMHttpClient>();
 0366        services.AddTransient<ShateMDataManager>();
 0367        services.AddTransient<ShateMCartService>();
 0368        services.AddTransient<IApiSupplierConnector, ArmtekSupplierConnector>();
 0369        services.AddTransient<IApiSupplierConnector, ArmtekBySupplierConnector>();
 0370        services.AddTransient<IApiSupplierConnector, ShateMSupplierConnector>();
 0371        services.AddHttpClient("AvdApi")
 0372            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0373        services.AddSingleton(new AvdConfig
 0374        {
 0375            BaseUrl = configuration["Avd:BaseUrl"] ?? "https://ws1.avdmotors.ru/AvdUserService.svc/secure",
 0376            Login = configuration["Avd:Login"] ?? string.Empty,
 0377            Password = configuration["Avd:Password"] ?? string.Empty
 0378        });
 0379        services.AddSingleton<AvdSoapClient>();
 0380        services.AddTransient<AvdDataManager>();
 0381        services.AddTransient<AvdBasketPlacementService>();
 0382        services.AddTransient<ArmtekCartService>();
 0383        services.AddScoped<ISupplierOrderGuard, SupplierOrderGuard>();
 384        // Outbox намерений — singleton: переживает scope запроса и помнит, что таблицы ещё нет (до миграции).
 0385        services.AddSingleton<ISupplierOrderIntentStore, SupplierOrderIntentStore>();
 0386        services.AddScoped<ISupplierOrderLookup, ArmtekOrderLookup>();
 0387        services.AddScoped<ISupplierOrderLookup, FavoritPartsOrderLookup>();
 0388        services.AddTransient<IApiSupplierConnector, AvdSupplierConnector>();
 0389        services.AddHttpClient("JapartsApi", client => client.Timeout = TimeSpan.FromSeconds(30))
 0390            .AddPolicyHandler(BaseApiClient.GetInteractiveRetryPolicy());
 0391        services.AddSingleton(new JapartsConfig
 0392        {
 0393            BaseUrl = configuration["Japarts:BaseUrl"] ?? "https://www.japarts.ru/",
 0394            Login = configuration["Japarts:Login"] ?? string.Empty,
 0395            Password = configuration["Japarts:Password"] ?? string.Empty
 0396        });
 0397        services.AddSingleton<JapartsHttpClient>();
 0398        services.AddTransient<JapartsDataManager>();
 0399        services.AddTransient<OzonOrdersWeb.TransactionServices.JapartsCartService>();
 0400        services.AddTransient<IApiSupplierConnector, JapartsSupplierConnector>();
 0401        services.AddHttpClient("BergApi")
 0402            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0403        services.AddSingleton(new BergConfig
 0404        {
 0405            BaseUrl = configuration["Berg:BaseUrl"] ?? "https://api.berg.ru",
 0406            ApiKey = configuration["Berg:ApiKey"] ?? string.Empty
 0407        });
 0408        services.AddSingleton<BergHttpClient>();
 0409        services.AddTransient<BergDataManager>();
 0410        services.AddTransient<IApiSupplierConnector, BergSupplierConnector>();
 0411        services.AddTransient<OzonOrdersWeb.TransactionServices.BergCartService>();
 412        // Ответ фида ждёт открытая страница, поэтому таймаут короткий, а повтор — один быстрый.
 0413        services.AddHttpClient("FidApi", client => client.Timeout = TimeSpan.FromSeconds(20))
 0414            .AddPolicyHandler(BaseApiClient.GetInteractiveRetryPolicy());
 0415        services.AddSingleton(new FidApiConfig
 0416        {
 0417            BaseUrl = configuration["FidApi:BaseUrl"] ?? "http://192.168.1.130:5080",
 0418            ApiKey = configuration["FidApi:ApiKey"] ?? string.Empty
 0419        });
 0420        services.AddSingleton<FidApiHttpClient>();
 0421        services.AddTransient<FidApiDataManager>();
 0422        services.AddHttpClient("MotexApi")
 0423            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0424        services.AddSingleton(new MotexConfig
 0425        {
 0426            BaseUrl = configuration["Motex:BaseUrl"] ?? "https://api.motexc.by",
 0427            Login = configuration["Motex:Login"] ?? string.Empty,
 0428            Password = configuration["Motex:Password"] ?? string.Empty
 0429        });
 0430        services.AddSingleton<MotexHttpClient>();
 0431        services.AddTransient<MotexDataManager>();
 0432        services.AddTransient<MotexCartService>();
 0433        services.AddTransient<IApiSupplierConnector, MotexSupplierConnector>();
 0434        services.AddHttpClient("MlAutoApi")
 0435            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0436        services.AddSingleton(new MlAutoConfig
 0437        {
 0438            BaseUrl = configuration["MlAuto:BaseUrl"] ?? "https://www.ml-auto.by/webservice",
 0439            Login = configuration["MlAuto:Login"] ?? string.Empty,
 0440            Password = configuration["MlAuto:Password"] ?? string.Empty,
 0441            RequestsPerMinute = configuration.GetValue("MlAuto:RequestsPerMinute", 60)
 0442        });
 0443        services.AddSingleton<MlAutoHttpClient>();
 0444        services.AddTransient<MlAutoDataManager>();
 0445        services.AddTransient<MlAutoCartService>();
 0446        services.AddTransient<IApiSupplierConnector, MlAutoSupplierConnector>();
 0447        services.AddHttpClient("MlAutoRuApi")
 0448            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0449        services.AddSingleton(new MlAutoRuConfig
 0450        {
 0451            BaseUrl = configuration["MlAutoRu:BaseUrl"] ?? "https://ml-auto.ru/webservice",
 0452            Login = configuration["MlAutoRu:Login"] ?? string.Empty,
 0453            Password = configuration["MlAutoRu:Password"] ?? string.Empty,
 0454            RequestsPerMinute = configuration.GetValue("MlAutoRu:RequestsPerMinute", 60)
 0455        });
 0456        services.AddSingleton<MlAutoRuHttpClient>();
 0457        services.AddTransient<MlAutoRuDataManager>();
 0458        services.AddTransient<MlAutoRuCartService>();
 0459        services.AddTransient<IApiSupplierConnector, MlAutoRuSupplierConnector>();
 0460        services.AddHttpClient("ForumAutoApi")
 0461            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0462        services.AddSingleton(new ForumAutoConfig
 0463        {
 0464            BaseUrl = configuration["ForumAuto:BaseUrl"] ?? "https://api.forum-auto.ru/v2",
 0465            Login = configuration["ForumAuto:Login"] ?? string.Empty,
 0466            Password = configuration["ForumAuto:Password"] ?? string.Empty
 0467        });
 0468        services.AddSingleton<ForumAutoHttpClient>();
 0469        services.AddTransient<ForumAutoDataManager>();
 0470        services.AddTransient<IApiSupplierConnector, ForumAutoSupplierConnector>();
 0471        services.AddHttpClient("ForumAutoInterpartsApi")
 0472            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0473        services.AddSingleton(new ForumAutoInterpartsConfig
 0474        {
 0475            BaseUrl = configuration["ForumAutoInterparts:BaseUrl"] ?? "https://api.forum-auto.ru/v2",
 0476            Login = configuration["ForumAutoInterparts:Login"] ?? string.Empty,
 0477            Password = configuration["ForumAutoInterparts:Password"] ?? string.Empty
 0478        });
 0479        services.AddSingleton<ForumAutoInterpartsHttpClient>();
 0480        services.AddTransient<ForumAutoInterpartsDataManager>();
 0481        services.AddTransient<IApiSupplierConnector, ForumAutoInterpartsSupplierConnector>();
 0482        services.AddHttpClient("ForumAutoPiterApi")
 0483            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0484        services.AddSingleton(new ForumAutoPiterConfig
 0485        {
 0486            BaseUrl = configuration["ForumAutoPiter:BaseUrl"] ?? "https://api.forum-auto.ru/v2",
 0487            Login = configuration["ForumAutoPiter:Login"] ?? string.Empty,
 0488            Password = configuration["ForumAutoPiter:Password"] ?? string.Empty
 0489        });
 0490        services.AddSingleton<ForumAutoPiterHttpClient>();
 0491        services.AddTransient<ForumAutoPiterDataManager>();
 0492        services.AddTransient<IApiSupplierConnector, ForumAutoPiterSupplierConnector>();
 0493        services.AddHttpClient("ForumAutoRostovApi")
 0494            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0495        services.AddSingleton(new ForumAutoRostovConfig
 0496        {
 0497            BaseUrl = configuration["ForumAutoRostov:BaseUrl"] ?? "https://api.forum-auto.ru/v2",
 0498            Login = configuration["ForumAutoRostov:Login"] ?? string.Empty,
 0499            Password = configuration["ForumAutoRostov:Password"] ?? string.Empty
 0500        });
 0501        services.AddSingleton<ForumAutoRostovHttpClient>();
 0502        services.AddTransient<ForumAutoRostovDataManager>();
 0503        services.AddTransient<IApiSupplierConnector, ForumAutoRostovSupplierConnector>();
 0504        services.AddHttpClient("ForumAutoIstraApi")
 0505            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0506        services.AddSingleton(new ForumAutoIstraConfig
 0507        {
 0508            BaseUrl = configuration["ForumAutoIstra:BaseUrl"] ?? "https://api.forum-auto.ru/v2",
 0509            Login = configuration["ForumAutoIstra:Login"] ?? string.Empty,
 0510            Password = configuration["ForumAutoIstra:Password"] ?? string.Empty
 0511        });
 0512        services.AddSingleton<ForumAutoIstraHttpClient>();
 0513        services.AddTransient<ForumAutoIstraDataManager>();
 0514        services.AddTransient<IApiSupplierConnector, ForumAutoIstraSupplierConnector>();
 0515        services.AddTransient<ForumAutoCartService>();
 516        // Таймаут задан явно: со значением HttpClient по умолчанию (100 с) три повтора политики
 517        // растягивают один вызов почти на 7 минут и держат поток запроса пользователя.
 0518        services.AddHttpClient("FavoritPartsPriceApi", client => client.Timeout = TimeSpan.FromSeconds(30))
 0519            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 520        // Повторы только для GET: POST ws/v1/order/ не идемпотентен — на таймауте или 500 заказ у
 521        // поставщика мог уже создаться, и повтор превратился бы в дубль заказа.
 0522        var favoritPartsReadRetry = BaseApiClient.GetRetryPolicy();
 0523        var favoritPartsNoRetry = Policy.NoOpAsync<HttpResponseMessage>();
 0524        services.AddHttpClient("FavoritPartsOrderApi", client => client.Timeout = TimeSpan.FromSeconds(45))
 0525            .AddPolicyHandler(request => request.Method == HttpMethod.Get ? favoritPartsReadRetry : favoritPartsNoRetry)
 526        // Через Configure, а не singleton-снимок: ротация ключа покупателя/разработчика в конфигурации
 527        // подхватывается по IOptionsMonitor без перезапуска приложения.
 0528        services.Configure<FavoritPartsConfig>(configuration.GetSection(FavoritPartsConfig.SectionName));
 0529        services.Configure<FavoritPartsConfig>(FavoritPartsConfig.IstraSectionName, configuration.GetSection(FavoritPart
 0530        services.Configure<FavoritPartsConfig>(FavoritPartsConfig.RostovSectionName, configuration.GetSection(FavoritPar
 0531        services.PostConfigure<FavoritPartsConfig>(config =>
 0532        {
 0533            if (string.IsNullOrWhiteSpace(config.BaseUrl)) config.BaseUrl = "https://api.favorit-parts.ru";
 0534        });
 0535        services.AddSingleton<FavoritPartsPriceHttpClient>();
 0536        services.AddSingleton<FavoritPartsOrderHttpClient>();
 0537        services.AddTransient<FavoritPartsDataManager>();
 0538        services.AddTransient<IApiSupplierConnector, FavoritPartsSupplierConnector>();
 0539        services.AddTransient<FavoritPartsCartService>();
 0540        services.AddTransient<FavoritPartsIstraDataManager>(sp => new FavoritPartsIstraDataManager(
 0541            new FavoritPartsPriceHttpClient(sp.GetRequiredService<IHttpClientFactory>(), sp.GetRequiredService<IOptionsM
 0542            new FavoritPartsOrderHttpClient(sp.GetRequiredService<IHttpClientFactory>(), sp.GetRequiredService<IOptionsM
 0543        services.AddTransient<IApiSupplierConnector, FavoritPartsIstraSupplierConnector>();
 0544        services.AddTransient<FavoritPartsIstraCartService>();
 0545        services.AddTransient<FavoritPartsRostovDataManager>(sp => new FavoritPartsRostovDataManager(
 0546            new FavoritPartsPriceHttpClient(sp.GetRequiredService<IHttpClientFactory>(), sp.GetRequiredService<IOptionsM
 0547            new FavoritPartsOrderHttpClient(sp.GetRequiredService<IHttpClientFactory>(), sp.GetRequiredService<IOptionsM
 0548        services.AddTransient<IApiSupplierConnector, FavoritPartsRostovSupplierConnector>();
 0549        services.AddTransient<FavoritPartsRostovCartService>();
 0550        services.AddHttpClient("MoskvorechieIstraApi", client => client.Timeout = TimeSpan.FromSeconds(30))
 0551            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0552        services.Configure<MoskvorechieIstraConfig>(configuration.GetSection(MoskvorechieIstraConfig.SectionName));
 0553        services.PostConfigure<MoskvorechieIstraConfig>(config =>
 0554        {
 0555            if (string.IsNullOrWhiteSpace(config.BaseUrl)) config.BaseUrl = "https://portal.moskvorechie.ru/portal.api";
 0556        });
 0557        services.AddSingleton<MoskvorechieIstraHttpClient>();
 0558        services.AddSingleton<MoskvorechiePortalPriceClient>();
 0559        services.AddTransient<MoskvorechieIstraDataManager>();
 0560        services.AddTransient<IApiSupplierConnector, MoskvorechieIstraSupplierConnector>();
 0561        services.AddTransient<MoskvorechieIstraCartService>();
 0562        services.AddHttpClient("ProfitLigaApi")
 0563            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0564        services.AddSingleton(new ProfitLigaConfig
 0565        {
 0566            BaseUrl = configuration["ProfitLiga:BaseUrl"] ?? "https://api.pr-lg.ru",
 0567            Secret = configuration["ProfitLiga:Secret"] ?? string.Empty
 0568        });
 0569        services.AddSingleton<ProfitLigaHttpClient>();
 0570        services.AddTransient<ProfitLigaDataManager>();
 0571        services.AddTransient<IApiSupplierConnector, ProfitLigaSupplierConnector>();
 0572        services.AddTransient<ProfitLigaCartService>();
 0573        services.AddHttpClient("NikeiApi", client => client.Timeout = TimeSpan.FromSeconds(30))
 0574            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0575        services.Configure<NikeiConfig>(configuration.GetSection(NikeiConfig.SectionName));
 0576        services.PostConfigure<NikeiConfig>(config =>
 0577        {
 0578            if (string.IsNullOrWhiteSpace(config.BaseUrl)) config.BaseUrl = "https://nikei.ru";
 0579        });
 0580        services.AddSingleton<NikeiHttpClient>();
 0581        services.AddTransient<NikeiDataManager>();
 0582        services.AddTransient<IApiSupplierConnector, NikeiSupplierConnector>();
 0583        services.AddTransient<NikeiCartService>();
 0584        services.AddHttpClient("MikadoMskHod20Api", client => client.Timeout = TimeSpan.FromSeconds(30))
 0585            .AddPolicyHandler(BaseApiClient.GetRetryPolicy());
 0586        services.AddSingleton(new MikadoConfig
 0587        {
 0588            BaseUrl = configuration["MikadoMskHod20:BaseUrl"] ?? "https://www.mikado-parts.ru/ws1",
 0589            ClientId = configuration["MikadoMskHod20:ClientId"] ?? string.Empty,
 0590            Password = configuration["MikadoMskHod20:Password"] ?? string.Empty
 0591        });
 0592        services.AddSingleton<MikadoHttpClient>();
 0593        services.AddTransient<MikadoDataManager>();
 0594        services.AddTransient<MikadoCartService>();
 0595        services.AddTransient<IApiSupplierConnector, MikadoSupplierConnector>();
 0596        services.AddHttpClient("ZZapApi", client => client.Timeout = TimeSpan.FromSeconds(60));
 0597        services.AddSingleton(new ZZapConfig
 0598        {
 0599            BaseUrl = configuration["ZZap:BaseUrl"] ?? "https://b52-api.zzap.pro",
 0600            ApiKey = configuration["ZZap:ApiKey"] ?? string.Empty,
 0601            CodeRegion = int.TryParse(configuration["ZZap:CodeRegion"], out var zzapRegion) ? zzapRegion : 1,
 0602            MinRequestIntervalMs = int.TryParse(configuration["ZZap:MinRequestIntervalMs"], out var zzapInterval) ? zzap
 0603        });
 604        // Без retry-политики: повтор на 429 делает сам клиент с учётом интервала, а слепой retry только
 605        // умножил бы отказы по частоте.
 0606        services.AddSingleton<ZZapHttpClient>();
 0607        services.AddTransient<ZZapDataManager>();
 0608        services.AddTransient<IApiSupplierConnector, ZZapMoscowSupplierConnector>();
 0609        services.AddTransient<ZZapCartService>();
 0610        services.AddTransient<ApiSupplierConnectorRegistry>();
 611
 0612        services.AddSingleton(new InterpartsApiConfig
 0613        {
 0614            Token = configuration["Interparts:Token"] ?? string.Empty,
 0615            BaseUrl = configuration["Interparts:BaseUrl"] ?? string.Empty
 0616        });
 617
 0618        services.AddSingleton<InterpartsDataManager>();
 619
 620        // Telegram-бот: токен и чат берутся из секции TelegramBot. RemoveAllLoggers — токен входит в URL
 621        // запроса и не должен попадать в стандартные логи HttpClient.
 0622        var telegramBotConfig = configuration.GetSection(TelegramBotConfig.SectionName).Get<TelegramBotConfig>()
 0623                                ?? new TelegramBotConfig();
 0624        services.AddSingleton(telegramBotConfig);
 0625        services.AddHttpClient<TelegramBotApiClient>(client =>
 0626            {
 0627                client.BaseAddress = new Uri(telegramBotConfig.BaseUrl.TrimEnd('/') + "/");
 0628                client.Timeout = TimeSpan.FromSeconds(Math.Max(1, telegramBotConfig.TimeoutSeconds));
 0629            })
 0630            .RemoveAllLoggers();
 0631        services.AddTransient<ITelegramNotifier, TelegramNotifier>();
 0632        services.AddTransient<NewOrdersTelegramNotifier>();
 0633        services.AddTransient<Services.BitrixStockServices.BitrixStockRolloutTelegramReporter>();
 634
 635        // OpenRouter AI (OpenAI-compatible API)
 0636        var openRouterConfig = new OpenRouterApiConfig
 0637        {
 0638            ApiKey         = configuration["OpenRouter:ApiKey"] ?? string.Empty,
 0639            BaseUrl        = configuration["OpenRouter:BaseUrl"] ?? "https://openrouter.ai/api/v1",
 0640            Model          = configuration["OpenRouter:Model"]   ?? "openrouter/free",
 0641            ChipsModel     = configuration["OpenRouter:ChipsModel"] ?? "openrouter/auto",
 0642            TimeoutSeconds = int.TryParse(configuration["OpenRouter:TimeoutSeconds"], out var orTimeout) ? orTimeout : 6
 0643        };
 0644        services.AddSingleton(openRouterConfig);
 645
 0646        services.AddHttpClient<OpenRouterApiClient>((sp, client) =>
 0647        {
 0648            var cfg = sp.GetRequiredService<OpenRouterApiConfig>();
 0649            client.BaseAddress = new Uri(cfg.BaseUrl.TrimEnd('/') + "/");
 0650            client.Timeout     = TimeSpan.FromSeconds(cfg.TimeoutSeconds);
 0651            if (!string.IsNullOrWhiteSpace(cfg.ApiKey))
 0652            {
 0653                client.DefaultRequestHeaders.Add("Authorization", $"Bearer {cfg.ApiKey}");
 0654            }
 0655            client.DefaultRequestHeaders.Add("HTTP-Referer", "https://studio2.mpsuper.ru");
 0656            client.DefaultRequestHeaders.Add("X-Title", "Studio2 AI Assistant");
 0657        });
 658
 659        // Partsnavigator: карточка запчасти для модалки товара в гридах (парсер partsnavigator.com.au, 5 воркеров).
 660        // Ретраев Polly нет намеренно: каждый повтор занимает один из 5 браузерных воркеров сервиса.
 0661        var partsnavigatorConfig = configuration.GetSection(PartsnavigatorConfig.SectionName).Get<PartsnavigatorConfig>(
 0662                                   ?? new PartsnavigatorConfig();
 0663        services.AddSingleton(partsnavigatorConfig);
 0664        services.AddHttpClient(PartsnavigatorConfig.HttpClientName, client =>
 0665        {
 0666            client.BaseAddress = new Uri(partsnavigatorConfig.BaseUrl.TrimEnd('/') + "/");
 0667            client.Timeout = TimeSpan.FromSeconds(Math.Max(5, partsnavigatorConfig.TimeoutSeconds));
 0668        });
 0669        services.AddSingleton<IPartsnavigatorApiClient, PartsnavigatorApiClient>();
 0670        services.AddSingleton<IPartsnavigatorService, PartsnavigatorService>();
 671
 672        // Движок Interparts: предложения по артикулу. Ретраев нет: каждый повтор заново опрашивает поставщиков.
 0673        var interpartsEngineConfig = configuration.GetSection(InterpartsEngineConfig.SectionName).Get<InterpartsEngineCo
 0674                                     ?? new InterpartsEngineConfig();
 0675        services.AddSingleton(interpartsEngineConfig);
 0676        services.AddHttpClient(InterpartsEngineConfig.HttpClientName, client =>
 0677        {
 0678            client.BaseAddress = new Uri(interpartsEngineConfig.BaseUrl.TrimEnd('/') + "/");
 0679            client.Timeout = TimeSpan.FromSeconds(Math.Max(5, interpartsEngineConfig.TimeoutSeconds));
 0680            if (!interpartsEngineConfig.IsConfigured) return;
 0681            client.DefaultRequestHeaders.Add("X-Api-Key", interpartsEngineConfig.ApiKey);
 0682            client.DefaultRequestHeaders.Add("X-Customer-Login", interpartsEngineConfig.CustomerLogin);
 0683        });
 0684        services.AddSingleton<IInterpartsEngineApiClient, InterpartsEngineApiClient>();
 0685        services.AddSingleton<IInterpartsEngineService, InterpartsEngineService>();
 686
 0687        services.AddTransient<OpenRouterDataManager>();
 0688        services.AddTransient<AiOrderScopeApplier>();
 0689        services.AddTransient<OrderHistoryPageContextLoader>();
 0690        services.AddTransient<AiProductContextLoader>();
 0691        services.AddTransient<AiChatEnrichmentBuilder>();
 0692        services.AddTransient<AiOzonLiveProductService>();
 0693        services.AddTransient<AiOrderAnalysisService>();
 0694        services.AddTransient<AiOrderWriteService>();
 0695        services.AddTransient<AiOrderSearchAgent>();
 0696        services.AddTransient<AiOrderFilterLlmParser>();
 0697        services.AddTransient<AiOrderFilterQueryStudio>();
 698
 699        // Action chip suggestors (порядок → порядок чипов: read раньше nav)
 0700        services.AddTransient<IAiActionSuggestor, AiArticleActionSuggestor>();
 0701        services.AddTransient<IAiActionSuggestor, AiOrderActionSuggestor>();
 0702        services.AddTransient<AiActionSuggestorAggregator>();
 703
 704        // Services used by Studio2 Orders/transactions controllers.
 0705        services.AddHttpClient("ProxyClient");
 0706        services.AddSingleton<UrlResponseCacheService>();
 0707        services.AddTransient<ProxyHttpClientService>();
 708
 0709        services.AddTransient<AssemblyListService>();
 0710        services.AddScoped<AssemblyListRunRepository>();
 0711        services.AddScoped<AssemblyListGenerationOrchestrator>();
 0712    }
 713}