import { aggregateAcrossTeams, formatAggregationResult, loopGet, } from "../lib/loop-api.js"; // ─── Loop API V2: /people/{id} ───────────────────────────────── // Fields: id, title, firstName, lastName, contactNotes, // primaryPhone, primaryPhoneType, primaryEmail, primaryEmailType, // dateCreated, houseNameOrNumber, houseSecondaryNameOrNumber, // street, locality, town, district, county, postcode, // buyerProfiles[], sellerProfiles[], renterIds[], landlordIds[], // gdpr*, communication* // ──────────────────────────────────────────────────────────────── interface LoopPersonDetail { id: number; title?: string; firstName?: string; lastName?: string; contactNotes?: string; primaryPhone?: string; primaryPhoneType?: string; primaryEmail?: string; primaryEmailType?: string; dateCreated?: string; houseNameOrNumber?: string; houseSecondaryNameOrNumber?: string; street?: string; locality?: string; town?: string; district?: string; county?: string; postcode?: string; buyerProfiles?: LoopBuyerProfileRef[]; sellerProfiles?: LoopSellerProfileRef[]; renterIds?: number[]; landlordIds?: number[]; [key: string]: unknown; } interface LoopBuyerProfileRef { id: number; buyerGroupName?: string; maxPrice?: number | null; minBeds?: number; viewingCount?: number; offerCount?: number; [key: string]: unknown; } interface LoopSellerProfileRef { id: number; sellerGroupName?: string; propertyId?: number; propertyAddress?: string; price?: number; propertyStatus?: string; [key: string]: unknown; } // ─── Loop API V2: /people/buyers/{id} ────────────────────────── // Fields: id, buyerGroupName, buyerIsGroup, maxPrice, minBeds, // position, requirementNotes, sellingPosition, purchaseReason, // financiallyVerified, mortgageOffered, insuranceOffered, // people[] (nested PersonSummary), propertyTypes[], searchAreas[], // viewings[], offers[] // ──────────────────────────────────────────────────────────────── interface LoopBuyerDetail { id: number; buyerGroupName?: string; buyerIsGroup?: boolean; maxPrice?: number | null; minBeds?: number; position?: string; requirementNotes?: string; sellingPosition?: string | null; purchaseReason?: string | null; financiallyVerified?: string | null; mortgageOffered?: string | null; insuranceOffered?: string | null; people?: LoopNestedPerson[]; propertyTypes?: string[]; searchAreas?: string[]; viewings?: Record[]; offers?: Record[]; [key: string]: unknown; } // ─── Loop API V2: /people/sellers/{id} ───────────────────────── // Fields: id, sellerGroupName, sellerIsGroup, // people[] (nested PersonSummary), property (nested PropertySummary) // ──────────────────────────────────────────────────────────────── interface LoopSellerDetail { id: number; sellerGroupName?: string; sellerIsGroup?: boolean; people?: LoopNestedPerson[]; property?: Record; [key: string]: unknown; } interface LoopNestedPerson { id: number; title?: string; firstName?: string; lastName?: string; contactNotes?: string; primaryPhone?: string; primaryPhoneType?: string; primaryEmail?: string; primaryEmailType?: string; [key: string]: unknown; } type PeopleRole = "buyers" | "sellers" | "renters" | "landlords"; export async function peopleDetail(params: { accountId: string; personId: number; role?: PeopleRole; teamName?: string; }): Promise { const { accountId, personId, role, teamName } = params; const result = await aggregateAcrossTeams>( accountId, "people", "loop-people-detail", async (apiKey, team) => { const basePath = role ? `/people/${role}` : "/people"; const path = `${basePath}/${personId}`; const data = await loopGet>(apiKey, path, "loop-people-detail", team); if (data && typeof data === "object" && !Array.isArray(data)) { return [data]; } return []; }, { teamName } ); return formatAggregationResult( result, (record) => formatDetailRecord(record, role), "person details" ); } function formatDetailRecord(record: Record, role?: PeopleRole): string { if (role === "buyers") { const p = record as unknown as LoopBuyerDetail; const lines = [`**${p.buyerGroupName ?? "Unknown"}** [ID: ${p.id}]`]; if (p.maxPrice != null) lines.push(`Max price: £${p.maxPrice.toLocaleString("en-GB")}`); if (p.minBeds) lines.push(`Min beds: ${p.minBeds}`); if (p.position && p.position !== "none") lines.push(`Position: ${p.position}`); if (p.sellingPosition) lines.push(`Selling position: ${p.sellingPosition}`); if (p.purchaseReason) lines.push(`Purchase reason: ${p.purchaseReason}`); if (p.financiallyVerified) lines.push(`Financially verified: ${p.financiallyVerified}`); if (p.mortgageOffered) lines.push(`Mortgage: ${p.mortgageOffered}`); if (p.requirementNotes) lines.push(`Requirements: ${p.requirementNotes}`); if (p.propertyTypes?.length) lines.push(`Property types: ${p.propertyTypes.join(", ")}`); if (p.searchAreas?.length) lines.push(`Search areas: ${p.searchAreas.join(", ")}`); if (p.viewings?.length) lines.push(`Viewings: ${p.viewings.length}`); if (p.offers?.length) lines.push(`Offers: ${p.offers.length}`); if (p.people?.length) { lines.push(`\n**Contacts (${p.people.length}):**`); for (const person of p.people) { const name = [person.firstName, person.lastName].filter(Boolean).join(" "); const phone = person.primaryPhone ?? ""; const email = person.primaryEmail ?? ""; const contact = [phone, email].filter(Boolean).join(" | "); lines.push(` - ${name} [ID: ${person.id}]${contact ? ` — ${contact}` : ""}`); } } return lines.join("\n"); } if (role === "sellers" || role === "landlords") { const p = record as unknown as LoopSellerDetail; const lines = [`**${p.sellerGroupName ?? "Unknown"}** [ID: ${p.id}]`]; if (p.property) { const prop = p.property; if (prop.propertyAddress) lines.push(`Property: ${prop.propertyAddress}`); if (prop.price != null) lines.push(`Price: £${Number(prop.price).toLocaleString("en-GB")}`); if (prop.status) lines.push(`Status: ${prop.status}`); if (prop.propertyType) lines.push(`Type: ${prop.propertyType}`); if (prop.propertyId || prop.id) lines.push(`Property ID: ${prop.propertyId ?? prop.id}`); } if (p.people?.length) { lines.push(`\n**Contacts (${p.people.length}):**`); for (const person of p.people) { const name = [person.firstName, person.lastName].filter(Boolean).join(" "); const phone = person.primaryPhone ?? ""; const email = person.primaryEmail ?? ""; const contact = [phone, email].filter(Boolean).join(" | "); lines.push(` - ${name} [ID: ${person.id}]${contact ? ` — ${contact}` : ""}`); } } return lines.join("\n"); } // Generic /people/{id} — PersonDetail schema const p = record as unknown as LoopPersonDetail; const title = p.title?.trim() ? `${p.title.trim()} ` : ""; const name = [p.firstName, p.lastName].filter(Boolean).join(" ") || "Unknown"; const lines = [`**${title}${name}** [ID: ${p.id}]`]; if (p.primaryEmail) lines.push(`Email: ${p.primaryEmail}`); if (p.primaryPhone) lines.push(`Phone: ${p.primaryPhone}`); if (p.dateCreated) lines.push(`Created: ${p.dateCreated.split("T")[0]}`); // Build address from components const addrParts = [ p.houseSecondaryNameOrNumber, p.houseNameOrNumber, p.street, p.locality, p.town, p.postcode, ].filter(Boolean); if (addrParts.length) lines.push(`Address: ${addrParts.join(", ")}`); if (p.contactNotes) lines.push(`Notes: ${p.contactNotes}`); if (p.buyerProfiles?.length) { lines.push(`\n**Buyer profiles (${p.buyerProfiles.length}):**`); for (const bp of p.buyerProfiles) { const bpName = bp.buyerGroupName ?? `Buyer ${bp.id}`; const price = bp.maxPrice != null ? ` max £${bp.maxPrice.toLocaleString("en-GB")}` : ""; lines.push(` - ${bpName} [ID: ${bp.id}]${price}`); } } if (p.sellerProfiles?.length) { lines.push(`\n**Seller profiles (${p.sellerProfiles.length}):**`); for (const sp of p.sellerProfiles) { const spName = sp.sellerGroupName ?? `Seller ${sp.id}`; const addr = sp.propertyAddress ? ` — ${sp.propertyAddress}` : ""; lines.push(` - ${spName} [ID: ${sp.id}]${addr}`); } } return lines.join("\n"); }