autotouch-cli 0.2.22__tar.gz → 0.2.23__tar.gz
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.
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/PKG-INFO +10 -1
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/autotouch_cli.egg-info/PKG-INFO +10 -1
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/docs/research-table/reference/autotouch-cli-pypi.md +9 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/pyproject.toml +1 -1
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/smart_table_cli.py +250 -1
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/autotouch_cli.egg-info/SOURCES.txt +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/autotouch_cli.egg-info/dependency_links.txt +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/autotouch_cli.egg-info/entry_points.txt +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/autotouch_cli.egg-info/requires.txt +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/autotouch_cli.egg-info/top_level.txt +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/__init__.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/add_column_unique_index.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/attach_csv_import_leads_to_research_table.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/bundle_sequences_backend.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/check_agent_traces.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/check_column_mode.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/exit_terminal_leads_from_sequences.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/fetch_lead.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/fix_lead_titles_from_csv.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250106_add_column_position.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250108_fix_legacy_column_fields.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250109_add_user_fields_to_tables.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250117_add_call_logs_webhook_indexes.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250117_rename_call_logs_collection.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250119_create_leads_unique_email_index.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250123_add_filter_indexes.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250123_add_llm_responses_collection.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250128_migrate_user_ids_to_objectid.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250208_backfill_task_research_values.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250604_add_origin_indexes.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250608_cleanup_agent_metadata.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250608_rename_agent_metadata_to_metadata.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250922_add_activity_indexes.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250926_migrate_single_to_arrays.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250928_add_missing_timestamp_fields.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250929_add_task_join_indexes.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250929_add_task_join_indexes_safe.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250929_create_shared_phone_cache.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20251007_add_rows_position_id_index.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20251109_add_ttl_for_llm_and_preview_traces.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20260113_normalize_table_filter_operators.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20260113_set_user_permissions_user_admin.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20260204_sync_lead_owner_from_tasks.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20260303_add_webhook_subscription_collections.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20260305_force_formatter_autorun_on_source_update.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/migrate_org_user_credits.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/set_default_lead_status.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/update_lead_owner_from_tasks.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/reassign_sequence_owner.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/run_sidecar_orchestrator_demo.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/test_crm_company_policy.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/test_sequences_instantly_e2e.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/test_sequences_personal_e2e.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/test_task_error_logger.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/verify_azurite_voicemail.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/setup.cfg +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/tests/test_contactout_custom.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/tests/test_contactout_integration.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/tests/test_contactout_multi_titles.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/tests/test_contactout_pipeline.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/tests/test_contactout_simple.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/tests/test_contactout_v2_bulk.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/tests/test_lead_required_fields.py +0 -0
- {autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/tests/test_phone_provider_pipeline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: autotouch-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.23
|
|
4
4
|
Summary: Autotouch Smart Table CLI
|
|
5
5
|
Requires-Python: >=3.9
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -207,6 +207,12 @@ Notes:
|
|
|
207
207
|
- `add_to_crm` is optional and non-billable.
|
|
208
208
|
- Email/phone enrichment does not require creating/running `add_to_crm`.
|
|
209
209
|
- For `add_to_crm`, required mapping keys are `linkedinUrl` and `companyDomain`.
|
|
210
|
+
- CRM data model expectations for `add_to_crm`:
|
|
211
|
+
- Lead identity/dedupe expects `linkedin_url` + `company_domain` (clean domain format).
|
|
212
|
+
- `company_domain` is required; `company_name` is a display hint applied to the linked Company record.
|
|
213
|
+
- Leads link to companies via `company_id`; canonical company name lives on the Company document.
|
|
214
|
+
- Canonical lead contact fields are arrays (`email_addresses[]`, `phone_numbers[]`); legacy scalar fields may exist but are fallback only.
|
|
215
|
+
- Reference: `docs/data/leads.md`, `docs/data/companies.md`.
|
|
210
216
|
- Formatter formulas must use row references (`row['first_name']`, `row.last_name`), not bare template placeholders like ``${first_name}``.
|
|
211
217
|
- `sync_to_table` supports both:
|
|
212
218
|
- single destination: `destinationTableId` + `columnMappings`
|
|
@@ -243,6 +249,9 @@ If path A is missing, continue to B/C before declaring `not_found`.
|
|
|
243
249
|
JSON split note:
|
|
244
250
|
- Optional by default.
|
|
245
251
|
- Use only when downstream filters/mappings need stable flat keys.
|
|
252
|
+
- If source columns are JSON enrichments, run with `--wait` and confirm terminal job status before splitting.
|
|
253
|
+
- CLI behavior: `columns projections` emits preflight warnings when a JSON enrichment source appears unrun/unverified.
|
|
254
|
+
- Warning output contract: when warnings exist, JSON output is wrapped as `{ "event": "projections.created_with_warnings", "warnings": [...], "result": <api_response> }`.
|
|
246
255
|
|
|
247
256
|
Reference:
|
|
248
257
|
- `docs/research-table/guides/context-first-sequence-playbook.md`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: autotouch-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.23
|
|
4
4
|
Summary: Autotouch Smart Table CLI
|
|
5
5
|
Requires-Python: >=3.9
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -207,6 +207,12 @@ Notes:
|
|
|
207
207
|
- `add_to_crm` is optional and non-billable.
|
|
208
208
|
- Email/phone enrichment does not require creating/running `add_to_crm`.
|
|
209
209
|
- For `add_to_crm`, required mapping keys are `linkedinUrl` and `companyDomain`.
|
|
210
|
+
- CRM data model expectations for `add_to_crm`:
|
|
211
|
+
- Lead identity/dedupe expects `linkedin_url` + `company_domain` (clean domain format).
|
|
212
|
+
- `company_domain` is required; `company_name` is a display hint applied to the linked Company record.
|
|
213
|
+
- Leads link to companies via `company_id`; canonical company name lives on the Company document.
|
|
214
|
+
- Canonical lead contact fields are arrays (`email_addresses[]`, `phone_numbers[]`); legacy scalar fields may exist but are fallback only.
|
|
215
|
+
- Reference: `docs/data/leads.md`, `docs/data/companies.md`.
|
|
210
216
|
- Formatter formulas must use row references (`row['first_name']`, `row.last_name`), not bare template placeholders like ``${first_name}``.
|
|
211
217
|
- `sync_to_table` supports both:
|
|
212
218
|
- single destination: `destinationTableId` + `columnMappings`
|
|
@@ -243,6 +249,9 @@ If path A is missing, continue to B/C before declaring `not_found`.
|
|
|
243
249
|
JSON split note:
|
|
244
250
|
- Optional by default.
|
|
245
251
|
- Use only when downstream filters/mappings need stable flat keys.
|
|
252
|
+
- If source columns are JSON enrichments, run with `--wait` and confirm terminal job status before splitting.
|
|
253
|
+
- CLI behavior: `columns projections` emits preflight warnings when a JSON enrichment source appears unrun/unverified.
|
|
254
|
+
- Warning output contract: when warnings exist, JSON output is wrapped as `{ "event": "projections.created_with_warnings", "warnings": [...], "result": <api_response> }`.
|
|
246
255
|
|
|
247
256
|
Reference:
|
|
248
257
|
- `docs/research-table/guides/context-first-sequence-playbook.md`
|
{autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/docs/research-table/reference/autotouch-cli-pypi.md
RENAMED
|
@@ -198,6 +198,12 @@ Notes:
|
|
|
198
198
|
- `add_to_crm` is optional and non-billable.
|
|
199
199
|
- Email/phone enrichment does not require creating/running `add_to_crm`.
|
|
200
200
|
- For `add_to_crm`, required mapping keys are `linkedinUrl` and `companyDomain`.
|
|
201
|
+
- CRM data model expectations for `add_to_crm`:
|
|
202
|
+
- Lead identity/dedupe expects `linkedin_url` + `company_domain` (clean domain format).
|
|
203
|
+
- `company_domain` is required; `company_name` is a display hint applied to the linked Company record.
|
|
204
|
+
- Leads link to companies via `company_id`; canonical company name lives on the Company document.
|
|
205
|
+
- Canonical lead contact fields are arrays (`email_addresses[]`, `phone_numbers[]`); legacy scalar fields may exist but are fallback only.
|
|
206
|
+
- Reference: `docs/data/leads.md`, `docs/data/companies.md`.
|
|
201
207
|
- Formatter formulas must use row references (`row['first_name']`, `row.last_name`), not bare template placeholders like ``${first_name}``.
|
|
202
208
|
- `sync_to_table` supports both:
|
|
203
209
|
- single destination: `destinationTableId` + `columnMappings`
|
|
@@ -234,6 +240,9 @@ If path A is missing, continue to B/C before declaring `not_found`.
|
|
|
234
240
|
JSON split note:
|
|
235
241
|
- Optional by default.
|
|
236
242
|
- Use only when downstream filters/mappings need stable flat keys.
|
|
243
|
+
- If source columns are JSON enrichments, run with `--wait` and confirm terminal job status before splitting.
|
|
244
|
+
- CLI behavior: `columns projections` emits preflight warnings when a JSON enrichment source appears unrun/unverified.
|
|
245
|
+
- Warning output contract: when warnings exist, JSON output is wrapped as `{ "event": "projections.created_with_warnings", "warnings": [...], "result": <api_response> }`.
|
|
237
246
|
|
|
238
247
|
Reference:
|
|
239
248
|
- `docs/research-table/guides/context-first-sequence-playbook.md`
|
|
@@ -57,6 +57,14 @@ TERMINAL_JOB_STATUSES = {
|
|
|
57
57
|
"failed",
|
|
58
58
|
"completed_with_errors",
|
|
59
59
|
}
|
|
60
|
+
NON_TERMINAL_JOB_STATUSES = {
|
|
61
|
+
"queued",
|
|
62
|
+
"distributing",
|
|
63
|
+
"processing",
|
|
64
|
+
"running",
|
|
65
|
+
"pending",
|
|
66
|
+
}
|
|
67
|
+
PROJECTION_READINESS_SAMPLE_SIZE = 50
|
|
60
68
|
CONFIG_ENV_KEY = "AUTOTOUCH_CONFIG_PATH"
|
|
61
69
|
CONFIG_DIR_NAME = "autotouch"
|
|
62
70
|
CONFIG_FILE_NAME = "config.json"
|
|
@@ -256,6 +264,7 @@ RUN_SOP_GUIDE: Dict[str, Any] = {
|
|
|
256
264
|
"For add_to_crm, generate payload from columns recipe before create/update.",
|
|
257
265
|
"Keep unprocessedOnly enabled unless you intentionally reprocess rows.",
|
|
258
266
|
"Treat bulk job state as source of truth: queued/distributing/processing are non-terminal; completed/partial/error/cancelled are terminal.",
|
|
267
|
+
"For JSON enrichment columns, run with --wait and verify terminal status before creating projection splits.",
|
|
259
268
|
"Always inspect raw outputs before summary counts; parse by column dataType (json vs scalar).",
|
|
260
269
|
"When summarizing enrichment outputs, parse by key precedence (phone: mobile_number -> phone_numbers[0].number -> primary_phone; email: response -> email -> work_email).",
|
|
261
270
|
],
|
|
@@ -1028,6 +1037,224 @@ def _resolve_column_key(
|
|
|
1028
1037
|
sys.exit(1)
|
|
1029
1038
|
|
|
1030
1039
|
|
|
1040
|
+
def _extract_columns_list(columns_raw: Any) -> List[Dict[str, Any]]:
|
|
1041
|
+
if isinstance(columns_raw, list):
|
|
1042
|
+
return [col for col in columns_raw if isinstance(col, dict)]
|
|
1043
|
+
if isinstance(columns_raw, dict):
|
|
1044
|
+
candidates = columns_raw.get("columns") or columns_raw.get("items") or columns_raw.get("data") or []
|
|
1045
|
+
if isinstance(candidates, list):
|
|
1046
|
+
return [col for col in candidates if isinstance(col, dict)]
|
|
1047
|
+
return []
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
def _extract_jobs_list(jobs_raw: Any) -> List[Dict[str, Any]]:
|
|
1051
|
+
if isinstance(jobs_raw, list):
|
|
1052
|
+
return [job for job in jobs_raw if isinstance(job, dict)]
|
|
1053
|
+
if isinstance(jobs_raw, dict):
|
|
1054
|
+
candidates = jobs_raw.get("jobs") or jobs_raw.get("items") or jobs_raw.get("data") or []
|
|
1055
|
+
if isinstance(candidates, list):
|
|
1056
|
+
return [job for job in candidates if isinstance(job, dict)]
|
|
1057
|
+
return []
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
def _is_json_enrichment_column(column: Dict[str, Any]) -> bool:
|
|
1061
|
+
kind = str(column.get("kind") or "").strip().lower()
|
|
1062
|
+
data_type = str(column.get("dataType") or column.get("data_type") or "").strip().lower()
|
|
1063
|
+
return kind == "enrichment" and data_type == "json"
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
def _projection_recommended_run_command(table_id: str, column_id: str) -> str:
|
|
1067
|
+
return (
|
|
1068
|
+
f"autotouch columns run --table-id {table_id} --column-id {column_id} "
|
|
1069
|
+
"--scope all --show-estimate --wait --output json"
|
|
1070
|
+
)
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
def _projection_preflight_warn(
|
|
1074
|
+
*,
|
|
1075
|
+
code: str,
|
|
1076
|
+
column: Dict[str, Any],
|
|
1077
|
+
reason: str,
|
|
1078
|
+
recommended_next_command: str,
|
|
1079
|
+
latest_job_status: Optional[str] = None,
|
|
1080
|
+
column_status: Optional[str] = None,
|
|
1081
|
+
sample_rows_scanned: Optional[int] = None,
|
|
1082
|
+
sample_non_empty_values: Optional[int] = None,
|
|
1083
|
+
) -> Dict[str, Any]:
|
|
1084
|
+
warning: Dict[str, Any] = {
|
|
1085
|
+
"code": code,
|
|
1086
|
+
"severity": "warning",
|
|
1087
|
+
"source_column_id": str(column.get("id") or column.get("_id") or ""),
|
|
1088
|
+
"source_column_key": str(column.get("key") or ""),
|
|
1089
|
+
"source_column_label": str(column.get("label") or column.get("key") or ""),
|
|
1090
|
+
"reason": reason,
|
|
1091
|
+
"recommended_next_command": recommended_next_command,
|
|
1092
|
+
}
|
|
1093
|
+
if latest_job_status:
|
|
1094
|
+
warning["latest_job_status"] = latest_job_status
|
|
1095
|
+
if column_status:
|
|
1096
|
+
warning["column_status"] = column_status
|
|
1097
|
+
if sample_rows_scanned is not None:
|
|
1098
|
+
warning["sample_rows_scanned"] = int(sample_rows_scanned)
|
|
1099
|
+
if sample_non_empty_values is not None:
|
|
1100
|
+
warning["sample_non_empty_values"] = int(sample_non_empty_values)
|
|
1101
|
+
return warning
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
def _print_projection_preflight_warnings(warnings: List[Dict[str, Any]]) -> None:
|
|
1105
|
+
if not warnings:
|
|
1106
|
+
return
|
|
1107
|
+
print(
|
|
1108
|
+
f"WARNING: projection preflight found {len(warnings)} source column(s) that may not be ready yet.",
|
|
1109
|
+
file=sys.stderr,
|
|
1110
|
+
)
|
|
1111
|
+
for warning in warnings:
|
|
1112
|
+
col_label = warning.get("source_column_label") or warning.get("source_column_key") or warning.get("source_column_id")
|
|
1113
|
+
col_id = warning.get("source_column_id") or "unknown"
|
|
1114
|
+
reason = warning.get("reason") or "source may be unprocessed"
|
|
1115
|
+
print(f"WARNING: [{warning.get('code')}] {col_label} ({col_id}): {reason}", file=sys.stderr)
|
|
1116
|
+
cmd = warning.get("recommended_next_command")
|
|
1117
|
+
if cmd:
|
|
1118
|
+
print(f" Recommended: {cmd}", file=sys.stderr)
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
def _build_projection_preflight_warnings(
|
|
1122
|
+
*,
|
|
1123
|
+
table_id: str,
|
|
1124
|
+
payload: Dict[str, Any],
|
|
1125
|
+
base_url: str,
|
|
1126
|
+
token: str,
|
|
1127
|
+
use_x_api_key: bool,
|
|
1128
|
+
timeout: int,
|
|
1129
|
+
verbose: bool,
|
|
1130
|
+
) -> List[Dict[str, Any]]:
|
|
1131
|
+
items = payload.get("items")
|
|
1132
|
+
if not isinstance(items, list) or not items:
|
|
1133
|
+
return []
|
|
1134
|
+
|
|
1135
|
+
source_ids: List[str] = []
|
|
1136
|
+
seen: set[str] = set()
|
|
1137
|
+
for item in items:
|
|
1138
|
+
if not isinstance(item, dict):
|
|
1139
|
+
continue
|
|
1140
|
+
raw_source = item.get("sourceColumnId") or item.get("source_column_id")
|
|
1141
|
+
source_id = str(raw_source or "").strip()
|
|
1142
|
+
if not source_id or source_id in seen:
|
|
1143
|
+
continue
|
|
1144
|
+
seen.add(source_id)
|
|
1145
|
+
source_ids.append(source_id)
|
|
1146
|
+
if not source_ids:
|
|
1147
|
+
return []
|
|
1148
|
+
|
|
1149
|
+
columns_raw = _request_api(
|
|
1150
|
+
"GET",
|
|
1151
|
+
f"/api/tables/{table_id}/columns",
|
|
1152
|
+
base_url=base_url,
|
|
1153
|
+
token=token,
|
|
1154
|
+
use_x_api_key=use_x_api_key,
|
|
1155
|
+
timeout=timeout,
|
|
1156
|
+
verbose=verbose,
|
|
1157
|
+
)
|
|
1158
|
+
columns = _extract_columns_list(columns_raw)
|
|
1159
|
+
by_id: Dict[str, Dict[str, Any]] = {}
|
|
1160
|
+
for col in columns:
|
|
1161
|
+
cid = str(col.get("id") or col.get("_id") or "").strip()
|
|
1162
|
+
if cid:
|
|
1163
|
+
by_id[cid] = col
|
|
1164
|
+
|
|
1165
|
+
warnings: List[Dict[str, Any]] = []
|
|
1166
|
+
rows_sample: Optional[List[Dict[str, Any]]] = None
|
|
1167
|
+
jobs_cache: Dict[str, Optional[Dict[str, Any]]] = {}
|
|
1168
|
+
|
|
1169
|
+
for source_id in source_ids:
|
|
1170
|
+
source_col = by_id.get(source_id)
|
|
1171
|
+
if not source_col or not _is_json_enrichment_column(source_col):
|
|
1172
|
+
continue
|
|
1173
|
+
|
|
1174
|
+
source_col_id = str(source_col.get("id") or source_col.get("_id") or source_id)
|
|
1175
|
+
source_key = str(source_col.get("key") or "").strip()
|
|
1176
|
+
recommended_next = _projection_recommended_run_command(table_id, source_col_id)
|
|
1177
|
+
|
|
1178
|
+
latest_job = jobs_cache.get(source_col_id)
|
|
1179
|
+
if source_col_id not in jobs_cache:
|
|
1180
|
+
jobs_raw = _request_api(
|
|
1181
|
+
"GET",
|
|
1182
|
+
"/api/bulk-jobs",
|
|
1183
|
+
base_url=base_url,
|
|
1184
|
+
token=token,
|
|
1185
|
+
use_x_api_key=use_x_api_key,
|
|
1186
|
+
params={"table_id": table_id, "column_id": source_col_id, "limit": 1},
|
|
1187
|
+
timeout=timeout,
|
|
1188
|
+
verbose=verbose,
|
|
1189
|
+
)
|
|
1190
|
+
jobs = _extract_jobs_list(jobs_raw)
|
|
1191
|
+
latest_job = jobs[0] if jobs else None
|
|
1192
|
+
jobs_cache[source_col_id] = latest_job
|
|
1193
|
+
|
|
1194
|
+
latest_job_status = str((latest_job or {}).get("status") or "").strip().lower()
|
|
1195
|
+
if latest_job_status in NON_TERMINAL_JOB_STATUSES:
|
|
1196
|
+
warnings.append(
|
|
1197
|
+
_projection_preflight_warn(
|
|
1198
|
+
code="source_job_not_terminal",
|
|
1199
|
+
column=source_col,
|
|
1200
|
+
reason=f"latest run status is '{latest_job_status}' (not terminal)",
|
|
1201
|
+
recommended_next_command=recommended_next,
|
|
1202
|
+
latest_job_status=latest_job_status,
|
|
1203
|
+
column_status=str(source_col.get("status") or "").strip().lower() or None,
|
|
1204
|
+
)
|
|
1205
|
+
)
|
|
1206
|
+
continue
|
|
1207
|
+
if latest_job_status in TERMINAL_JOB_STATUSES:
|
|
1208
|
+
continue
|
|
1209
|
+
|
|
1210
|
+
has_run_metadata = bool(
|
|
1211
|
+
source_col.get("lastRunAt")
|
|
1212
|
+
or source_col.get("last_run_at")
|
|
1213
|
+
or source_col.get("lastJobId")
|
|
1214
|
+
or source_col.get("last_job_id")
|
|
1215
|
+
)
|
|
1216
|
+
if has_run_metadata:
|
|
1217
|
+
continue
|
|
1218
|
+
|
|
1219
|
+
if rows_sample is None:
|
|
1220
|
+
rows_page_raw = _request_api(
|
|
1221
|
+
"GET",
|
|
1222
|
+
f"/api/tables/{table_id}/rows",
|
|
1223
|
+
base_url=base_url,
|
|
1224
|
+
token=token,
|
|
1225
|
+
use_x_api_key=use_x_api_key,
|
|
1226
|
+
params={"page_size": PROJECTION_READINESS_SAMPLE_SIZE},
|
|
1227
|
+
timeout=timeout,
|
|
1228
|
+
verbose=verbose,
|
|
1229
|
+
)
|
|
1230
|
+
if not isinstance(rows_page_raw, dict):
|
|
1231
|
+
rows_sample = []
|
|
1232
|
+
else:
|
|
1233
|
+
candidate_rows = rows_page_raw.get("rows") or []
|
|
1234
|
+
rows_sample = [row for row in candidate_rows if isinstance(row, dict)] if isinstance(candidate_rows, list) else []
|
|
1235
|
+
|
|
1236
|
+
sample_count = 0
|
|
1237
|
+
if source_key:
|
|
1238
|
+
sample_count = sum(1 for row in (rows_sample or []) if _is_processed_cell_value(row.get(source_key)))
|
|
1239
|
+
if sample_count > 0:
|
|
1240
|
+
continue
|
|
1241
|
+
|
|
1242
|
+
warnings.append(
|
|
1243
|
+
_projection_preflight_warn(
|
|
1244
|
+
code="source_unrun_or_empty",
|
|
1245
|
+
column=source_col,
|
|
1246
|
+
reason="source JSON enrichment has no terminal run evidence and sampled values are empty",
|
|
1247
|
+
recommended_next_command=recommended_next,
|
|
1248
|
+
latest_job_status=latest_job_status or None,
|
|
1249
|
+
column_status=str(source_col.get("status") or "").strip().lower() or None,
|
|
1250
|
+
sample_rows_scanned=len(rows_sample or []),
|
|
1251
|
+
sample_non_empty_values=sample_count,
|
|
1252
|
+
)
|
|
1253
|
+
)
|
|
1254
|
+
|
|
1255
|
+
return warnings
|
|
1256
|
+
|
|
1257
|
+
|
|
1031
1258
|
def _is_processed_cell_value(value: Any) -> bool:
|
|
1032
1259
|
if value is None:
|
|
1033
1260
|
return False
|
|
@@ -2226,7 +2453,7 @@ def cmd_capabilities(args: argparse.Namespace) -> None:
|
|
|
2226
2453
|
basic_types_str = ", ".join(str(t) for t in basic_types) if isinstance(basic_types, list) else "text/json"
|
|
2227
2454
|
print(f"basic : {basic_types_str} (JSON requires dataType=json)")
|
|
2228
2455
|
print("")
|
|
2229
|
-
print("Tip:
|
|
2456
|
+
print("Tip: for JSON enrichment columns, run with `--wait` and verify terminal status before `autotouch columns projections`.")
|
|
2230
2457
|
return
|
|
2231
2458
|
|
|
2232
2459
|
_print_json(data, compact=args.compact)
|
|
@@ -3343,6 +3570,18 @@ def cmd_columns_projections(args: argparse.Namespace) -> None:
|
|
|
3343
3570
|
if not isinstance(payload, dict):
|
|
3344
3571
|
print("ERROR: projection create requires --data-json/--data-file with a JSON object", file=sys.stderr)
|
|
3345
3572
|
sys.exit(2)
|
|
3573
|
+
|
|
3574
|
+
preflight_warnings = _build_projection_preflight_warnings(
|
|
3575
|
+
table_id=args.table_id,
|
|
3576
|
+
payload=payload,
|
|
3577
|
+
base_url=args.base_url,
|
|
3578
|
+
token=token,
|
|
3579
|
+
use_x_api_key=args.use_x_api_key,
|
|
3580
|
+
timeout=args.timeout,
|
|
3581
|
+
verbose=args.verbose,
|
|
3582
|
+
)
|
|
3583
|
+
_print_projection_preflight_warnings(preflight_warnings)
|
|
3584
|
+
|
|
3346
3585
|
data = _request_api(
|
|
3347
3586
|
"POST",
|
|
3348
3587
|
f"/api/tables/{args.table_id}/columns/projections",
|
|
@@ -3353,6 +3592,16 @@ def cmd_columns_projections(args: argparse.Namespace) -> None:
|
|
|
3353
3592
|
timeout=args.timeout,
|
|
3354
3593
|
verbose=args.verbose,
|
|
3355
3594
|
)
|
|
3595
|
+
if preflight_warnings:
|
|
3596
|
+
_print_json(
|
|
3597
|
+
{
|
|
3598
|
+
"event": "projections.created_with_warnings",
|
|
3599
|
+
"warnings": preflight_warnings,
|
|
3600
|
+
"result": data,
|
|
3601
|
+
},
|
|
3602
|
+
compact=args.compact,
|
|
3603
|
+
)
|
|
3604
|
+
return
|
|
3356
3605
|
_print_json(data, compact=args.compact)
|
|
3357
3606
|
|
|
3358
3607
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/attach_csv_import_leads_to_research_table.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250106_add_column_position.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250123_add_filter_indexes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250604_add_origin_indexes.py
RENAMED
|
File without changes
|
{autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250608_cleanup_agent_metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
{autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250922_add_activity_indexes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/20250929_add_task_join_indexes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/migrate_org_user_credits.py
RENAMED
|
File without changes
|
|
File without changes
|
{autotouch_cli-0.2.22 → autotouch_cli-0.2.23}/scripts/migrations/update_lead_owner_from_tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|