leads-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- company_discovery/__init__.py +4 -0
- company_discovery/adapters/__init__.py +5 -0
- company_discovery/adapters/apollo.py +189 -0
- company_discovery/adapters/exa.py +112 -0
- company_discovery/adapters/llm.py +118 -0
- company_discovery/adapters/protocols.py +58 -0
- company_discovery/adapters/website.py +154 -0
- company_discovery/bundled_skills/__init__.py +1 -0
- company_discovery/bundled_skills/company-discovery-operator/SKILL.md +72 -0
- company_discovery/bundled_skills/company-discovery-operator/agents/openai.yaml +4 -0
- company_discovery/bundled_skills/company-enrichment-operator/SKILL.md +94 -0
- company_discovery/bundled_skills/company-enrichment-operator/agents/openai.yaml +4 -0
- company_discovery/bundled_skills/company-search-spec-writer/SKILL.md +109 -0
- company_discovery/bundled_skills/company-search-spec-writer/agents/openai.yaml +4 -0
- company_discovery/bundled_skills/contact-discovery-operator/SKILL.md +80 -0
- company_discovery/bundled_skills/contact-discovery-operator/agents/openai.yaml +4 -0
- company_discovery/bundled_skills/contact-enrichment-operator/SKILL.md +86 -0
- company_discovery/bundled_skills/contact-enrichment-operator/agents/openai.yaml +4 -0
- company_discovery/bundled_skills/contact-search-spec-writer/SKILL.md +86 -0
- company_discovery/bundled_skills/contact-search-spec-writer/agents/openai.yaml +4 -0
- company_discovery/bundled_skills/leads-update-operator/SKILL.md +60 -0
- company_discovery/bundled_skills/leads-update-operator/agents/openai.yaml +4 -0
- company_discovery/cli.py +1789 -0
- company_discovery/db/__init__.py +5 -0
- company_discovery/db/contact_enrichment_repository.py +268 -0
- company_discovery/db/contact_repository.py +366 -0
- company_discovery/db/enrichment_repository.py +207 -0
- company_discovery/db/models.py +324 -0
- company_discovery/db/repository.py +363 -0
- company_discovery/db/session.py +48 -0
- company_discovery/domain/__init__.py +24 -0
- company_discovery/domain/contact_models.py +178 -0
- company_discovery/domain/contact_spec.py +86 -0
- company_discovery/domain/models.py +287 -0
- company_discovery/domain/spec.py +263 -0
- company_discovery/migrations.py +190 -0
- company_discovery/prompts/__init__.py +8 -0
- company_discovery/prompts/candidate_evaluation/system.md +13 -0
- company_discovery/prompts/company_enrichment/system.md +42 -0
- company_discovery/prompts/contact_evaluation/system.md +18 -0
- company_discovery/prompts/query_generation/system.md +10 -0
- company_discovery/release_manifest.json +7 -0
- company_discovery/reports/__init__.py +4 -0
- company_discovery/reports/contact_enrichment_exporter.py +108 -0
- company_discovery/reports/contact_exporter.py +132 -0
- company_discovery/reports/enrichment_exporter.py +125 -0
- company_discovery/reports/exporter.py +135 -0
- company_discovery/runtime.py +336 -0
- company_discovery/services/__init__.py +4 -0
- company_discovery/services/contact_enrichment_pipeline.py +344 -0
- company_discovery/services/contact_enrichment_progress.py +37 -0
- company_discovery/services/contact_evaluator.py +110 -0
- company_discovery/services/contact_pipeline.py +295 -0
- company_discovery/services/contact_progress.py +38 -0
- company_discovery/services/enrichment_extractor.py +61 -0
- company_discovery/services/enrichment_pipeline.py +526 -0
- company_discovery/services/enrichment_progress.py +20 -0
- company_discovery/services/enrichment_resolver.py +148 -0
- company_discovery/services/evaluator.py +40 -0
- company_discovery/services/hygiene.py +51 -0
- company_discovery/services/memory.py +150 -0
- company_discovery/services/normalization.py +98 -0
- company_discovery/services/pipeline.py +628 -0
- company_discovery/services/progress.py +48 -0
- company_discovery/services/query_planner.py +47 -0
- company_discovery/settings.py +152 -0
- company_discovery/skill_installer.py +197 -0
- company_discovery/update_plan.py +79 -0
- leads_cli-0.1.0.dist-info/METADATA +277 -0
- leads_cli-0.1.0.dist-info/RECORD +72 -0
- leads_cli-0.1.0.dist-info/WHEEL +4 -0
- leads_cli-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime, timedelta
|
|
4
|
+
from typing import Any
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import func, select
|
|
8
|
+
from sqlalchemy.exc import IntegrityError
|
|
9
|
+
from sqlalchemy.orm import joinedload
|
|
10
|
+
|
|
11
|
+
from company_discovery.db.models import (
|
|
12
|
+
CandidateEvaluationRow,
|
|
13
|
+
CompanyCandidateRow,
|
|
14
|
+
DiscoveryRunRow,
|
|
15
|
+
EnrichmentFactRow,
|
|
16
|
+
EnrichmentItemRow,
|
|
17
|
+
EnrichmentRunRow,
|
|
18
|
+
)
|
|
19
|
+
from company_discovery.db.repository import CandidateNotFoundError, RunNotFoundError
|
|
20
|
+
from company_discovery.db.session import Database
|
|
21
|
+
from company_discovery.domain.models import EnrichmentItem, EnrichmentProfile, EnrichmentSummary
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EnrichmentRunNotFoundError(LookupError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EnrichmentRepository:
|
|
29
|
+
RUN_ID_PREFIX = "company-enrich-"
|
|
30
|
+
CREATE_RUN_ATTEMPTS = 5
|
|
31
|
+
|
|
32
|
+
def __init__(self, database: Database) -> None:
|
|
33
|
+
self.database = database
|
|
34
|
+
|
|
35
|
+
def discovery_candidates(self, run_id: str, bucket: str, limit: int | None) -> list[dict[str, Any]]:
|
|
36
|
+
with self.database.session() as session:
|
|
37
|
+
run = session.get(DiscoveryRunRow, run_id)
|
|
38
|
+
if run is None:
|
|
39
|
+
raise RunNotFoundError(f"run not found: {run_id}")
|
|
40
|
+
if run.status != "completed":
|
|
41
|
+
raise ValueError(f"discovery run {run_id} is {run.status}, not completed")
|
|
42
|
+
statement = (
|
|
43
|
+
select(CandidateEvaluationRow, CompanyCandidateRow)
|
|
44
|
+
.join(CompanyCandidateRow)
|
|
45
|
+
.where(
|
|
46
|
+
CandidateEvaluationRow.run_id == run_id,
|
|
47
|
+
CandidateEvaluationRow.bucket == bucket,
|
|
48
|
+
)
|
|
49
|
+
.order_by(CandidateEvaluationRow.id)
|
|
50
|
+
)
|
|
51
|
+
if limit is not None:
|
|
52
|
+
statement = statement.limit(limit)
|
|
53
|
+
rows = session.execute(statement).all()
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
"candidate_id": candidate.id,
|
|
57
|
+
"company": candidate.normalized_payload,
|
|
58
|
+
"evaluation": evaluation.evaluation_payload,
|
|
59
|
+
"bucket": evaluation.bucket,
|
|
60
|
+
"source": evaluation.source,
|
|
61
|
+
"spec": run.spec_payload,
|
|
62
|
+
}
|
|
63
|
+
for evaluation, candidate in rows
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
def create_run(self, discovery_run_id: str, bucket: str, options: dict[str, Any]) -> str:
|
|
67
|
+
for _ in range(self.CREATE_RUN_ATTEMPTS):
|
|
68
|
+
try:
|
|
69
|
+
with self.database.session() as session:
|
|
70
|
+
if session.get(DiscoveryRunRow, discovery_run_id) is None:
|
|
71
|
+
raise RunNotFoundError(f"run not found: {discovery_run_id}")
|
|
72
|
+
run_id = self._new_run_id()
|
|
73
|
+
session.add(
|
|
74
|
+
EnrichmentRunRow(
|
|
75
|
+
id=run_id,
|
|
76
|
+
discovery_run_id=discovery_run_id,
|
|
77
|
+
bucket=bucket,
|
|
78
|
+
options_payload=options,
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
return run_id
|
|
82
|
+
except IntegrityError:
|
|
83
|
+
continue
|
|
84
|
+
raise RuntimeError("unable to allocate a unique enrichment run id")
|
|
85
|
+
|
|
86
|
+
def fresh_profile(self, candidate_id: int, freshness_days: int) -> EnrichmentProfile:
|
|
87
|
+
cutoff = datetime.now(UTC) - timedelta(days=freshness_days)
|
|
88
|
+
latest = (
|
|
89
|
+
select(
|
|
90
|
+
EnrichmentFactRow.fact_kind,
|
|
91
|
+
func.max(EnrichmentFactRow.id).label("latest_id"),
|
|
92
|
+
)
|
|
93
|
+
.where(
|
|
94
|
+
EnrichmentFactRow.candidate_id == candidate_id,
|
|
95
|
+
EnrichmentFactRow.observed_at >= cutoff,
|
|
96
|
+
)
|
|
97
|
+
.group_by(EnrichmentFactRow.fact_kind)
|
|
98
|
+
.subquery()
|
|
99
|
+
)
|
|
100
|
+
with self.database.session() as session:
|
|
101
|
+
facts = session.scalars(
|
|
102
|
+
select(EnrichmentFactRow).join(latest, EnrichmentFactRow.id == latest.c.latest_id)
|
|
103
|
+
).all()
|
|
104
|
+
payload = {fact.fact_kind: fact.fact_payload for fact in facts}
|
|
105
|
+
return EnrichmentProfile.model_validate(payload)
|
|
106
|
+
|
|
107
|
+
def save_item(self, run_id: str, item: EnrichmentItem) -> None:
|
|
108
|
+
with self.database.session() as session:
|
|
109
|
+
if session.get(CompanyCandidateRow, item.company_id) is None:
|
|
110
|
+
raise CandidateNotFoundError(f"candidate not found: {item.company_id}")
|
|
111
|
+
session.add(
|
|
112
|
+
EnrichmentItemRow(
|
|
113
|
+
run_id=run_id,
|
|
114
|
+
candidate_id=item.company_id,
|
|
115
|
+
discovery_snapshot=item.discovery,
|
|
116
|
+
enrichment_payload=item.enrichment.model_dump(mode="json"),
|
|
117
|
+
inherited_status={key: value.value for key, value in item.inherited_status.items()},
|
|
118
|
+
outcome=item.outcome.value,
|
|
119
|
+
conflicts=item.conflicts,
|
|
120
|
+
review_flags=item.review_flags,
|
|
121
|
+
trace_payload=item.trace,
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
for kind in ("phone", "location", "independence", "linkedin"):
|
|
125
|
+
fact = getattr(item.enrichment, kind)
|
|
126
|
+
if fact is not None:
|
|
127
|
+
session.add(
|
|
128
|
+
EnrichmentFactRow(
|
|
129
|
+
candidate_id=item.company_id,
|
|
130
|
+
enrichment_run_id=run_id,
|
|
131
|
+
fact_kind=kind,
|
|
132
|
+
fact_payload=fact.model_dump(mode="json"),
|
|
133
|
+
observed_at=fact.observed_at,
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def complete_run(self, run_id: str, summary: EnrichmentSummary, paths: dict[str, str]) -> None:
|
|
138
|
+
with self.database.session() as session:
|
|
139
|
+
row = self._require_run(session, run_id)
|
|
140
|
+
row.status = "completed"
|
|
141
|
+
row.summary_payload = summary.model_dump(mode="json")
|
|
142
|
+
row.artifact_paths = paths
|
|
143
|
+
row.completed_at = datetime.now(UTC)
|
|
144
|
+
|
|
145
|
+
def fail_run(self, run_id: str, error: Exception) -> None:
|
|
146
|
+
with self.database.session() as session:
|
|
147
|
+
row = self._require_run(session, run_id)
|
|
148
|
+
row.status = "failed"
|
|
149
|
+
row.error_message = str(error)
|
|
150
|
+
row.completed_at = datetime.now(UTC)
|
|
151
|
+
|
|
152
|
+
def set_artifacts(self, run_id: str, paths: dict[str, str]) -> None:
|
|
153
|
+
with self.database.session() as session:
|
|
154
|
+
self._require_run(session, run_id).artifact_paths = paths
|
|
155
|
+
|
|
156
|
+
def get_run(self, run_id: str) -> dict[str, Any]:
|
|
157
|
+
with self.database.session() as session:
|
|
158
|
+
row = session.scalar(
|
|
159
|
+
select(EnrichmentRunRow)
|
|
160
|
+
.options(joinedload(EnrichmentRunRow.items))
|
|
161
|
+
.where(EnrichmentRunRow.id == run_id)
|
|
162
|
+
)
|
|
163
|
+
if row is None:
|
|
164
|
+
raise EnrichmentRunNotFoundError(f"enrichment run not found: {run_id}")
|
|
165
|
+
return {
|
|
166
|
+
"run_id": row.id,
|
|
167
|
+
"discovery_run_id": row.discovery_run_id,
|
|
168
|
+
"bucket": row.bucket,
|
|
169
|
+
"options": row.options_payload,
|
|
170
|
+
"status": row.status,
|
|
171
|
+
"summary": row.summary_payload,
|
|
172
|
+
"artifacts": row.artifact_paths,
|
|
173
|
+
"error": row.error_message,
|
|
174
|
+
"created_at": row.created_at.isoformat(),
|
|
175
|
+
"completed_at": row.completed_at.isoformat() if row.completed_at else None,
|
|
176
|
+
"items": [
|
|
177
|
+
{
|
|
178
|
+
"company_id": item.candidate_id,
|
|
179
|
+
"discovery": item.discovery_snapshot,
|
|
180
|
+
"enrichment": item.enrichment_payload,
|
|
181
|
+
"inherited_status": item.inherited_status,
|
|
182
|
+
"outcome": item.outcome,
|
|
183
|
+
"conflicts": item.conflicts,
|
|
184
|
+
"review_flags": item.review_flags,
|
|
185
|
+
"trace": item.trace_payload,
|
|
186
|
+
}
|
|
187
|
+
for item in row.items
|
|
188
|
+
],
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
def inspect_item(self, run_id: str, domain: str) -> dict[str, Any]:
|
|
192
|
+
payload = self.get_run(run_id)
|
|
193
|
+
for item in payload["items"]:
|
|
194
|
+
if item["discovery"]["domain"] == domain:
|
|
195
|
+
return item
|
|
196
|
+
raise CandidateNotFoundError(f"domain {domain} was not enriched in run {run_id}")
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def _require_run(session: Any, run_id: str) -> EnrichmentRunRow:
|
|
200
|
+
row = session.get(EnrichmentRunRow, run_id)
|
|
201
|
+
if row is None:
|
|
202
|
+
raise EnrichmentRunNotFoundError(f"enrichment run not found: {run_id}")
|
|
203
|
+
return row
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def _new_run_id(cls) -> str:
|
|
207
|
+
return f"{cls.RUN_ID_PREFIX}{uuid4().hex[:12]}"
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Index, Integer, JSON, String, Text, UniqueConstraint
|
|
7
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def utc_now() -> datetime:
|
|
11
|
+
return datetime.now(UTC)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Base(DeclarativeBase):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DiscoveryRunRow(Base):
|
|
19
|
+
__tablename__ = "company_discovery_runs"
|
|
20
|
+
|
|
21
|
+
id: Mapped[str] = mapped_column(String(32), primary_key=True)
|
|
22
|
+
spec_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
|
|
23
|
+
source_spec_path: Mapped[str | None] = mapped_column(Text)
|
|
24
|
+
status: Mapped[str] = mapped_column(String(32), nullable=False, default="running")
|
|
25
|
+
summary_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
|
26
|
+
artifact_paths: Mapped[dict[str, str]] = mapped_column(JSON, nullable=False, default=dict)
|
|
27
|
+
error_message: Mapped[str | None] = mapped_column(Text)
|
|
28
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
29
|
+
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
30
|
+
|
|
31
|
+
queries: Mapped[list[DiscoveryQueryRow]] = relationship(
|
|
32
|
+
back_populates="run", cascade="all, delete-orphan", order_by="DiscoveryQueryRow.query_order"
|
|
33
|
+
)
|
|
34
|
+
evaluations: Mapped[list[CandidateEvaluationRow]] = relationship(
|
|
35
|
+
back_populates="run", cascade="all, delete-orphan"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DiscoveryQueryRow(Base):
|
|
40
|
+
__tablename__ = "company_discovery_queries"
|
|
41
|
+
__table_args__ = (UniqueConstraint("run_id", "query_order"),)
|
|
42
|
+
|
|
43
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
44
|
+
run_id: Mapped[str] = mapped_column(ForeignKey("company_discovery_runs.id", ondelete="CASCADE"), index=True)
|
|
45
|
+
query_text: Mapped[str] = mapped_column(Text, nullable=False)
|
|
46
|
+
query_order: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
47
|
+
rationale: Mapped[str] = mapped_column(Text, default="", nullable=False)
|
|
48
|
+
result_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
49
|
+
cost_dollars: Mapped[float] = mapped_column(Float, default=0, nullable=False)
|
|
50
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
51
|
+
|
|
52
|
+
run: Mapped[DiscoveryRunRow] = relationship(back_populates="queries")
|
|
53
|
+
raw_results: Mapped[list[RawResultRow]] = relationship(
|
|
54
|
+
back_populates="query", cascade="all, delete-orphan"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RawResultRow(Base):
|
|
59
|
+
__tablename__ = "company_discovery_raw_results"
|
|
60
|
+
|
|
61
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
62
|
+
run_id: Mapped[str] = mapped_column(ForeignKey("company_discovery_runs.id", ondelete="CASCADE"), index=True)
|
|
63
|
+
query_id: Mapped[int] = mapped_column(ForeignKey("company_discovery_queries.id", ondelete="CASCADE"), index=True)
|
|
64
|
+
result_position: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
65
|
+
observed_url: Mapped[str] = mapped_column(Text, nullable=False)
|
|
66
|
+
observed_title: Mapped[str] = mapped_column(Text, nullable=False)
|
|
67
|
+
raw_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
|
|
68
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
69
|
+
|
|
70
|
+
query: Mapped[DiscoveryQueryRow] = relationship(back_populates="raw_results")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CompanyCandidateRow(Base):
|
|
74
|
+
__tablename__ = "company_candidates"
|
|
75
|
+
__table_args__ = (
|
|
76
|
+
Index("ix_company_candidates_market", "vertical", "country", "state"),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
80
|
+
canonical_name: Mapped[str] = mapped_column(Text, nullable=False)
|
|
81
|
+
domain: Mapped[str] = mapped_column(String(255), nullable=False, unique=True, index=True)
|
|
82
|
+
dedupe_key: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
|
|
83
|
+
normalized_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
|
|
84
|
+
vertical: Mapped[str | None] = mapped_column(String(128), index=True)
|
|
85
|
+
country: Mapped[str | None] = mapped_column(String(2), index=True)
|
|
86
|
+
state: Mapped[str | None] = mapped_column(String(8), index=True)
|
|
87
|
+
employee_min: Mapped[int | None] = mapped_column(Integer)
|
|
88
|
+
employee_max: Mapped[int | None] = mapped_column(Integer)
|
|
89
|
+
ownership_type: Mapped[str | None] = mapped_column(String(128), index=True)
|
|
90
|
+
prior_bucket: Mapped[str | None] = mapped_column(String(32), index=True)
|
|
91
|
+
prior_reason: Mapped[str | None] = mapped_column(Text)
|
|
92
|
+
excluded: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
93
|
+
first_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
94
|
+
last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
95
|
+
last_evaluated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
96
|
+
|
|
97
|
+
evaluations: Mapped[list[CandidateEvaluationRow]] = relationship(back_populates="candidate")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class CandidateEvaluationRow(Base):
|
|
101
|
+
__tablename__ = "company_candidate_evaluations"
|
|
102
|
+
__table_args__ = (UniqueConstraint("run_id", "candidate_id"),)
|
|
103
|
+
|
|
104
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
105
|
+
run_id: Mapped[str] = mapped_column(ForeignKey("company_discovery_runs.id", ondelete="CASCADE"), index=True)
|
|
106
|
+
candidate_id: Mapped[int] = mapped_column(ForeignKey("company_candidates.id"), index=True)
|
|
107
|
+
evaluation_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
|
|
108
|
+
fit_outcome: Mapped[str] = mapped_column(String(32), nullable=False, index=True)
|
|
109
|
+
bucket: Mapped[str] = mapped_column(String(32), nullable=False, index=True)
|
|
110
|
+
reason: Mapped[str] = mapped_column(Text, nullable=False)
|
|
111
|
+
reason_codes: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
|
112
|
+
source: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
113
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
114
|
+
|
|
115
|
+
run: Mapped[DiscoveryRunRow] = relationship(back_populates="evaluations")
|
|
116
|
+
candidate: Mapped[CompanyCandidateRow] = relationship(back_populates="evaluations")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class EnrichmentRunRow(Base):
|
|
120
|
+
__tablename__ = "company_enrichment_runs"
|
|
121
|
+
|
|
122
|
+
id: Mapped[str] = mapped_column(String(32), primary_key=True)
|
|
123
|
+
discovery_run_id: Mapped[str] = mapped_column(
|
|
124
|
+
ForeignKey("company_discovery_runs.id"), nullable=False, index=True
|
|
125
|
+
)
|
|
126
|
+
bucket: Mapped[str] = mapped_column(String(32), nullable=False, default="selected")
|
|
127
|
+
options_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
|
128
|
+
status: Mapped[str] = mapped_column(String(32), nullable=False, default="running")
|
|
129
|
+
summary_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
|
130
|
+
artifact_paths: Mapped[dict[str, str]] = mapped_column(JSON, nullable=False, default=dict)
|
|
131
|
+
error_message: Mapped[str | None] = mapped_column(Text)
|
|
132
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
133
|
+
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
134
|
+
|
|
135
|
+
items: Mapped[list[EnrichmentItemRow]] = relationship(
|
|
136
|
+
back_populates="run", cascade="all, delete-orphan", order_by="EnrichmentItemRow.id"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class EnrichmentItemRow(Base):
|
|
141
|
+
__tablename__ = "company_enrichment_items"
|
|
142
|
+
__table_args__ = (UniqueConstraint("run_id", "candidate_id"),)
|
|
143
|
+
|
|
144
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
145
|
+
run_id: Mapped[str] = mapped_column(
|
|
146
|
+
ForeignKey("company_enrichment_runs.id", ondelete="CASCADE"), index=True
|
|
147
|
+
)
|
|
148
|
+
candidate_id: Mapped[int] = mapped_column(ForeignKey("company_candidates.id"), index=True)
|
|
149
|
+
discovery_snapshot: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
|
|
150
|
+
enrichment_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
|
|
151
|
+
inherited_status: Mapped[dict[str, str]] = mapped_column(JSON, nullable=False)
|
|
152
|
+
outcome: Mapped[str] = mapped_column(String(48), nullable=False, index=True)
|
|
153
|
+
conflicts: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
|
154
|
+
review_flags: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
|
155
|
+
trace_payload: Mapped[list[dict[str, Any]]] = mapped_column(JSON, nullable=False, default=list)
|
|
156
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
157
|
+
|
|
158
|
+
run: Mapped[EnrichmentRunRow] = relationship(back_populates="items")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class EnrichmentFactRow(Base):
|
|
162
|
+
__tablename__ = "company_enrichment_facts"
|
|
163
|
+
__table_args__ = (Index("ix_enrichment_fact_latest", "candidate_id", "fact_kind", "observed_at"),)
|
|
164
|
+
|
|
165
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
166
|
+
candidate_id: Mapped[int] = mapped_column(ForeignKey("company_candidates.id"), index=True)
|
|
167
|
+
enrichment_run_id: Mapped[str] = mapped_column(ForeignKey("company_enrichment_runs.id"), index=True)
|
|
168
|
+
fact_kind: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
169
|
+
fact_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
|
|
170
|
+
observed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class ContactDiscoveryRunRow(Base):
|
|
174
|
+
__tablename__ = "contact_discovery_runs"
|
|
175
|
+
|
|
176
|
+
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
|
177
|
+
enrichment_run_id: Mapped[str] = mapped_column(
|
|
178
|
+
ForeignKey("company_enrichment_runs.id"), nullable=False, index=True
|
|
179
|
+
)
|
|
180
|
+
spec_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
|
|
181
|
+
source_spec_path: Mapped[str | None] = mapped_column(Text)
|
|
182
|
+
status: Mapped[str] = mapped_column(String(32), nullable=False, default="running")
|
|
183
|
+
summary_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
|
184
|
+
artifact_paths: Mapped[dict[str, str]] = mapped_column(JSON, nullable=False, default=dict)
|
|
185
|
+
error_message: Mapped[str | None] = mapped_column(Text)
|
|
186
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
187
|
+
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
188
|
+
|
|
189
|
+
queries: Mapped[list[ContactDiscoveryQueryRow]] = relationship(
|
|
190
|
+
back_populates="run",
|
|
191
|
+
cascade="all, delete-orphan",
|
|
192
|
+
order_by="ContactDiscoveryQueryRow.id",
|
|
193
|
+
)
|
|
194
|
+
evaluations: Mapped[list[ContactEvaluationRow]] = relationship(
|
|
195
|
+
back_populates="run", cascade="all, delete-orphan"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class ContactDiscoveryQueryRow(Base):
|
|
200
|
+
__tablename__ = "contact_discovery_queries"
|
|
201
|
+
|
|
202
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
203
|
+
run_id: Mapped[str] = mapped_column(
|
|
204
|
+
ForeignKey("contact_discovery_runs.id", ondelete="CASCADE"), index=True
|
|
205
|
+
)
|
|
206
|
+
company_domain: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
207
|
+
role_key: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
|
|
208
|
+
query_text: Mapped[str] = mapped_column(Text, nullable=False)
|
|
209
|
+
result_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
210
|
+
cost_dollars: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
|
211
|
+
raw_results: Mapped[list[dict[str, Any]]] = mapped_column(JSON, nullable=False, default=list)
|
|
212
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
213
|
+
|
|
214
|
+
run: Mapped[ContactDiscoveryRunRow] = relationship(back_populates="queries")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class ContactCandidateRow(Base):
|
|
218
|
+
__tablename__ = "contact_candidates"
|
|
219
|
+
__table_args__ = (
|
|
220
|
+
UniqueConstraint("company_domain", "identity_key"),
|
|
221
|
+
Index("ix_contact_candidates_company", "company_domain", "normalized_name"),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
225
|
+
company_candidate_id: Mapped[int] = mapped_column(
|
|
226
|
+
ForeignKey("company_candidates.id"), nullable=False, index=True
|
|
227
|
+
)
|
|
228
|
+
company_name: Mapped[str] = mapped_column(Text, nullable=False)
|
|
229
|
+
company_domain: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
230
|
+
full_name: Mapped[str] = mapped_column(Text, nullable=False)
|
|
231
|
+
normalized_name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
232
|
+
identity_key: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
233
|
+
title: Mapped[str] = mapped_column(Text, nullable=False)
|
|
234
|
+
linkedin_url: Mapped[str | None] = mapped_column(Text)
|
|
235
|
+
source_urls: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
|
236
|
+
evidence: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
|
237
|
+
first_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
238
|
+
last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
239
|
+
|
|
240
|
+
evaluations: Mapped[list[ContactEvaluationRow]] = relationship(back_populates="candidate")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class ContactEvaluationRow(Base):
|
|
244
|
+
__tablename__ = "contact_evaluations"
|
|
245
|
+
__table_args__ = (UniqueConstraint("run_id", "candidate_id", "role_key"),)
|
|
246
|
+
|
|
247
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
248
|
+
run_id: Mapped[str] = mapped_column(
|
|
249
|
+
ForeignKey("contact_discovery_runs.id", ondelete="CASCADE"), index=True
|
|
250
|
+
)
|
|
251
|
+
candidate_id: Mapped[int] = mapped_column(
|
|
252
|
+
ForeignKey("contact_candidates.id"), nullable=False, index=True
|
|
253
|
+
)
|
|
254
|
+
role_key: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
|
|
255
|
+
verdict: Mapped[str] = mapped_column(String(16), nullable=False, index=True)
|
|
256
|
+
reason: Mapped[str] = mapped_column(Text, nullable=False)
|
|
257
|
+
current_company_match: Mapped[str] = mapped_column(String(16), nullable=False)
|
|
258
|
+
role_match: Mapped[str] = mapped_column(String(16), nullable=False)
|
|
259
|
+
identity_clear: Mapped[bool] = mapped_column(Boolean, nullable=False)
|
|
260
|
+
source: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
261
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
262
|
+
|
|
263
|
+
run: Mapped[ContactDiscoveryRunRow] = relationship(back_populates="evaluations")
|
|
264
|
+
candidate: Mapped[ContactCandidateRow] = relationship(back_populates="evaluations")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class ContactEnrichmentRunRow(Base):
|
|
268
|
+
__tablename__ = "contact_enrichment_runs"
|
|
269
|
+
|
|
270
|
+
id: Mapped[str] = mapped_column(String(48), primary_key=True)
|
|
271
|
+
contact_discovery_run_id: Mapped[str] = mapped_column(
|
|
272
|
+
ForeignKey("contact_discovery_runs.id"), nullable=False, index=True
|
|
273
|
+
)
|
|
274
|
+
options_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
|
275
|
+
status: Mapped[str] = mapped_column(String(32), nullable=False, default="running")
|
|
276
|
+
summary_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
|
277
|
+
artifact_paths: Mapped[dict[str, str]] = mapped_column(JSON, nullable=False, default=dict)
|
|
278
|
+
error_message: Mapped[str | None] = mapped_column(Text)
|
|
279
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
280
|
+
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
281
|
+
|
|
282
|
+
items: Mapped[list[ContactEnrichmentItemRow]] = relationship(
|
|
283
|
+
back_populates="run", cascade="all, delete-orphan", order_by="ContactEnrichmentItemRow.id"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class ContactEnrichmentItemRow(Base):
|
|
288
|
+
__tablename__ = "contact_enrichment_items"
|
|
289
|
+
__table_args__ = (UniqueConstraint("run_id", "candidate_id"),)
|
|
290
|
+
|
|
291
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
292
|
+
run_id: Mapped[str] = mapped_column(
|
|
293
|
+
ForeignKey("contact_enrichment_runs.id", ondelete="CASCADE"), index=True
|
|
294
|
+
)
|
|
295
|
+
candidate_id: Mapped[int] = mapped_column(
|
|
296
|
+
ForeignKey("contact_candidates.id"), nullable=False, index=True
|
|
297
|
+
)
|
|
298
|
+
discovery_snapshot: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
|
|
299
|
+
channels_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
|
|
300
|
+
outcome: Mapped[str] = mapped_column(String(16), nullable=False, index=True)
|
|
301
|
+
review_flags: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
|
302
|
+
trace_payload: Mapped[list[dict[str, Any]]] = mapped_column(JSON, nullable=False, default=list)
|
|
303
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|
|
304
|
+
|
|
305
|
+
run: Mapped[ContactEnrichmentRunRow] = relationship(back_populates="items")
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class ContactEnrichmentFactRow(Base):
|
|
309
|
+
__tablename__ = "contact_enrichment_facts"
|
|
310
|
+
__table_args__ = (
|
|
311
|
+
Index("ix_contact_enrichment_fact_latest", "candidate_id", "observed_at"),
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
315
|
+
candidate_id: Mapped[int] = mapped_column(
|
|
316
|
+
ForeignKey("contact_candidates.id"), nullable=False, index=True
|
|
317
|
+
)
|
|
318
|
+
enrichment_run_id: Mapped[str] = mapped_column(
|
|
319
|
+
ForeignKey("contact_enrichment_runs.id"), nullable=False, index=True
|
|
320
|
+
)
|
|
321
|
+
channels_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
|
|
322
|
+
outcome: Mapped[str] = mapped_column(String(16), nullable=False)
|
|
323
|
+
review_flags: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
|
324
|
+
observed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
|