diff --git a/backend/email_integration/routes.py b/backend/email_integration/routes.py index e1e6d22..bba89e1 100644 --- a/backend/email_integration/routes.py +++ b/backend/email_integration/routes.py @@ -162,6 +162,22 @@ def _h_list_accounts(handler): "FROM email_accounts ORDER BY email_address" ) rows = [dict(r) for r in cur.fetchall()] + # Per-mailbox counts: emails are de-duplicated globally, so "captured per + # mailbox" comes from the per-account sighting table; "matched" joins to emails. + captured, matched = {}, {} + try: + captured = {r["account_id"]: r["n"] for r in cur.execute( + "SELECT account_id, COUNT(*) AS n FROM email_account_messages " + "WHERE deleted_at IS NULL GROUP BY account_id")} + matched = {r["account_id"]: r["n"] for r in cur.execute( + "SELECT eam.account_id AS account_id, COUNT(*) AS n FROM email_account_messages eam " + "JOIN emails e ON e.id = eam.email_id " + "WHERE eam.deleted_at IS NULL AND e.is_matched = 1 GROUP BY eam.account_id")} + except sqlite3.OperationalError: + pass + for r in rows: + r["captured"] = captured.get(r["id"], 0) + r["matched"] = matched.get(r["id"], 0) finally: conn.close() # Non-admins only see their own row diff --git a/frontend/index.html b/frontend/index.html index 8063de1..c4e41b9 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -10197,6 +10197,7 @@
{a.email_address}
{a.sync_status || 'pending'}{a.backfill_complete ? '' : ' · backfilling'}
+
{(a.captured ?? 0)} captured · {(a.matched ?? 0)} matched
{a.last_synced_at ? `last ${formatDate(a.last_synced_at)}` : 'never synced'}{a.sync_error ? ` · ${a.sync_error}` : ''}
))} diff --git a/start9/0.4/startos/utils.ts b/start9/0.4/startos/utils.ts index 28b062e..3130be4 100644 --- a/start9/0.4/startos/utils.ts +++ b/start9/0.4/startos/utils.ts @@ -29,8 +29,9 @@ export const PACKAGE_TITLE = 'Ten31 Database' // * 0.1.0:61 (Email Capture: live backfill progress + auto-refresh) // * 0.1.0:62 (fix backfill crash on no-Reply-To emails; Sync now retries errored mailboxes) // * 0.1.0:63 (System Status: storage usage — DB, attachments, backups, disk free) -// * Current: 0.1.0:64 (email-activity agent: propose->review->approve grid notes; sync ~15 min) -export const PACKAGE_VERSION = '0.1.0:64' +// * 0.1.0:64 (email-activity agent: propose->review->approve grid notes; sync ~15 min) +// * Current: 0.1.0:65 (Email Capture: per-mailbox captured/matched counts) +export const PACKAGE_VERSION = '0.1.0:65' export const DATA_MOUNT_PATH = '/data' export const WEB_PORT = 8080 diff --git a/start9/0.4/startos/versions/index.ts b/start9/0.4/startos/versions/index.ts index 97cf82d..d51faa9 100644 --- a/start9/0.4/startos/versions/index.ts +++ b/start9/0.4/startos/versions/index.ts @@ -25,8 +25,9 @@ import { v_0_1_0_61 } from './v0.1.0.61' import { v_0_1_0_62 } from './v0.1.0.62' import { v_0_1_0_63 } from './v0.1.0.63' import { v_0_1_0_64 } from './v0.1.0.64' +import { v_0_1_0_65 } from './v0.1.0.65' export const versionGraph = VersionGraph.of({ - current: v_0_1_0_64, - other: [v_0_1_0_39, v_0_1_0_40, v_0_1_0_41, v_0_1_0_42, v_0_1_0_43, v_0_1_0_44, v_0_1_0_45, v_0_1_0_46, v_0_1_0_47, v_0_1_0_48, v_0_1_0_49, v_0_1_0_50, v_0_1_0_51, v_0_1_0_52, v_0_1_0_53, v_0_1_0_54, v_0_1_0_55, v_0_1_0_56, v_0_1_0_57, v_0_1_0_58, v_0_1_0_59, v_0_1_0_60, v_0_1_0_61, v_0_1_0_62, v_0_1_0_63], + current: v_0_1_0_65, + other: [v_0_1_0_39, v_0_1_0_40, v_0_1_0_41, v_0_1_0_42, v_0_1_0_43, v_0_1_0_44, v_0_1_0_45, v_0_1_0_46, v_0_1_0_47, v_0_1_0_48, v_0_1_0_49, v_0_1_0_50, v_0_1_0_51, v_0_1_0_52, v_0_1_0_53, v_0_1_0_54, v_0_1_0_55, v_0_1_0_56, v_0_1_0_57, v_0_1_0_58, v_0_1_0_59, v_0_1_0_60, v_0_1_0_61, v_0_1_0_62, v_0_1_0_63, v_0_1_0_64], }) diff --git a/start9/0.4/startos/versions/v0.1.0.65.ts b/start9/0.4/startos/versions/v0.1.0.65.ts new file mode 100644 index 0000000..9250741 --- /dev/null +++ b/start9/0.4/startos/versions/v0.1.0.65.ts @@ -0,0 +1,16 @@ +import { VersionInfo } from '@start9labs/start-sdk' + +// Email Capture: per-mailbox counts. Each enrolled mailbox card now shows how many +// emails that mailbox captured and how many matched to investors (from the per-account +// sighting table; emails are de-duplicated globally, so an email seen by two mailboxes +// counts for each). Read-only. No schema migration. +export const v_0_1_0_65 = VersionInfo.of({ + version: '0.1.0:65', + releaseNotes: { + en_US: [ + 'Email Capture now shows captured and matched-to-investor counts per Ten31 mailbox,', + 'so you can see each user’s email coverage, not just the totals.', + ].join(' '), + }, + migrations: { up: async () => {}, down: async () => {} }, +})