()
| 773 | // ── Inventory Profile Building ─────────────────────────────────── |
| 774 | |
| 775 | private async buildInventoryProfiles(): Promise<Map<string, ProfileUpsertInput>> { |
| 776 | const profileMap = new Map<string, ProfileUpsertInput>(); |
| 777 | if (!this.profilesDb) return profileMap; |
| 778 | |
| 779 | const agents = await this.federatedIndex.listAllAgents(); |
| 780 | const profiles: ProfileUpsertInput[] = []; |
| 781 | |
| 782 | for (const agent of agents) { |
| 783 | const domains = await this.federatedIndex.getDomainsForAgent(agent.url); |
| 784 | if (domains.length === 0) continue; |
| 785 | |
| 786 | const markets = new Set<string>(); |
| 787 | const propertyTypes = new Set<string>(); |
| 788 | const tags = new Set<string>(); |
| 789 | const deliveryTypes = new Set<string>(); |
| 790 | const publisherDomains = new Set<string>(domains); |
| 791 | let propertyCount = 0; |
| 792 | |
| 793 | // Get properties for this agent |
| 794 | const properties = await this.federatedIndex.getPropertiesForAgent(agent.url); |
| 795 | for (const prop of properties) { |
| 796 | propertyCount++; |
| 797 | if (prop.property_type) propertyTypes.add(prop.property_type); |
| 798 | if (prop.tags) prop.tags.forEach(t => tags.add(t)); |
| 799 | } |
| 800 | |
| 801 | // Derive channels from property types |
| 802 | const channels = new Set<string>(); |
| 803 | if (propertyTypes.has('website')) channels.add('display'); |
| 804 | if (propertyTypes.has('ctv_app')) channels.add('ctv'); |
| 805 | if (propertyTypes.has('mobile_app')) channels.add('mobile'); |
| 806 | if (propertyTypes.has('audio_stream') || propertyTypes.has('podcast')) channels.add('audio'); |
| 807 | if (propertyTypes.has('dooh_screen')) channels.add('dooh'); |
| 808 | |
| 809 | const profile: ProfileUpsertInput = { |
| 810 | agent_url: agent.url, |
| 811 | channels: [...channels], |
| 812 | property_types: [...propertyTypes], |
| 813 | markets: [...markets], |
| 814 | categories: [], // Populated when collections have genre_taxonomy |
| 815 | tags: [...tags], |
| 816 | delivery_types: [...deliveryTypes], |
| 817 | property_count: propertyCount, |
| 818 | publisher_count: publisherDomains.size, |
| 819 | has_tmp: false, // Updated when TMP registration data is available |
| 820 | }; |
| 821 | profiles.push(profile); |
| 822 | profileMap.set(agent.url, profile); |
| 823 | } |
| 824 | |
| 825 | if (profiles.length > 0) { |
| 826 | await this.profilesDb.upsertProfiles(profiles); |
| 827 | const currentUrls = profiles.map(p => p.agent_url); |
| 828 | const staleDeleted = await this.profilesDb.deleteStaleProfiles(currentUrls); |
| 829 | if (staleDeleted > 0) { |
| 830 | log.info({ deleted: staleDeleted }, 'Stale inventory profiles cleaned up'); |
| 831 | } |
| 832 | log.info({ profileCount: profiles.length }, 'Inventory profiles updated'); |
no test coverage detected