docs
    re-site-kit: Market snapshot
    v0.6.0.md

    Market snapshot

    summarizeListings turns a set of listings into the numbers a buyer wants at a glance: how many are for sale, the price range, median price and price per square foot, typical HOA, days on market, bedroom range and how many allow pets. It works on listings your page already fetched, for a building, a neighborhood or a search, with no extra request.

    import { summarizeListings, monthlyHoa } from '@sonordev/re-site-kit'

    Both are pure functions that are safe anywhere: server components, client components, route handlers.

    summarizeListings

    summarizeListings(listings: RealEstateListing[]): ListingStats
    const { listings } = await getBuildingListings('harbor-point', { limit: 50 })
    const stats = summarizeListings(listings)
    // stats.count → 7, stats.medianPrice → 415000, stats.medianMonthlyHoa → 380, ...
    FieldTypeWhat it is
    countnumberHow many listings you passed in, whether or not they have the other values.
    priceMin, priceMaxnumber | nullThe lowest and highest price.
    medianPricenumber | nullThe median price.
    medianPricePerSqftnumber | nullThe median price per square foot.
    medianSqftnumber | nullThe median square footage.
    medianMonthlyHoanumber | nullThe median HOA fee per month, with quarterly and annual fees converted (see monthlyHoa).
    medianDaysOnMarketnumber | nullThe median days on market.
    bedsMin, bedsMaxnumber | nullThe bedroom range. A studio counts as 0.
    petFriendlySharenumber | nullThe share of listings that allow pets, from 0 to 1, counting only listings that say either way.

    How the numbers are worked out:

    • Missing values are skipped, figure by figure. A listing with no square footage still counts toward the median price; it's just left out of the square footage and price per square foot. So one incomplete listing doesn't skew anything.
    • Prices, square footage and HOA fees count only when they're above zero.
    • Price per square foot is the listing's price divided by its square footage when it has both. Otherwise the feed's own price_per_sqft is used, when it has one.
    • Medians are rounded to whole numbers. The price range and petFriendlyShare aren't rounded.
    • With nothing to measure, a figure is null. An empty list gives count: 0 and null everywhere else.

    The snapshot covers exactly the listings you pass in, so fetch the whole set you want to describe. For a building with more listings than one page holds, raise limit.

    Showing the snapshot

    The stats are plain numbers, so format them however your site formats money. This component shows each figure the listings have and skips the rest:

    // components/MarketSnapshot.tsx
    import type { ListingStats } from '@sonordev/re-site-kit'
    
    const usd = (n: number) =>
      new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD',
        maximumFractionDigits: 0,
      }).format(n)
    
    export function MarketSnapshot({ stats }: { stats: ListingStats }) {
      const rows: Array<[string, string | null]> = [
        ['For sale', String(stats.count)],
        ['Median price', stats.medianPrice == null ? null : usd(stats.medianPrice)],
        ['Median $/sqft', stats.medianPricePerSqft == null ? null : usd(stats.medianPricePerSqft)],
        ['Typical HOA', stats.medianMonthlyHoa == null ? null : `${usd(stats.medianMonthlyHoa)}/mo`],
        ['Days on market', stats.medianDaysOnMarket == null ? null : String(stats.medianDaysOnMarket)],
        [
          'Pet friendly',
          stats.petFriendlyShare == null ? null : `${Math.round(stats.petFriendlyShare * 100)}%`,
        ],
      ]
    
      return (
        <dl className="market-snapshot">
          {rows
            .filter((row): row is [string, string] => row[1] != null)
            .map(([label, value]) => (
              <div key={label}>
                <dt>{label}</dt>
                <dd>{value}</dd>
              </div>
            ))}
        </dl>
      )
    }

    Then on any page that has listings:

    <MarketSnapshot stats={summarizeListings(listings)} />

    Buildings and the registry has a full building page with a snapshot.

    monthlyHoa

    monthlyHoa(listing: Pick<RealEstateListing, 'hoa_fee' | 'hoa_frequency'>): number | null

    One listing's HOA fee as a monthly amount, rounded to whole dollars. summarizeListings uses it for medianMonthlyHoa, and it's handy on a listing page too:

    monthlyHoa({ hoa_fee: 900, hoa_frequency: 'Quarterly' }) // 300
    monthlyHoa({ hoa_fee: 1200, hoa_frequency: 'Annually' }) // 100
    monthlyHoa({ hoa_fee: 250, hoa_frequency: null }) // 250 (no frequency means monthly)
    monthlyHoa({ hoa_fee: 250, hoa_frequency: 'Weekly' }) // null

    It understands these frequencies, ignoring case and surrounding spaces:

    FrequencyDivided by
    monthly, month1
    quarterly, quarter3
    semiannually, semi-annually6
    annually, annual, yearly12

    It returns null when there's no fee, the fee isn't above zero, or the frequency is one it doesn't know.