namespace FinlyticCore.Util;
///
/// Single source of truth for every MQTT topic name and RPC channel name used across the Finlytic microservice
/// fleet (FinlyticAssets, FinlyticNews, FinlyticSentiment, FinlyticFundamentals, FinlyticTechnicals,
/// FinlyticEngine, FinlyticSimulation, FinlyticBot, and the FinlyticBackend aggregation bridge).
/// Before this class existed, every service built topic strings via ad-hoc interpolation, so publishers and
/// subscribers were only ever kept in sync by naming convention. Any new topic or RPC channel must be added
/// here and referenced from call sites instead of being written as a literal.
///
public static class MqttTopics
{
// ---------------------------------------------------------------------------------------------------
// RPC envelope: services/request/{channel}/{correlationId} <-> services/response/{channel}/{correlationId}
// See ManagedMqttClient.SendRpcRequestAsync / SubscribeRpcAsync for the runtime mechanics.
// ---------------------------------------------------------------------------------------------------
private const string RequestRoot = "services/request";
private const string ResponseRoot = "services/response";
///
/// Gets the literal prefix ("services/request/") that precedes every RPC channel name in a request topic.
/// Used to strip the prefix back off when a caller passes a full topic instead of a bare channel name.
///
public const string RequestPrefix = RequestRoot + "/";
///
/// Gets the literal prefix ("services/response/") that precedes every RPC channel name in a response topic.
/// Used to detect whether an incoming message belongs to the RPC response convention.
///
public const string ResponsePrefix = ResponseRoot + "/";
///
/// Gets the wildcard filter that matches every RPC response, regardless of channel or correlation ID.
/// Every service subscribes to this once at startup so pending SendRpcRequestAsync calls can resolve.
///
public const string ResponseWildcard = ResponseRoot + "/#";
///
/// Builds the concrete RPC request topic for a channel and correlation ID: services/request/{channel}/{correlationId}.
///
public static string RequestTopic(string channel, string correlationId) => $"{RequestRoot}/{channel}/{correlationId}";
///
/// Builds the concrete RPC response topic for a channel and correlation ID: services/response/{channel}/{correlationId}.
///
public static string ResponseTopic(string channel, string correlationId) => $"{ResponseRoot}/{channel}/{correlationId}";
///
/// Builds the subscription wildcard filter that matches every request on a given RPC channel: services/request/{channel}/#.
///
public static string RequestFilter(string channel) => $"{RequestRoot}/{channel}/#";
///
/// Named RPC channel identifiers (the {channel} segment of the request/response envelope above),
/// grouped by the service that owns/serves each channel.
///
public static class Channels
{
///
/// Shared liveness-check channel implemented identically by every service. The request topic carries the
/// target service name as an extra path segment so only the addressed service responds.
///
public const string HealthPing = "health_Ping";
// ---- FinlyticAssets ----
/// Served by FinlyticAssets: resolves valid assets for an ISIN.
public const string AssetsGet = "assets_Get";
/// Served by FinlyticAssets: returns the curated discovery/watchlist asset set.
public const string AssetsGetDiscovery = "assets_GetDiscovery";
/// Served by FinlyticAssets: resolves derivative instruments for an underlying ISIN.
public const string AssetsGetDerivatives = "assets_GetDerivatives";
/// Served by FinlyticAssets: returns a live Trade Republic price tick for an ISIN.
public const string TrGetLivePrice = "tr_GetLivePrice";
/// Served by FinlyticAssets: returns all dynamic settings for the service.
public const string AssetsSettingsGetAll = "assets_settings_GetAll";
/// Served by FinlyticAssets: applies dynamic setting updates for the service.
public const string AssetsSettingsUpdate = "assets_settings_Update";
// ---- FinlyticNews ----
/// Served by FinlyticNews: returns filtered/paginated news articles.
public const string NewsGet = "news_Get";
/// Served by FinlyticNews: returns a single article by ID.
public const string NewsGetById = "news_GetById";
/// Served by FinlyticNews: returns articles awaiting downstream sentiment analysis.
public const string NewsGetPending = "news_GetPending";
/// Served by FinlyticNews: updates the processing status of an article.
public const string NewsUpdateStatus = "news_UpdateStatus";
/// Served by FinlyticNews: returns all dynamic settings for the service.
public const string NewsSettingsGetAll = "news_settings_GetAll";
/// Served by FinlyticNews: applies dynamic setting updates for the service.
public const string NewsSettingsUpdate = "news_settings_Update";
// ---- FinlyticSentiment ----
/// Served by FinlyticSentiment: returns the pre-aggregated sentiment summary for an ISIN.
public const string SentimentGetIsin = "sentiment_GetIsin";
/// Served by FinlyticSentiment: returns the pre-aggregated sentiment summary for a sector.
public const string SentimentGetSector = "sentiment_GetSector";
/// Served by FinlyticSentiment: returns the persisted FinBERT analysis entry for a single article.
public const string SentimentGetArticle = "sentiment_GetArticle";
/// Served by FinlyticSentiment: returns paginated per-company sentiment summaries.
public const string SentimentGetAll = "sentiment_GetAll";
/// Served by FinlyticSentiment: runs FinBERT analysis for an inline article payload or article ID.
public const string SentimentAnalyze = "sentiment_Analyze";
/// Served by FinlyticSentiment: returns all dynamic settings for the service.
public const string SentimentSettingsGetAll = "sentiment_settings_GetAll";
/// Served by FinlyticSentiment: applies dynamic setting updates for the service.
public const string SentimentSettingsUpdate = "sentiment_settings_Update";
// ---- FinlyticFundamentals ----
/// Served by FinlyticFundamentals: returns fundamentals data for an ISIN/ticker.
public const string FundamentalsGet = "fundamentals_Get";
/// Served by FinlyticFundamentals: returns all known calendar events.
public const string EventsGetAll = "events_GetAll";
/// Served by FinlyticFundamentals: returns calendar events for a given year/month.
public const string EventsGetByMonth = "events_GetByMonth";
/// Served by FinlyticFundamentals: returns all dynamic settings for the service.
public const string FundamentalsSettingsGetAll = "fundamentals_settings_GetAll";
/// Served by FinlyticFundamentals: applies dynamic setting updates for the service.
public const string FundamentalsSettingsUpdate = "fundamentals_settings_Update";
// ---- FinlyticTechnicals ----
/// Served by FinlyticTechnicals: returns the technical analysis DTO for an ISIN.
public const string TaGetAnalysis = "ta_GetAnalysis";
/// Served by FinlyticTechnicals: returns active strategy setups for a single ISIN.
public const string TaGetSetupsForIsin = "ta_GetSetupsForIsin";
/// Served by FinlyticTechnicals: returns active strategy setups across the universe.
public const string TaGetSetups = "ta_GetSetups";
/// Served by FinlyticTechnicals: returns aggregated candles for an ISIN/timeframe.
public const string TaGetCandles = "ta_GetCandles";
/// Served by FinlyticTechnicals: returns the current monitored scan universe ("watchlist").
public const string TaGetWatchlist = "ta_GetWatchlist";
/// Served by FinlyticTechnicals: returns an ISIN's recent setup/score history (see ).
public const string TaGetRecentSetupHistory = "ta_GetRecentSetupHistory";
/// Served by FinlyticTechnicals: returns all dynamic settings for the service.
public const string TaSettingsGetAll = "ta_settings_GetAll";
/// Served by FinlyticTechnicals: applies dynamic setting updates for the service.
public const string TaSettingsUpdate = "ta_settings_Update";
// ---- FinlyticEngine ----
/// Served by FinlyticEngine: returns trade proposals.
public const string EngineGetProposals = "engine_GetProposals";
/// Served by FinlyticEngine: returns active trades.
public const string EngineGetTrades = "engine_GetTrades";
/// Served by FinlyticEngine: evaluates a single ISIN and returns a trade proposal if warranted.
public const string EngineEvaluateIsin = "engine_EvaluateIsin";
/// Served by FinlyticEngine: records a fill against an active trade.
public const string EngineAddFill = "engine_AddFill";
/// Served by FinlyticEngine: updates the stop-loss of an active trade.
public const string EngineUpdateStopLoss = "engine_UpdateStopLoss";
/// Served by FinlyticEngine: closes an active trade.
public const string EngineCloseTrade = "engine_CloseTrade";
///
/// Served by FinlyticEngine: accepts a proposal on behalf of one user and creates a trade owned by that
/// user. Takes an . The proposal is NOT consumed — it stays
/// available for other users until it expires.
/// There is deliberately no counterpart channel for declining a proposal: declining has no server-side
/// effect and is handled entirely in the client.
///
public const string EngineAcceptProposal = "engine_AcceptProposal";
///
/// Served by FinlyticEngine: opens a trade owned by one user with no backing proposal (manual entry from
/// the Web UI). Takes a . Unlike
/// , the resulting trade's ProposalId is .
///
public const string EngineCreateManualTrade = "engine_CreateManualTrade";
///
/// Served by FinlyticEngine: returns a paginated, filtered history of every persisted evaluation
/// snapshot (EngineEvaluationSnapshotEntity) for the admin-only "why no proposals" Web UI tab.
/// Takes a and returns a
/// .
///
public const string EngineGetEvaluationHistory = "engine_GetEvaluationHistory";
/// Served by FinlyticEngine: returns all dynamic settings for the service.
public const string EngineSettingsGetAll = "engine_settings_GetAll";
/// Served by FinlyticEngine: applies dynamic setting updates for the service.
public const string EngineSettingsUpdate = "engine_settings_Update";
// ---- FinlyticSimulation ----
/// Served by FinlyticSimulation: runs a quantitative backtest.
public const string SimRunBacktest = "sim_RunBacktest";
/// Served by FinlyticSimulation: returns the reliability score for a strategy/asset/timeframe.
public const string SimGetReliability = "sim_GetReliability";
/// Served by FinlyticSimulation: returns the full strategy reliability matrix for an asset.
public const string SimGetMatrixForAsset = "sim_GetMatrixForAsset";
///
/// Served by FinlyticSimulation: returns a paginated, filterable summary history of past backtest runs
/// for an ISIN - every run is already persisted (SimulationRunEntity) but was previously only
/// reachable indirectly (it fed the reliability matrix), never queryable as a history in its own right.
///
public const string SimGetBacktestHistory = "sim_GetBacktestHistory";
/// Served by FinlyticSimulation: returns the full, already-persisted report (trades + equity curve) for one past backtest run by its RunId.
public const string SimGetBacktestRunDetail = "sim_GetBacktestRunDetail";
/// Served by FinlyticSimulation: returns a saved per-asset/per-strategy indicator parameter profile, or null if none was saved.
public const string SimGetStrategyParameters = "sim_GetStrategyParameters";
/// Served by FinlyticSimulation: saves/updates a per-asset/per-strategy indicator parameter profile.
public const string SimSaveStrategyParameters = "sim_SaveStrategyParameters";
/// Served by FinlyticSimulation: returns all dynamic settings for the service.
public const string SimSettingsGetAll = "sim_settings_GetAll";
/// Served by FinlyticSimulation: applies dynamic setting updates for the service.
public const string SimSettingsUpdate = "sim_settings_Update";
// ---- FinlyticBot ----
/// Served by FinlyticBot: returns the current paper-trading bot status.
public const string BotGetStatus = "bot_GetStatus";
/// Served by FinlyticBot: returns currently open paper-trading positions.
public const string BotGetPositions = "bot_GetPositions";
/// Served by FinlyticBot: returns the paper-trading account summary.
public const string BotGetSummary = "bot_GetSummary";
/// Served by FinlyticBot: executes a trade proposal as a paper trade.
public const string BotExecuteProposal = "bot_ExecuteProposal";
///
/// Served by FinlyticBot: emergency-closes every open paper-trading position (synthetic ledger
/// positions are closed unconditionally; Alpaca positions are only closed if the broker confirms the
/// liquidation and are otherwise left open and reported as skipped — see the handler for details).
///
public const string BotPanicClose = "bot_PanicClose";
/// Served by FinlyticBot: returns all dynamic settings for the service.
public const string BotSettingsGetAll = "bot_settings_GetAll";
/// Served by FinlyticBot: applies dynamic setting updates for the service.
public const string BotSettingsUpdate = "bot_settings_Update";
// ---- FinlyticBackend ----
///
/// Served by FinlyticBackend: returns the aggregated favorites list across all users. Centralized here
/// even though FinlyticBackend is outside this refactor's scope, so no future service hardcodes it again.
///
public const string BackendGetAggregatedFavorites = "backend_GetAggregatedFavorites";
}
// ---------------------------------------------------------------------------------------------------
// Event / stream topics: plain fire-and-forget pub/sub outside the RPC envelope.
// ---------------------------------------------------------------------------------------------------
///
/// Published by FinlyticNews once an article finishes ingestion and asset matching. Consumed by
/// FinlyticSentiment (to trigger analysis) and the FinlyticBackend bridge.
///
public const string NewsCompleted = "services/news/completed";
///
/// Gets the literal prefix ("finlytic/news/") shared by every FinlyticNews event topic.
/// and every per-ISIN topic are derived from this constant so a StartsWith check (as used by
/// the FinlyticBackend bridge) can never drift from the wildcard subscription filter.
///
public const string NewsPrefix = "finlytic/news/";
private const string NewsStreamTemplate = NewsPrefix + "stream/{0}";
///
/// Builds the per-ISIN topic that FinlyticNews publishes newly matched articles to: finlytic/news/stream/{isin}.
/// The ISIN is normalized (trimmed, lower-cased) to match the convention already used by every publisher/subscriber pair.
///
public static string NewsStream(string isin) => string.Format(NewsStreamTemplate, NormalizeIsin(isin));
///
/// Wildcard filter matching every FinlyticNews stream topic, used by the FinlyticBackend bridge.
///
public const string NewsStreamWildcard = NewsPrefix + "#";
///
/// Gets the literal prefix ("finlytic/sentiment/") shared by every FinlyticSentiment event topic. Used to
/// detect whether an incoming message on the subscription is a sentiment event.
/// and every per-ISIN topic are derived from this
/// constant so they cannot drift apart.
///
public const string SentimentPrefix = "finlytic/sentiment/";
private const string SentimentStreamTemplate = SentimentPrefix + "stream/{0}";
///
/// Builds the per-ISIN topic that FinlyticSentiment publishes updated sentiment summaries to: finlytic/sentiment/stream/{isin}.
///
public static string SentimentStream(string isin) => string.Format(SentimentStreamTemplate, NormalizeIsin(isin));
///
/// Wildcard filter matching every FinlyticSentiment topic (currently only the per-ISIN stream). Used by
/// FinlyticTechnicals to detect sentiment spikes and by the FinlyticBackend bridge.
///
public const string SentimentWildcard = SentimentPrefix + "#";
///
/// Gets the literal prefix ("finlytic/engine/") shared by every FinlyticEngine event topic (proposals and
/// trade status changes). is derived from this constant, and
/// / are namespaced sub-prefixes of it, so
/// none of the three can drift apart from one another.
///
public const string EnginePrefix = "finlytic/engine/";
///
/// Wildcard filter matching every FinlyticEngine event topic (proposals and trade status changes). Used by
/// the FinlyticBackend bridge.
///
public const string EngineWildcard = EnginePrefix + "#";
///
/// Gets the literal prefix ("finlytic/engine/proposals/") shared by every FinlyticEngine proposal event
/// topic. Used by the FinlyticBackend bridge to distinguish proposal events from trade status events on the
/// shared subscription.
///
public const string EngineProposalsPrefix = EnginePrefix + "proposals/";
///
/// Published by FinlyticEngine whenever a new trade proposal is created. Consumed by FinlyticBot (to
/// evaluate auto-execution) and the FinlyticBackend bridge.
///
public const string EngineProposalsCreated = EngineProposalsPrefix + "created";
///
/// Gets the literal prefix ("finlytic/engine/trades/") shared by every FinlyticEngine trade lifecycle event
/// topic. Used by the FinlyticBackend bridge to distinguish trade status events from proposal events on the
/// shared subscription.
///
public const string EngineTradesPrefix = EnginePrefix + "trades/";
///
/// Published by FinlyticEngine whenever an active trade's lifecycle status changes (fills, stop-loss
/// updates, closes). Consumed by the FinlyticBackend bridge.
///
public const string EngineTradesStatusChanged = EngineTradesPrefix + "status_changed";
///
/// Gets the literal prefix ("finlytic/bot/") shared by every FinlyticBot event topic.
/// and are derived from this constant so they cannot drift apart.
///
public const string BotPrefix = "finlytic/bot/";
///
/// Gets the literal prefix ("finlytic/bot/trades/") shared by every FinlyticBot trade lifecycle event topic.
/// Used by the FinlyticBackend bridge to distinguish trade stream events from other bot events on the shared
/// subscription.
///
public const string BotTradesPrefix = BotPrefix + "trades/";
///
/// Published by FinlyticBot whenever a paper-trading position's lifecycle status changes. Consumed by the
/// FinlyticBackend bridge.
///
public const string BotTradesStream = BotTradesPrefix + "stream";
///
/// Wildcard filter matching every FinlyticBot event topic. Used by the FinlyticBackend bridge.
///
public const string BotWildcard = BotPrefix + "#";
///
/// Gets the literal prefix ("finlytic/logs/") shared by every structured-log broadcast topic.
/// and every per-service topic are derived from this constant.
///
public const string LogsPrefix = "finlytic/logs/";
private const string LogsTemplate = LogsPrefix + "{0}";
///
/// Builds the structured-log broadcast topic for a given service name (e.g. finlytic/logs/FinlyticAssets),
/// published by every service's hook and consumed by the FinlyticBackend bridge.
///
public static string Logs(string serviceName) => string.Format(LogsTemplate, serviceName);
///
/// Wildcard filter matching structured-log broadcasts from every service. Used by the FinlyticBackend bridge.
///
public const string LogsWildcard = LogsPrefix + "#";
///
/// Normalizes an ISIN for use as an MQTT topic path segment. MQTT topics are case-sensitive and every known
/// publisher/subscriber pair in this system agreed on trimmed, lower-case ISINs; this keeps that convention
/// in one place instead of repeating .Trim().ToLowerInvariant() at every call site.
///
/// Thrown when is null, empty, or whitespace.
private static string NormalizeIsin(string isin)
{
if (string.IsNullOrWhiteSpace(isin))
throw new ArgumentException("ISIN must not be null or empty when building an MQTT topic.", nameof(isin));
return isin.Trim().ToLowerInvariant();
}
}