email: per-mailbox captured/matched counts on Email Capture (v0.1.0:65)
/api/email/accounts now returns captured + matched per account (from the per-mailbox sighting table email_account_messages joined to emails; emails dedupe globally so an email seen by two mailboxes counts for each). Each mailbox card on the Email Capture page shows "<N> captured · <M> matched" so per-user coverage is visible, not just the aggregate. Verified in preview with two seeded mailboxes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -10197,6 +10197,7 @@
|
||||
<div key={a.id} className="kpi-card">
|
||||
<div className="kpi-label">{a.email_address}</div>
|
||||
<div className="kpi-value" style={{ fontSize: '15px' }}>{a.sync_status || 'pending'}{a.backfill_complete ? '' : ' · backfilling'}</div>
|
||||
<div className="kpi-subtitle">{(a.captured ?? 0)} captured · {(a.matched ?? 0)} matched</div>
|
||||
<div className="kpi-subtitle">{a.last_synced_at ? `last ${formatDate(a.last_synced_at)}` : 'never synced'}{a.sync_error ? ` · ${a.sync_error}` : ''}</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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 () => {} },
|
||||
})
|
||||
Reference in New Issue
Block a user