Class: PartyResearch::Adapters::LogoDev
- Defined in:
- app/services/party_research/adapters/logo_dev.rb
Overview
Logo.dev Brand API adapter — a cheap, deterministic lookup keyed on a
company's web domain. We take two things from the payload: the brand's
public social profiles and its one-line business description.
Everything else Logo.dev returns is deliberately ignored. The logo/
brandmark imagery is already covered by PartyProfileImageLookupService
via the image CDN (which costs no credits), colors and
logo_blurhash have no consumer here, and name is the marketing brand
string ("sweetgreen") rather than the legal entity name the CRM tracks.
Complementary to Apollo/PDL rather than a replacement: those resolve
people and firmographics off a name, this resolves what a domain
publicly presents. Because it needs nothing but a domain and costs one
request, it's the only adapter cheap enough to run at customer-creation
time — see the only: %w[logo_dev] enqueue in
Customer::NewCustomer#build_and_save_customer. The full adapter set
still runs later when the lead reaches lead_qualify.
Auth: a secret key (sk_...) as a bearer token, server-side only.
This is NOT the publishable key embedded in img.logo.dev URLs:
logo_dev:
publishable_key: pk_xxx # image CDN (existing)
secret_key: sk_xxx # Brand API (this adapter)
Cost: 5 credits per call against a monthly grant (Pro = 15,000 credits
≈ 3,000 calls). Responses are cached per domain because brand profiles
change on the order of years, and 404s are cached too so an unknown
domain is only paid for once.
Constant Summary collapse
- ENDPOINT =
'https://api.logo.dev/brand'- TIMEOUT_SECONDS =
Logo.dev indexes on demand and documents responses taking up to 30s;
their floor is 15. Nothing is waiting on this — it runs on thelow
Sidekiq queue — so take the generous end. 30- CACHE_TTL =
30.days
- SOCIAL_PLATFORMS =
Logo.dev detects 13 platforms; we map only the five that have a
real ContactPoint category. The others (youtube, github, reddit,
telegram, tiktok…) are dropped on purpose: FindingApplier falls
back to the genericwebsitecategory for platforms it doesn't
recognize, and a YouTube URL sitting in thewebsitecontact point
would then be mistaken for the company domain by every later
lookup — including this adapter's own company_domain.Each maps to the ContactPoint format validation it must satisfy.
Reusing the model's own constants means a proposal can't drift from
whatAcceptwill actually let through — and a finding that fails
validation is worse than no finding, because the reviewer only
discovers it when the accept button blows up. { 'facebook' => ContactPoint::FACEBOOK_REGEX, 'twitter' => ContactPoint::TWITTER_REGEX, 'linkedin' => ContactPoint::LINKEDIN_REGEX, 'instagram' => ContactPoint::INSTAGRAM_REGEX, 'pinterest' => ContactPoint::PINTEREST_REGEX }.freeze
Constants inherited from Base
Base::ALL, Base::PERSONAL_EMAIL_DOMAINS
Instance Attribute Summary
Attributes inherited from Base
Class Method Summary collapse
-
.company_domain(party) ⇒ Object
Company domain, in priority order: 1.
- .configured? ⇒ Boolean
-
.relevant_for?(party) ⇒ Boolean
The Brand API is keyed on a company domain, so this is useful only for Customers we can resolve one for.
Instance Method Summary collapse
Methods inherited from Base
adapter_name, arbiter?, #finding, #initialize, lookup, registered
Constructor Details
This class inherits a constructor from PartyResearch::Adapters::Base
Class Method Details
.company_domain(party) ⇒ Object
Company domain, in priority order:
- The party's website contact point
- A non-webmail email domain on the party
- A non-webmail email domain on one of its contacts
Contacts' websites are deliberately not consulted — those are
routinely third-party portals (CommerceHub and friends), not the
customer's own domain.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'app/services/party_research/adapters/logo_dev.rb', line 88 def company_domain(party) return nil unless party.respond_to?(:contact_points) website = party.contact_points.websites.first&.detail host = host_from(website) return host if host.present? emails = party.contact_points.emails.pluck(:detail) # One query across every contact rather than one per contact — # this runs inside `relevant_for?`, which the picker UI calls for # each adapter on page load. emails += ContactPoint.emails.where(party_id: party.contact_ids).order(:id).pluck(:detail) if party.is_a?(Customer) emails.filter_map { |email| domain_from_email(email) }.first end |
.configured? ⇒ Boolean
66 67 68 |
# File 'app/services/party_research/adapters/logo_dev.rb', line 66 def configured? Heatwave::Configuration.fetch(:logo_dev, :secret_key).present? end |
.relevant_for?(party) ⇒ Boolean
The Brand API is keyed on a company domain, so this is useful
only for Customers we can resolve one for. Homeowners and
webmail-only leads have nothing to look up.
73 74 75 76 77 78 |
# File 'app/services/party_research/adapters/logo_dev.rb', line 73 def relevant_for?(party) return false unless party.is_a?(Customer) return false if party.try(:homeowner?) company_domain(party).present? end |
Instance Method Details
#findings_for ⇒ Object
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'app/services/party_research/adapters/logo_dev.rb', line 126 def findings_for unless self.class.configured? raise ConfigurationError, 'Logo.dev secret_key is not configured (Heatwave::Configuration[:logo_dev, :secret_key])' end domain = self.class.company_domain(party) return [] if domain.blank? brand = fetch(domain) return [] if brand.blank? rationale = "Logo.dev Brand profile for #{domain}" (brand, rationale) + description_findings(brand, rationale) end |