import {z} from 'zod' export type GetApiV1AvmPropertyIdArgs = {propertyId: string}; export type GetApiV1AvmPropertyIdOptions = {baseUrl?: string | undefined, apikey?: string | undefined, fetch?: typeof fetch | undefined}; export const domainAvmV1Address = z.object({unitNumber: z.string().nullable().optional(), streetNumber: z.string().nullable().optional(), streetName: z.string().nullable().optional(), streetType: z.string().nullable().optional(), locality: z.string().nullable().optional(), postcode: z.string().nullable().optional(), state: z.string().nullable().optional()}); export const domainAvmV1AddressCoordinate = z.object({lat: z.number().nullable().optional(), lon: z.number().nullable().optional()}); export const domainAvmV1ActivityBoundary = z.object({advertisedDate: z.string().nullable().optional(), advertisedPrice: z.number().int().nullable().optional(), agency: z.string().nullable().optional(), suppressPrice: z.boolean().nullable().optional()}); export const domainAvmV1RentalActivity = z.object({first: domainAvmV1ActivityBoundary.optional(), last: domainAvmV1ActivityBoundary.optional(), rentedPrice: z.number().int().nullable().optional(), status: z.string().nullable().optional()}); export const domainAvmV1SaleActivity = z.object({date: z.string().nullable().optional(), price: z.number().int().nullable().optional(), type: z.string().nullable().optional()}); export const domainAvmV1History = z.object({rentals: z.array(domainAvmV1RentalActivity).nullable().optional(), sales: z.array(domainAvmV1SaleActivity).nullable().optional()}); export const domainAvmV1Photo = z.object({fullUrl: z.string().nullable().optional(), rank: z.number().int().nullable().optional()}); export const domainAvmV1RentalAvm = z.object({estimateDate: z.string().nullable().optional(), percentYieldRentEstimate: z.number().nullable().optional(), rentalFsd: z.number().nullable().optional(), weeklyRentEstimate: z.number().int().nullable().optional()}); export const domainAvmV1SaleAvm = z.object({confidenceLevel: z.string().nullable().optional(), estimate: z.number().int().nullable().optional(), estimateDate: z.string().nullable().optional(), estimateHigh: z.number().int().nullable().optional(), estimateLow: z.number().int().nullable().optional(), percentFsd: z.number().nullable().optional()}); export const domainAvmV1Advert = z.object({displayPrice: z.string().nullable().optional(), status: z.string().nullable().optional()}); export const domainAvmV1Sale = z.object({date: z.string().nullable().optional(), price: z.number().int().nullable().optional(), type: z.string().nullable().optional()}); export const domainAvmV1Item = z.object({address: z.string().nullable().optional(), addressCoordinate: domainAvmV1AddressCoordinate.optional(), adverts: z.array(domainAvmV1Advert).nullable().optional(), areaSize: z.number().nullable().optional(), bathrooms: z.number().int().nullable().optional(), bedrooms: z.number().int().nullable().optional(), buildingArea: z.number().nullable().optional(), carSpaces: z.number().int().nullable().optional(), lastSaleActivity: domainAvmV1Sale.optional(), id: z.string().nullable().optional(), photos: z.array(domainAvmV1Photo).nullable().optional(), priceHeading: z.string().nullable().optional()}); export const domainAvmV1SalesGrowth = z.object({annualGrowth: z.number().nullable().optional(), medianSoldPrice: z.number().int().nullable().optional(), numberSold: z.number().int().nullable().optional(), year: z.string().nullable().optional()}); export const domainAvmV1PropertyCategory = z.object({auctionClearanceRate: z.number().nullable().optional(), bedrooms: z.number().int().nullable().optional(), daysOnMarket: z.number().int().nullable().optional(), entryLevelPrice: z.number().int().nullable().optional(), luxuryLevelPrice: z.number().int().nullable().optional(), medianRentPrice: z.number().int().nullable().optional(), medianSoldPrice: z.number().int().nullable().optional(), numberSold: z.number().int().nullable().optional(), propertyCategory: z.string().nullable().optional(), salesGrowthList: z.array(domainAvmV1SalesGrowth).nullable().optional()}); export const domainAvmV1AvmResponse = z.object({address: domainAvmV1Address.optional(), addressCoordinate: domainAvmV1AddressCoordinate.optional(), areaSize: z.number().nullable().optional(), bathrooms: z.number().int().nullable().optional(), bedrooms: z.number().int().nullable().optional(), buildingArea: z.number().nullable().optional(), carSpaces: z.number().int().nullable().optional(), history: domainAvmV1History.optional(), legalParcel: z.string().nullable().optional(), photos: z.array(domainAvmV1Photo).nullable().optional(), propertyId: z.string().nullable().optional(), propertyType: z.string().nullable().optional(), rentalAvm: domainAvmV1RentalAvm.optional(), saleAvm: domainAvmV1SaleAvm.optional(), similarListings: z.array(domainAvmV1Item).nullable().optional(), similarRentals: z.array(domainAvmV1Item).nullable().optional(), similarSolds: z.array(domainAvmV1Item).nullable().optional(), suburbPerformance: domainAvmV1PropertyCategory.optional(), yearBuilt: z.number().int().nullable().optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Creates a valuation for a given property id. * It uses a combination of the cache valuation and Domain API calls to populate the report. * * GET /v1/avm/{propertyId} * * Also accepts auth: oauth2 */ export const getApiV1AvmPropertyId = async (args: GetApiV1AvmPropertyIdArgs, options?: GetApiV1AvmPropertyIdOptions) => { const {baseUrl = "https://api.domain.com.au", fetch: fetchFn = fetch, apikey} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (apikey !== undefined) headers.set("x-api-key", apikey) const url = `${baseUrl.replace(/\/+$/, '')}/v1/avm/${encodeURIComponent(String(args.propertyId))}${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(domainAvmV1AvmResponse, await res.json()) };