Class: PartyResearch::Adapters::Base

Inherits:
Object
  • Object
show all
Defined in:
app/services/party_research/adapters/base.rb

Direct Known Subclasses

Apollo, Gemini, GooglePlaces, PeopleDataLabs

Defined Under Namespace

Classes: AdapterError, ConfigurationError

Constant Summary collapse

ALL =

Explicit registry — adding a new adapter requires registering it here.
We intentionally don't use .descendants because Zeitwerk only loads
subclasses when something references them; in dev mode the list would
be empty until each adapter class is referenced elsewhere.

Ordering matters: Gemini runs last and acts as the arbiter, taking
all the other adapters' raw findings as context for synthesis.

%w[apollo people_data_labs google_places gemini].freeze
PERSONAL_EMAIL_DOMAINS =

Consumer/webmail domains that don't anchor a company lookup. Shared
by adapters that derive a company domain (PDL) or judge whether an
email is a corporate anchor worth grounding on (Gemini). Subclasses
reference it unqualified via constant inheritance.

%w[
  gmail.com yahoo.com hotmail.com outlook.com aol.com icloud.com
  me.com mac.com live.com msn.com proton.me protonmail.com
  ymail.com rocketmail.com att.net comcast.net verizon.net
  sbcglobal.net cox.net charter.net bellsouth.net juno.net
  earthlink.net optonline.net mindspring.net netzero.net
].to_set.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(party, raw_findings: []) ⇒ Base

The Gemini arbiter receives raw_findings: — every other
adapter ignores it. Defaulted to [] so existing call sites
don't need to change.



92
93
94
95
# File 'app/services/party_research/adapters/base.rb', line 92

def initialize(party, raw_findings: [])
  @party = party
  @raw_findings = raw_findings
end

Instance Attribute Details

#partyObject (readonly, protected)

Returns the value of attribute party.



104
105
106
# File 'app/services/party_research/adapters/base.rb', line 104

def party
  @party
end

#raw_findingsObject (readonly, protected)

Returns the value of attribute raw_findings.



104
105
106
# File 'app/services/party_research/adapters/base.rb', line 104

def raw_findings
  @raw_findings
end

Class Method Details

.adapter_nameObject



46
47
48
# File 'app/services/party_research/adapters/base.rb', line 46

def adapter_name
  name.demodulize.underscore
end

.arbiter?Boolean

Whether this adapter SYNTHESIZES findings from raw inputs
(Gemini) versus emitting raw API-derived findings of its own
(everyone else). Used by the orchestrator to flag the resulting
party_research_findings.arbiter column.

Returns:

  • (Boolean)


72
73
74
# File 'app/services/party_research/adapters/base.rb', line 72

def arbiter?
  false
end

.configured?Boolean

Override to return false when credentials are missing. The default
returns true so adapters that don't need configuration (a noop
test adapter, an LLM with a globally-set key) work without extra
boilerplate.

Returns:

  • (Boolean)


54
55
56
# File 'app/services/party_research/adapters/base.rb', line 54

def configured?
  true
end

.lookup(name) ⇒ Object



76
77
78
79
80
81
# File 'app/services/party_research/adapters/base.rb', line 76

def lookup(name)
  klass = "PartyResearch::Adapters::#{name.to_s.camelize}".safe_constantize
  return nil unless klass && klass < self

  klass
end

.registeredObject

All registered adapter classes, in display order.



84
85
86
# File 'app/services/party_research/adapters/base.rb', line 84

def registered
  ALL.filter_map { |name| lookup(name) }
end

.relevant_for?(_party) ⇒ Boolean

Override to declare whether this adapter is likely to return
useful findings for the given party. The picker UI uses this to
decide whether to check the box by default — irrelevant adapters
still appear (so the user can opt in) but start unchecked. The
default is "true" so adapters without an opinion (a hypothetical
universal lookup) are checked by default.

Returns:

  • (Boolean)


64
65
66
# File 'app/services/party_research/adapters/base.rb', line 64

def relevant_for?(_party)
  true
end

Instance Method Details

#finding(category:, proposed_value:, field: nil, confidence: nil, evidence: {}) ⇒ Object (protected)

Build a normalized finding hash. Categories outside PartyResearchFinding::CATEGORIES
are rejected at the DB layer (Postgres enum), so a typo crashes the
transaction rather than silently writing junk.



109
110
111
112
113
114
115
116
117
118
# File 'app/services/party_research/adapters/base.rb', line 109

def finding(category:, proposed_value:, field: nil, confidence: nil, evidence: {})
  {
    adapter: self.class.adapter_name,
    category: category.to_s,
    field: field,
    proposed_value: proposed_value,
    confidence: confidence,
    evidence: evidence
  }
end

#findings_forArray<Hash>

Returns finding-attribute hashes — see #finding.

Returns:

  • (Array<Hash>)

    finding-attribute hashes — see #finding

Raises:

  • (NotImplementedError)


98
99
100
# File 'app/services/party_research/adapters/base.rb', line 98

def findings_for
  raise NotImplementedError, "#{self.class} must implement #findings_for"
end