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; }
}