@cloudstreamsoftware/claude-tools 1.0.0 → 1.1.0

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.
Files changed (189) hide show
  1. package/README.md +152 -37
  2. package/agents/INDEX.md +183 -0
  3. package/agents/architect.md +247 -0
  4. package/agents/build-error-resolver.md +555 -0
  5. package/agents/catalyst-deployer.md +132 -0
  6. package/agents/code-reviewer.md +121 -0
  7. package/agents/compliance-auditor.md +148 -0
  8. package/agents/creator-architect.md +395 -0
  9. package/agents/deluge-reviewer.md +98 -0
  10. package/agents/doc-updater.md +471 -0
  11. package/agents/e2e-runner.md +711 -0
  12. package/agents/planner.md +122 -0
  13. package/agents/refactor-cleaner.md +309 -0
  14. package/agents/security-reviewer.md +582 -0
  15. package/agents/tdd-guide.md +302 -0
  16. package/config/versions.json +63 -0
  17. package/dist/hooks/hooks.json +209 -0
  18. package/dist/index.js +47 -0
  19. package/dist/lib/asset-value.js +609 -0
  20. package/dist/lib/client-manager.js +300 -0
  21. package/dist/lib/command-matcher.js +242 -0
  22. package/dist/lib/cross-session-patterns.js +754 -0
  23. package/dist/lib/intent-classifier.js +1075 -0
  24. package/dist/lib/package-manager.js +374 -0
  25. package/dist/lib/recommendation-engine.js +597 -0
  26. package/dist/lib/session-memory.js +489 -0
  27. package/dist/lib/skill-effectiveness.js +486 -0
  28. package/dist/lib/skill-matcher.js +595 -0
  29. package/dist/lib/tutorial-metrics.js +242 -0
  30. package/dist/lib/tutorial-progress.js +209 -0
  31. package/dist/lib/tutorial-renderer.js +431 -0
  32. package/dist/lib/utils.js +380 -0
  33. package/dist/lib/verify-formatter.js +143 -0
  34. package/dist/lib/workflow-state.js +249 -0
  35. package/hooks/hooks.json +209 -0
  36. package/package.json +5 -1
  37. package/scripts/aggregate-sessions.js +290 -0
  38. package/scripts/branch-name-validator.js +291 -0
  39. package/scripts/build.js +101 -0
  40. package/scripts/commands/client-switch.js +231 -0
  41. package/scripts/deprecate-skill.js +610 -0
  42. package/scripts/diagnose.js +324 -0
  43. package/scripts/doc-freshness.js +168 -0
  44. package/scripts/generate-weekly-digest.js +393 -0
  45. package/scripts/health-check.js +270 -0
  46. package/scripts/hooks/credential-check.js +101 -0
  47. package/scripts/hooks/evaluate-session.js +81 -0
  48. package/scripts/hooks/pre-compact.js +66 -0
  49. package/scripts/hooks/prompt-analyzer.js +276 -0
  50. package/scripts/hooks/prompt-router.js +422 -0
  51. package/scripts/hooks/quality-gate-enforcer.js +371 -0
  52. package/scripts/hooks/session-end.js +156 -0
  53. package/scripts/hooks/session-start.js +195 -0
  54. package/scripts/hooks/skill-injector.js +333 -0
  55. package/scripts/hooks/suggest-compact.js +58 -0
  56. package/scripts/lib/asset-value.js +609 -0
  57. package/scripts/lib/client-manager.js +300 -0
  58. package/scripts/lib/command-matcher.js +242 -0
  59. package/scripts/lib/cross-session-patterns.js +754 -0
  60. package/scripts/lib/intent-classifier.js +1075 -0
  61. package/scripts/lib/package-manager.js +374 -0
  62. package/scripts/lib/recommendation-engine.js +597 -0
  63. package/scripts/lib/session-memory.js +489 -0
  64. package/scripts/lib/skill-effectiveness.js +486 -0
  65. package/scripts/lib/skill-matcher.js +595 -0
  66. package/scripts/lib/tutorial-metrics.js +242 -0
  67. package/scripts/lib/tutorial-progress.js +209 -0
  68. package/scripts/lib/tutorial-renderer.js +431 -0
  69. package/scripts/lib/utils.js +380 -0
  70. package/scripts/lib/verify-formatter.js +143 -0
  71. package/scripts/lib/workflow-state.js +249 -0
  72. package/scripts/onboard.js +363 -0
  73. package/scripts/quarterly-report.js +692 -0
  74. package/scripts/setup-package-manager.js +204 -0
  75. package/scripts/sync-upstream.js +391 -0
  76. package/scripts/test.js +108 -0
  77. package/scripts/tutorial-runner.js +351 -0
  78. package/scripts/validate-all.js +201 -0
  79. package/scripts/verifiers/agents.js +245 -0
  80. package/scripts/verifiers/config.js +186 -0
  81. package/scripts/verifiers/environment.js +123 -0
  82. package/scripts/verifiers/hooks.js +188 -0
  83. package/scripts/verifiers/index.js +38 -0
  84. package/scripts/verifiers/persistence.js +140 -0
  85. package/scripts/verifiers/plugin.js +215 -0
  86. package/scripts/verifiers/skills.js +209 -0
  87. package/scripts/verify-setup.js +164 -0
  88. package/skills/INDEX.md +157 -0
  89. package/skills/backend-patterns/SKILL.md +586 -0
  90. package/skills/backend-patterns/catalyst-patterns.md +128 -0
  91. package/skills/bigquery-patterns/SKILL.md +27 -0
  92. package/skills/bigquery-patterns/performance-optimization.md +518 -0
  93. package/skills/bigquery-patterns/query-patterns.md +372 -0
  94. package/skills/bigquery-patterns/schema-design.md +78 -0
  95. package/skills/cloudstream-project-template/SKILL.md +20 -0
  96. package/skills/cloudstream-project-template/structure.md +65 -0
  97. package/skills/coding-standards/SKILL.md +524 -0
  98. package/skills/coding-standards/deluge-standards.md +83 -0
  99. package/skills/compliance-patterns/SKILL.md +28 -0
  100. package/skills/compliance-patterns/hipaa/audit-requirements.md +251 -0
  101. package/skills/compliance-patterns/hipaa/baa-process.md +298 -0
  102. package/skills/compliance-patterns/hipaa/data-archival-strategy.md +387 -0
  103. package/skills/compliance-patterns/hipaa/phi-handling.md +52 -0
  104. package/skills/compliance-patterns/pci-dss/saq-a-requirements.md +307 -0
  105. package/skills/compliance-patterns/pci-dss/tokenization-patterns.md +382 -0
  106. package/skills/compliance-patterns/pci-dss/zoho-checkout-patterns.md +56 -0
  107. package/skills/compliance-patterns/soc2/access-controls.md +344 -0
  108. package/skills/compliance-patterns/soc2/audit-logging.md +458 -0
  109. package/skills/compliance-patterns/soc2/change-management.md +403 -0
  110. package/skills/compliance-patterns/soc2/deluge-execution-logging.md +407 -0
  111. package/skills/consultancy-workflows/SKILL.md +19 -0
  112. package/skills/consultancy-workflows/client-isolation.md +21 -0
  113. package/skills/consultancy-workflows/documentation-automation.md +454 -0
  114. package/skills/consultancy-workflows/handoff-procedures.md +257 -0
  115. package/skills/consultancy-workflows/knowledge-capture.md +513 -0
  116. package/skills/consultancy-workflows/time-tracking.md +26 -0
  117. package/skills/continuous-learning/SKILL.md +84 -0
  118. package/skills/continuous-learning/config.json +18 -0
  119. package/skills/continuous-learning/evaluate-session.sh +60 -0
  120. package/skills/continuous-learning-v2/SKILL.md +126 -0
  121. package/skills/continuous-learning-v2/config.json +61 -0
  122. package/skills/frontend-patterns/SKILL.md +635 -0
  123. package/skills/frontend-patterns/zoho-widget-patterns.md +103 -0
  124. package/skills/gcp-data-engineering/SKILL.md +36 -0
  125. package/skills/gcp-data-engineering/bigquery/performance-optimization.md +337 -0
  126. package/skills/gcp-data-engineering/dataflow/error-handling.md +496 -0
  127. package/skills/gcp-data-engineering/dataflow/pipeline-patterns.md +444 -0
  128. package/skills/gcp-data-engineering/dbt/model-organization.md +63 -0
  129. package/skills/gcp-data-engineering/dbt/testing-patterns.md +503 -0
  130. package/skills/gcp-data-engineering/medallion-architecture/bronze-layer.md +60 -0
  131. package/skills/gcp-data-engineering/medallion-architecture/gold-layer.md +311 -0
  132. package/skills/gcp-data-engineering/medallion-architecture/layer-transitions.md +517 -0
  133. package/skills/gcp-data-engineering/medallion-architecture/silver-layer.md +305 -0
  134. package/skills/gcp-data-engineering/zoho-to-gcp/data-extraction.md +543 -0
  135. package/skills/gcp-data-engineering/zoho-to-gcp/real-time-vs-batch.md +337 -0
  136. package/skills/security-review/SKILL.md +498 -0
  137. package/skills/security-review/compliance-checklist.md +53 -0
  138. package/skills/strategic-compact/SKILL.md +67 -0
  139. package/skills/tdd-workflow/SKILL.md +413 -0
  140. package/skills/tdd-workflow/zoho-testing.md +124 -0
  141. package/skills/tutorial/SKILL.md +249 -0
  142. package/skills/tutorial/docs/ACCESSIBILITY.md +169 -0
  143. package/skills/tutorial/lessons/00-philosophy-and-workflow.md +198 -0
  144. package/skills/tutorial/lessons/01-basics.md +81 -0
  145. package/skills/tutorial/lessons/02-training.md +86 -0
  146. package/skills/tutorial/lessons/03-commands.md +109 -0
  147. package/skills/tutorial/lessons/04-workflows.md +115 -0
  148. package/skills/tutorial/lessons/05-compliance.md +116 -0
  149. package/skills/tutorial/lessons/06-zoho.md +121 -0
  150. package/skills/tutorial/lessons/07-hooks-system.md +277 -0
  151. package/skills/tutorial/lessons/08-mcp-servers.md +316 -0
  152. package/skills/tutorial/lessons/09-client-management.md +215 -0
  153. package/skills/tutorial/lessons/10-testing-e2e.md +260 -0
  154. package/skills/tutorial/lessons/11-skills-deep-dive.md +272 -0
  155. package/skills/tutorial/lessons/12-rules-system.md +326 -0
  156. package/skills/tutorial/lessons/13-golden-standard-graduation.md +213 -0
  157. package/skills/tutorial/lessons/14-fork-setup-and-sync.md +312 -0
  158. package/skills/tutorial/lessons/15-living-examples-system.md +221 -0
  159. package/skills/tutorial/tracks/accelerated/README.md +134 -0
  160. package/skills/tutorial/tracks/accelerated/assessment/checkpoint-1.md +161 -0
  161. package/skills/tutorial/tracks/accelerated/assessment/checkpoint-2.md +175 -0
  162. package/skills/tutorial/tracks/accelerated/day-1-core-concepts.md +234 -0
  163. package/skills/tutorial/tracks/accelerated/day-2-essential-commands.md +270 -0
  164. package/skills/tutorial/tracks/accelerated/day-3-workflow-mastery.md +305 -0
  165. package/skills/tutorial/tracks/accelerated/day-4-compliance-zoho.md +304 -0
  166. package/skills/tutorial/tracks/accelerated/day-5-hooks-skills.md +344 -0
  167. package/skills/tutorial/tracks/accelerated/day-6-client-testing.md +386 -0
  168. package/skills/tutorial/tracks/accelerated/day-7-graduation.md +369 -0
  169. package/skills/zoho-patterns/CHANGELOG.md +108 -0
  170. package/skills/zoho-patterns/SKILL.md +446 -0
  171. package/skills/zoho-patterns/analytics/dashboard-patterns.md +352 -0
  172. package/skills/zoho-patterns/analytics/zoho-to-bigquery-pipeline.md +427 -0
  173. package/skills/zoho-patterns/catalyst/appsail-deployment.md +349 -0
  174. package/skills/zoho-patterns/catalyst/context-close-patterns.md +354 -0
  175. package/skills/zoho-patterns/catalyst/cron-batch-processing.md +374 -0
  176. package/skills/zoho-patterns/catalyst/function-patterns.md +439 -0
  177. package/skills/zoho-patterns/creator/form-design.md +304 -0
  178. package/skills/zoho-patterns/creator/publish-api-patterns.md +313 -0
  179. package/skills/zoho-patterns/creator/widget-integration.md +306 -0
  180. package/skills/zoho-patterns/creator/workflow-automation.md +253 -0
  181. package/skills/zoho-patterns/deluge/api-patterns.md +468 -0
  182. package/skills/zoho-patterns/deluge/batch-processing.md +403 -0
  183. package/skills/zoho-patterns/deluge/cross-app-integration.md +356 -0
  184. package/skills/zoho-patterns/deluge/error-handling.md +423 -0
  185. package/skills/zoho-patterns/deluge/syntax-reference.md +65 -0
  186. package/skills/zoho-patterns/integration/cors-proxy-architecture.md +426 -0
  187. package/skills/zoho-patterns/integration/crm-books-native-sync.md +277 -0
  188. package/skills/zoho-patterns/integration/oauth-token-management.md +461 -0
  189. package/skills/zoho-patterns/integration/zoho-flow-patterns.md +334 -0
@@ -0,0 +1,543 @@
1
+ # Zoho to GCP Data Extraction
2
+
3
+ > Patterns for extracting data from Zoho CRM, Books, Creator, and Analytics into GCP using CData Sync, Catalyst Cron, webhooks, and APIs.
4
+
5
+ ## Extraction Method Overview
6
+
7
+ | Method | Latency | Complexity | Best For |
8
+ |--------|---------|------------|----------|
9
+ | CData Sync | 15min-daily | Low | Bulk initial loads, daily syncs |
10
+ | Catalyst Cron | 5min-hourly | Medium | Scheduled incremental pulls |
11
+ | Zoho Webhooks → PubSub | Real-time | High | Critical business events |
12
+ | Zoho Analytics Export | Daily | Low | Pre-built reports/datasets |
13
+ | Bulk API | Hourly-daily | Medium | Large record sets (>10K) |
14
+
15
+ ## CData Sync Configuration
16
+
17
+ CData Sync provides a managed connector from Zoho to BigQuery with minimal code.
18
+
19
+ ### Connection Setup
20
+
21
+ ```yaml
22
+ # cdata_sync_config.yml (conceptual - configured via CData UI)
23
+ connections:
24
+ zoho_crm:
25
+ driver: ZohoCRM
26
+ auth_type: OAuth
27
+ client_id: "${ZOHO_CLIENT_ID}"
28
+ client_secret: "${ZOHO_CLIENT_SECRET}"
29
+ refresh_token: "${ZOHO_REFRESH_TOKEN}"
30
+ api_domain: "https://www.zohoapis.com"
31
+ accounts_server: "https://accounts.zoho.com"
32
+
33
+ bigquery_target:
34
+ driver: BigQuery
35
+ project: cloudstream-prod
36
+ dataset: bronze
37
+ service_account_key: "${GCP_SA_KEY_PATH}"
38
+ location: us-central1
39
+
40
+ sync_jobs:
41
+ - name: zoho_deals_daily
42
+ source: zoho_crm
43
+ target: bigquery_target
44
+ table: Deals
45
+ destination_table: zoho_deals
46
+ schedule: "0 2 * * *" # 2 AM daily
47
+ mode: incremental
48
+ incremental_column: Modified_Time
49
+ batch_size: 5000
50
+
51
+ - name: zoho_contacts_daily
52
+ source: zoho_crm
53
+ target: bigquery_target
54
+ table: Contacts
55
+ destination_table: zoho_contacts
56
+ schedule: "0 2 * * *"
57
+ mode: incremental
58
+ incremental_column: Modified_Time
59
+
60
+ - name: zoho_invoices_daily
61
+ source: zoho_crm # Or Zoho Books connection
62
+ target: bigquery_target
63
+ table: Invoices
64
+ destination_table: zoho_invoices
65
+ schedule: "0 3 * * *" # After deals sync
66
+ mode: incremental
67
+ incremental_column: Modified_Time
68
+ ```
69
+
70
+ ### CData Sync Metadata Columns
71
+
72
+ CData automatically adds these columns - preserve them in bronze:
73
+
74
+ | Column | Description |
75
+ |--------|-------------|
76
+ | `_cdata_sync_id` | Unique sync operation ID |
77
+ | `_cdata_sync_timestamp` | When the record was synced |
78
+ | `_cdata_sync_operation` | INSERT, UPDATE, or DELETE |
79
+
80
+ ## Catalyst Cron Function API Pulls
81
+
82
+ ### Scheduled Extraction (Every 5 Minutes)
83
+
84
+ ```python
85
+ # catalyst_cron/zoho_crm_sync/main.py
86
+ """Catalyst Cron function: Pull modified Zoho CRM records every 5 minutes."""
87
+
88
+ import zcatalyst_sdk
89
+ from google.cloud import bigquery, pubsub_v1
90
+ from datetime import datetime, timedelta
91
+ import requests
92
+ import json
93
+
94
+ # Zoho API configuration
95
+ ZOHO_API_BASE = "https://www.zohoapis.com/crm/v5"
96
+ MODULES = ['Deals', 'Contacts', 'Accounts', 'Tasks']
97
+
98
+ def handler(context, cronDetails):
99
+ """Catalyst Cron entry point - runs every 5 minutes."""
100
+ app = zcatalyst_sdk.initialize()
101
+ cache = app.cache()
102
+
103
+ for module in MODULES:
104
+ try:
105
+ sync_module(app, cache, module)
106
+ except Exception as e:
107
+ print(f"Error syncing {module}: {e}")
108
+ # Continue with other modules even if one fails
109
+
110
+ def sync_module(app, cache, module_name):
111
+ """Sync a single Zoho module incrementally."""
112
+ # Get last sync timestamp from Catalyst cache
113
+ segment = cache.get_segment('sync_timestamps')
114
+ last_sync = segment.get(f'{module_name}_last_sync')
115
+
116
+ if last_sync:
117
+ last_sync_time = last_sync['value']
118
+ else:
119
+ # First run: get last 24 hours
120
+ last_sync_time = (datetime.utcnow() - timedelta(hours=24)).strftime('%Y-%m-%dT%H:%M:%S+00:00')
121
+
122
+ # Fetch modified records from Zoho
123
+ access_token = get_zoho_token(app)
124
+ records = fetch_modified_records(access_token, module_name, last_sync_time)
125
+
126
+ if records:
127
+ # Publish to PubSub for GCP ingestion
128
+ publish_to_pubsub(records, module_name)
129
+
130
+ # Update last sync timestamp
131
+ segment.put(f'{module_name}_last_sync', {
132
+ 'value': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S+00:00')
133
+ })
134
+
135
+ print(f"Synced {len(records)} {module_name} records")
136
+
137
+ def fetch_modified_records(token, module, since_time):
138
+ """Fetch records modified since last sync."""
139
+ headers = {"Authorization": f"Zoho-oauthtoken {token}"}
140
+ records = []
141
+ page = 1
142
+ has_more = True
143
+
144
+ while has_more:
145
+ params = {
146
+ 'modified_since': since_time,
147
+ 'page': page,
148
+ 'per_page': 200, # Max per page
149
+ 'sort_by': 'Modified_Time',
150
+ 'sort_order': 'asc'
151
+ }
152
+
153
+ response = requests.get(
154
+ f"{ZOHO_API_BASE}/{module}",
155
+ headers=headers,
156
+ params=params
157
+ )
158
+
159
+ if response.status_code == 200:
160
+ data = response.json()
161
+ records.extend(data.get('data', []))
162
+ has_more = data.get('info', {}).get('more_records', False)
163
+ page += 1
164
+ elif response.status_code == 204:
165
+ # No records modified
166
+ has_more = False
167
+ else:
168
+ raise Exception(f"Zoho API error: {response.status_code} - {response.text}")
169
+
170
+ return records
171
+
172
+ def publish_to_pubsub(records, module_name):
173
+ """Publish records to PubSub for downstream processing."""
174
+ publisher = pubsub_v1.PublisherClient()
175
+ topic_path = publisher.topic_path('cloudstream-prod', 'zoho-ingestion')
176
+
177
+ for record in records:
178
+ message = json.dumps({
179
+ 'module': module_name,
180
+ 'record': record,
181
+ 'extracted_at': datetime.utcnow().isoformat(),
182
+ 'source': 'catalyst_cron'
183
+ }).encode('utf-8')
184
+
185
+ publisher.publish(
186
+ topic_path,
187
+ message,
188
+ module=module_name,
189
+ record_id=str(record.get('id', ''))
190
+ )
191
+
192
+ def get_zoho_token(app):
193
+ """Get fresh OAuth token using refresh token from Catalyst vault."""
194
+ # Store refresh token in Catalyst Vault (not in code)
195
+ connector = app.connection()
196
+ token = connector.get_connector('zoho_crm').execute_request(
197
+ method='POST',
198
+ url='https://accounts.zoho.com/oauth/v2/token',
199
+ params={
200
+ 'grant_type': 'refresh_token',
201
+ 'refresh_token': app.vault().get_secret('zoho_refresh_token'),
202
+ 'client_id': app.vault().get_secret('zoho_client_id'),
203
+ 'client_secret': app.vault().get_secret('zoho_client_secret')
204
+ }
205
+ )
206
+ return token['access_token']
207
+ ```
208
+
209
+ ## Webhook-Triggered Ingestion
210
+
211
+ ### Zoho Flow to PubSub
212
+
213
+ ```python
214
+ # cloud_function/zoho_webhook_receiver/main.py
215
+ """Cloud Function: Receive Zoho webhooks and publish to PubSub."""
216
+
217
+ import functions_framework
218
+ from google.cloud import pubsub_v1
219
+ import json
220
+ import hashlib
221
+ import hmac
222
+ from datetime import datetime
223
+
224
+ WEBHOOK_SECRET = "your-webhook-secret" # Store in Secret Manager
225
+ PROJECT_ID = "cloudstream-prod"
226
+ TOPIC_ID = "zoho-webhooks"
227
+
228
+ @functions_framework.http
229
+ def receive_webhook(request):
230
+ """HTTP endpoint for Zoho workflow/Flow webhooks."""
231
+ # Verify webhook signature
232
+ if not verify_signature(request):
233
+ return ('Unauthorized', 401)
234
+
235
+ payload = request.get_json(force=True)
236
+
237
+ # Extract event metadata
238
+ event = {
239
+ 'module': payload.get('module', 'unknown'),
240
+ 'event_type': payload.get('event', 'unknown'), # create, update, delete
241
+ 'record_id': payload.get('id', ''),
242
+ 'record_data': payload.get('data', {}),
243
+ 'triggered_at': datetime.utcnow().isoformat(),
244
+ 'source': 'zoho_webhook'
245
+ }
246
+
247
+ # Publish to PubSub
248
+ publisher = pubsub_v1.PublisherClient()
249
+ topic_path = publisher.topic_path(PROJECT_ID, TOPIC_ID)
250
+
251
+ future = publisher.publish(
252
+ topic_path,
253
+ json.dumps(event).encode('utf-8'),
254
+ module=event['module'],
255
+ event_type=event['event_type'],
256
+ record_id=event['record_id']
257
+ )
258
+ message_id = future.result()
259
+
260
+ return (json.dumps({'status': 'ok', 'message_id': message_id}), 200)
261
+
262
+ def verify_signature(request):
263
+ """Verify Zoho webhook HMAC signature."""
264
+ signature = request.headers.get('X-Zoho-Webhook-Signature', '')
265
+ body = request.get_data()
266
+ expected = hmac.new(
267
+ WEBHOOK_SECRET.encode(), body, hashlib.sha256
268
+ ).hexdigest()
269
+ return hmac.compare_digest(signature, expected)
270
+ ```
271
+
272
+ ### Zoho Flow Configuration
273
+
274
+ ```
275
+ Zoho Flow Trigger:
276
+ Module: Deals
277
+ Events: Create, Update, Delete
278
+ Action: Webhook POST to:
279
+ URL: https://us-central1-cloudstream-prod.cloudfunctions.net/zoho-webhook-receiver
280
+ Headers:
281
+ Content-Type: application/json
282
+ X-Zoho-Webhook-Signature: ${HMAC_SHA256(payload, secret)}
283
+ Body: ${record_data}
284
+ ```
285
+
286
+ ## Zoho Analytics Export API
287
+
288
+ ```python
289
+ # scripts/zoho_analytics_export.py
290
+ """Export datasets from Zoho Analytics for comparison/validation."""
291
+
292
+ import requests
293
+ from google.cloud import storage
294
+ import json
295
+
296
+ ZOHO_ANALYTICS_API = "https://analyticsapi.zoho.com/api"
297
+ ORG_ID = "your-org-id"
298
+ WORKSPACE = "your-workspace"
299
+
300
+ def export_zoho_analytics_table(table_name, format='json'):
301
+ """Export a Zoho Analytics table/view to GCS."""
302
+ token = get_analytics_token()
303
+
304
+ response = requests.get(
305
+ f"{ZOHO_ANALYTICS_API}/{ORG_ID}/{WORKSPACE}/{table_name}",
306
+ headers={"Authorization": f"Zoho-oauthtoken {token}"},
307
+ params={
308
+ 'ZOHO_ACTION': 'EXPORT',
309
+ 'ZOHO_OUTPUT_FORMAT': format.upper(),
310
+ 'ZOHO_ERROR_FORMAT': 'JSON',
311
+ 'ZOHO_API_VERSION': '1.0'
312
+ }
313
+ )
314
+
315
+ if response.status_code == 200:
316
+ # Upload to GCS
317
+ client = storage.Client()
318
+ bucket = client.bucket('cloudstream-landing')
319
+ blob = bucket.blob(
320
+ f'zoho-analytics/{table_name}/{datetime.now().strftime("%Y/%m/%d")}/export.{format}'
321
+ )
322
+ blob.upload_from_string(response.content)
323
+ print(f"Exported {table_name} to GCS")
324
+ else:
325
+ raise Exception(f"Export failed: {response.status_code}")
326
+ ```
327
+
328
+ ## Bulk API vs Single Record
329
+
330
+ ### Decision Matrix
331
+
332
+ | Factor | Bulk API | Single Record API |
333
+ |--------|----------|-------------------|
334
+ | Records per call | Up to 200 (insert), 100 (read) | 1 |
335
+ | Rate limit impact | 1 API call = 1 credit | 1 API call = 1 credit |
336
+ | Latency | Higher (batching overhead) | Lower |
337
+ | Error handling | Partial success possible | All or nothing |
338
+ | Best for | Initial load, daily sync | Webhooks, real-time |
339
+ | Max daily calls | Based on plan | Based on plan |
340
+
341
+ ### Bulk Read Example
342
+
343
+ ```python
344
+ def bulk_read_zoho_module(module, criteria=None, page=1):
345
+ """Read up to 200 records per page using bulk read."""
346
+ headers = {"Authorization": f"Zoho-oauthtoken {get_token()}"}
347
+
348
+ # Submit bulk read job
349
+ body = {
350
+ "query": {
351
+ "module": {"api_name": module},
352
+ "page": page,
353
+ "per_page": 200
354
+ }
355
+ }
356
+ if criteria:
357
+ body["query"]["criteria"] = criteria
358
+
359
+ response = requests.post(
360
+ f"{ZOHO_API_BASE}/bulk/read",
361
+ headers=headers,
362
+ json=body
363
+ )
364
+
365
+ job_id = response.json()['data'][0]['details']['id']
366
+
367
+ # Poll for completion
368
+ while True:
369
+ status = requests.get(
370
+ f"{ZOHO_API_BASE}/bulk/read/{job_id}",
371
+ headers=headers
372
+ ).json()
373
+
374
+ state = status['data'][0]['state']
375
+ if state == 'COMPLETED':
376
+ # Download result file
377
+ download_url = status['data'][0]['result']['download_url']
378
+ return requests.get(download_url, headers=headers).content
379
+ elif state == 'FAILED':
380
+ raise Exception(f"Bulk read failed: {status}")
381
+ time.sleep(5)
382
+ ```
383
+
384
+ ## Rate Limiting Awareness
385
+
386
+ > **WARNING**: Zoho API rate limits vary significantly by plan. Exceeding limits returns HTTP 429 and may temporarily block API access.
387
+
388
+ | Zoho Plan | API Credits/Day | Credits/Minute | Concurrent Requests |
389
+ |-----------|----------------|----------------|---------------------|
390
+ | Free | 5,000 | 100 | 5 |
391
+ | Standard | 10,000 | 150 | 10 |
392
+ | Professional | 15,000 | 200 | 15 |
393
+ | Enterprise | 25,000 | 250 | 25 |
394
+ | Ultimate | 50,000 | 500 | 25 |
395
+
396
+ ```python
397
+ # Rate limiting implementation
398
+ import time
399
+ from functools import wraps
400
+
401
+ class ZohoRateLimiter:
402
+ """Rate limiter for Zoho API calls."""
403
+
404
+ def __init__(self, calls_per_minute=100, daily_limit=10000):
405
+ self.calls_per_minute = calls_per_minute
406
+ self.daily_limit = daily_limit
407
+ self.minute_calls = []
408
+ self.daily_count = 0
409
+
410
+ def wait_if_needed(self):
411
+ """Block if rate limit would be exceeded."""
412
+ now = time.time()
413
+
414
+ # Clean old entries
415
+ self.minute_calls = [t for t in self.minute_calls if now - t < 60]
416
+
417
+ if len(self.minute_calls) >= self.calls_per_minute:
418
+ sleep_time = 60 - (now - self.minute_calls[0])
419
+ print(f"Rate limit approaching, sleeping {sleep_time:.1f}s")
420
+ time.sleep(sleep_time)
421
+
422
+ if self.daily_count >= self.daily_limit * 0.9: # 90% threshold warning
423
+ print(f"WARNING: {self.daily_count}/{self.daily_limit} daily API credits used")
424
+
425
+ self.minute_calls.append(now)
426
+ self.daily_count += 1
427
+
428
+ rate_limiter = ZohoRateLimiter(calls_per_minute=80, daily_limit=10000) # 80% of limit
429
+
430
+ def rate_limited_request(url, headers, params=None):
431
+ """Make a rate-limited request to Zoho API."""
432
+ rate_limiter.wait_if_needed()
433
+ response = requests.get(url, headers=headers, params=params)
434
+
435
+ if response.status_code == 429:
436
+ # Hit rate limit - back off significantly
437
+ retry_after = int(response.headers.get('Retry-After', 60))
438
+ print(f"Rate limited! Waiting {retry_after}s")
439
+ time.sleep(retry_after)
440
+ return rate_limited_request(url, headers, params)
441
+
442
+ return response
443
+ ```
444
+
445
+ ## Incremental Extraction (modified_time Filter)
446
+
447
+ ```python
448
+ def incremental_extract(module, last_modified_time):
449
+ """Extract only records modified since last sync."""
450
+ headers = {"Authorization": f"Zoho-oauthtoken {get_token()}"}
451
+
452
+ all_records = []
453
+ page = 1
454
+ has_more = True
455
+
456
+ while has_more:
457
+ response = rate_limited_request(
458
+ f"{ZOHO_API_BASE}/{module}",
459
+ headers=headers,
460
+ params={
461
+ 'modified_since': last_modified_time, # ISO 8601 format
462
+ 'page': page,
463
+ 'per_page': 200,
464
+ 'sort_by': 'Modified_Time',
465
+ 'sort_order': 'asc',
466
+ 'fields': 'id,Modified_Time,Created_Time,Deal_Name,Amount,Stage,Owner'
467
+ }
468
+ )
469
+
470
+ if response.status_code == 200:
471
+ data = response.json()
472
+ all_records.extend(data.get('data', []))
473
+ has_more = data.get('info', {}).get('more_records', False)
474
+ page += 1
475
+ elif response.status_code == 204:
476
+ has_more = False # No modified records
477
+ else:
478
+ raise Exception(f"API error: {response.status_code}")
479
+
480
+ return all_records
481
+ ```
482
+
483
+ ## Full Refresh Patterns
484
+
485
+ ```python
486
+ def full_refresh(module, destination_table):
487
+ """Full table replacement - use sparingly due to API credit cost."""
488
+ print(f"Starting full refresh for {module}")
489
+
490
+ all_records = []
491
+ page = 1
492
+ has_more = True
493
+
494
+ while has_more:
495
+ response = rate_limited_request(
496
+ f"{ZOHO_API_BASE}/{module}",
497
+ headers={"Authorization": f"Zoho-oauthtoken {get_token()}"},
498
+ params={'page': page, 'per_page': 200}
499
+ )
500
+
501
+ if response.status_code == 200:
502
+ data = response.json()
503
+ all_records.extend(data.get('data', []))
504
+ has_more = data.get('info', {}).get('more_records', False)
505
+ page += 1
506
+ else:
507
+ has_more = False
508
+
509
+ # Load to BigQuery with WRITE_TRUNCATE (full replace)
510
+ client = bigquery.Client()
511
+ job_config = bigquery.LoadJobConfig(
512
+ write_disposition=bigquery.WriteDisposition.WRITE_TRUNCATE,
513
+ source_format=bigquery.SourceFormat.NEWLINE_DELIMITED_JSON
514
+ )
515
+
516
+ # Write to temp file, then load
517
+ import tempfile
518
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.jsonl') as f:
519
+ for record in all_records:
520
+ f.write(json.dumps(record) + '\n')
521
+ f.flush()
522
+
523
+ with open(f.name, 'rb') as source:
524
+ job = client.load_table_from_file(
525
+ source, destination_table, job_config=job_config
526
+ )
527
+ job.result()
528
+
529
+ print(f"Full refresh complete: {len(all_records)} records loaded to {destination_table}")
530
+ ```
531
+
532
+ > **COST TIP**: A full refresh of 50,000 records costs 250 API credits (200 records/page). Do full refreshes weekly at most; use incremental extraction for daily syncs.
533
+
534
+ ## Best Practices
535
+
536
+ 1. **Always use incremental extraction** for daily operations - saves API credits
537
+ 2. **Store refresh tokens in Secret Manager** - never in code or environment variables
538
+ 3. **Implement rate limiting client-side** - do not rely on 429 responses
539
+ 4. **Use PubSub as buffer** - decouples extraction speed from load speed
540
+ 5. **Schedule full refreshes weekly** - catches any missed incremental updates
541
+ 6. **Monitor API credit usage** - set alerts at 70% daily consumption
542
+ 7. **Use bulk API for initial loads** - more efficient than single-record pagination
543
+ 8. **Preserve all Zoho metadata** - `Modified_Time`, `Created_Time`, `Owner` are critical for silver layer