namespace BlueLaminate.EFCore.Entities; /// Lifecycle of a CSFloat listing as observed across sweeps. public enum ListingStatus { /// Seen in the most recent sweep that covered it. Active = 0, /// /// Previously seen, then absent from a sweep that should have covered it — /// i.e. sold or delisted. The disappearance is the signal; we can't tell sold /// from delisted with certainty, but bounds when. /// Removed = 1, } /// /// One active-market listing observed on CSFloat via the official /// GET /api/v1/listings endpoint. Rows are keyed by CSFloat's own listing /// id and soft-tracked across sweeps: / /// bound the observation window and flips to /// when a once-seen listing stops appearing, /// which approximates a sale/delisting. /// /// A global sweep returns items that may not be in our catalogue, so /// is a best-effort nullable link (resolved by /// def_index + paint_index); the listing stands on its own without it. /// public class Listing { public int Id { get; set; } /// CSFloat's listing id (a snowflake string). Natural key for dedup. public string CsFloatListingId { get; set; } = null!; /// "buy_now" or "auction". public string Type { get; set; } = null!; /// Asking price. public decimal Price { get; set; } /// Currency of . CSFloat lists in USD. public string Currency { get; set; } = "USD"; /// When CSFloat says the listing was created. public DateTimeOffset ListedAt { get; set; } // Item identity. Stored directly (not only via the Skin link) so listings for // items outside our catalogue are still fully described. public int DefIndex { get; set; } public int PaintIndex { get; set; } public string MarketHashName { get; set; } = null!; public string? WearName { get; set; } /// /// Exact float, or null for items with no float at all (e.g. Vanilla knives). /// Null is deliberately distinct from a genuine 0.0 float; a floatless item /// also can't be fingerprinted, so its stays null. /// public decimal? FloatValue { get; set; } public int PaintSeed { get; set; } public bool IsStatTrak { get; set; } public bool IsSouvenir { get; set; } public int StickerCount { get; set; } public string? SellerSteamId { get; set; } public string? InspectLink { get; set; } /// /// Steam asset id of the listed copy. Changes on trade, so not a stable /// identity — but the discriminator that distinguishes duped copies which /// otherwise share an identical fingerprint. /// public string? AssetId { get; set; } /// Best-effort catalogue link, resolved by def_index + paint_index. Null if unmatched. public int? SkinId { get; set; } public Skin? Skin { get; set; } /// /// The wear band this listing belongs to. Unlike this is NOT /// best-effort: the catalogue sweep pages one skin+wear band at a time, so the band /// is set directly from the sweep unit. Null for whole-skin sweeps (e.g. vanilla /// knives with no wear bands). /// public int? ConditionId { get; set; } public SkinCondition? Condition { get; set; } /// /// The physical item (by fingerprint) this listing is for. Many listings over /// time roll up to one instance, forming its market-movement history. Nullable /// because catalogue-less items can't be fingerprinted to a known skin. /// public int? SkinInstanceId { get; set; } public SkinInstance? SkinInstance { get; set; } // Soft-tracking across sweeps. public DateTimeOffset FirstSeenAt { get; set; } public DateTimeOffset LastSeenAt { get; set; } public ListingStatus Status { get; set; } /// When the listing was marked Removed (absent from a sweep). Null while Active. public DateTimeOffset? RemovedAt { get; set; } }