feat: self-provisioning Gmail connector (metadata-only, two-stage sync→build) - #2
Open
maxdolphin wants to merge 16 commits into
Open
maxdolphin wants to merge 16 commits into
maxdolphin wants to merge 16 commits into
Conversation
Self-provisioning Gmail network source: admin OAuth (domain-wide delegation, metadata-only gmail.metadata scope), org-wide pull into a persisted gmail_interactions table (Stage 1), and a pure re-runnable weighting/build step (Stage 2) feeding the existing build_flow_matrix_from_edges -> provision_network -> get_full_profile path. Hybrid recency-decay x sustained-engagement weighting, both individual and department granularity, configurable window + half-life. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- §5.1 node resolution: individual=email, department=orgUnitPath leaf; filter external addresses to known org users (drop + report count) - BaseConnector conformance: explicit sync()/build() + get_flow_data wrapper - sync_run_id / now_utc minted in UI layer, never in reusable modules - add external-filtering test to the plan surface Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bite-sized TDD tasks for the two-stage Gmail connector: package skeleton, metadata-only SQLite store, pure hybrid weighting (Stage 2), GmailConnector auth+sync (Stage 1), legacy-stub retirement, Connect Gmail UI, full-suite regression. Each task ships tests + a commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…standalone Eager imports in __init__ coupled all three submodules — importing gmail_store triggered imports of not-yet-created siblings, forcing throwaway stubs and making intermediate commits non-standalone. Resolve exports lazily via __getattr__ so each module imports independently and every commit stays green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…icit conn close, more tests Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…; doc fixes Guard half_life<=0 / beta<0 / window<0 (negative beta would inject negative flow weights, invalid in the Ulanowicz model). ImportError-only fallback. Correct 'ISO weeks' -> 7-day epoch windows in docstring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rsing Store-level INSERT OR IGNORE on message-edge identity so re-syncing overlapping windows no longer double-counts flows (would inflate Ulanowicz weights). Atomic client assignment in authenticate. RFC 2822 address parsing via email.utils. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ector Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rash) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full tests/ suite green: 321 passed (incl. ~22 new connector tests). Pre-existing validation/test_florida_bay.py collection error (relative-path script assuming CWD=validation/) is unrelated and out of scope. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ation, UI guard - gmail.metadata scope forbids the q search param (403); list SENT + filter internalDate client-side, preserving metadata-only. - per-user try/except so one bad mailbox doesn't abort the whole sync. - guard build_flow_matrix's NetworkIngestionError in the UI (empty/external-only window) with a friendly warning instead of a traceback. - replace deprecated datetime.utcnow() with datetime.now(timezone.utc). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A self-provisioning Gmail connector that lets a Google Workspace admin connect their org once and have OASIS build a weighted communication-flow network from message metadata, precompute it, and open it in the existing analysis view — no manual CSV export.
Base note: this PR is based on
base/pre-gmail-connector(the pre-connector state offeat/detailed-ecosystemic-report) rather thanmain, because the connector builds on the M2 self-service ingestion primitive (src/network_ingestion.py) which is not yet onmain. This keeps the diff connector-only for review.Architecture — two-stage (approach B)
GmailConnector.sync()(src/connectors/gmail_connector.py): admin OAuth via domain-wide delegation, least-privilegegmail.metadatascope, pulls From/To/Cc/timestamp/thread/size into a persisted, idempotentgmail_interactionsSQLite table. Metadata-only — never subject or body.build_flow_matrix()(src/connectors/gmail_weighting.py): a pure, clock-free transform — hybrid recency-decay × sustained-engagement weighting, external-address filtering, individual or department granularity — feeding the existingbuild_flow_matrix_from_edges → provision_network → get_full_profilepath.app.py):🔌 Connect Gmailmode (connect → sync → build → analyze).GoogleWorkspaceConnector.get_flow_datastub (it silently returned a zero matrix → now refuses loudly and points to the new connector).Privacy & scientific integrity
gmail.metadatascope +format='metadata'+ a schema with no subject/body columns (guarded by a test).β/half-life validated (ValueErroronβ<0,half_life<=0); no core Ulanowicz formula is touched.βand half-life are documented calibration parameters for network construction, not metric formulas.INSERT OR IGNOREon a message-edge UNIQUE index) so re-syncing overlapping windows can't double-count flows.now_utcis always passed in.Testing
tests/suite: 321 passed, zero regressions.Notes / follow-ups
.streamlit/secrets.toml; all logic is testable without it via injected fake clients.BaseConnector/ConnectorFactoryseams are reserved for them.🤖 Generated with Claude Code