feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)
Real IMAP connection via imapflow library: - Connects to IMAP server (IMAPFlow, TLS on port 993) - Fetches recent messages (configurable limit, default 20) - Extracts: from, fromEmail, subject, date, snippet (200 chars) - Thread-to-contact matching (PRD §6.1): 1. Match sender email against persons table 2. If no person match, extract domain and match against company.website 3. Returns matchedCompany + matchedPerson for each email - Audit-logged (action: crm.mailsync:imap) - Zod validated (host, port, user, password, folder, limit) - 502 on IMAP connection failure with detail - 400 on validation failure Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
This commit is contained in:
1 parent
7acc0c5451
commit
e45b68bb8b
1089 files changed
+260614
No files matched your search
@@ -0,0 +1,59 @@
|
||||
"""Generate fig_04_ablation_panels.pdf — 2x2 ablation panel figure."""
|
||||
import os
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from style_kit import PALETTE, add_simulated_watermark, apply_publication_style, save_publication_bundle
|
||||
|
||||
apply_publication_style()
|
||||
|
||||
PRIMARY = PALETTE["blue_main"]
|
||||
ABLATION = PALETTE["red_strong"]
|
||||
DATASETS = ["ETTm1", "Electricity", "Traffic", "Weather"]
|
||||
|
||||
horizons = np.array([96, 192, 336, 720])
|
||||
full = {
|
||||
"ETTm1": np.array([0.319, 0.346, 0.385, 0.460]),
|
||||
"Electricity": np.array([0.131, 0.142, 0.149, 0.171]),
|
||||
"Traffic": np.array([0.352, 0.378, 0.402, 0.451]),
|
||||
"Weather": np.array([0.179, 0.198, 0.220, 0.272]),
|
||||
}
|
||||
no_patch = {
|
||||
"ETTm1": np.array([0.354, 0.382, 0.422, 0.499]),
|
||||
"Electricity": np.array([0.143, 0.155, 0.164, 0.186]),
|
||||
"Traffic": np.array([0.378, 0.408, 0.434, 0.486]),
|
||||
"Weather": np.array([0.198, 0.221, 0.247, 0.301]),
|
||||
}
|
||||
|
||||
fig, axes = plt.subplots(2, 2, figsize=(6.6, 4.4), sharex=True)
|
||||
for ax, name in zip(axes.flat, DATASETS):
|
||||
ax.plot(horizons, full[name], marker="o", color=PRIMARY, label="Full")
|
||||
ax.plot(horizons, no_patch[name], marker="s", linestyle="--",
|
||||
color=ABLATION, label=r"$-$ patching")
|
||||
ax.set_title(name)
|
||||
ax.set_xscale("log", base=2)
|
||||
ax.set_xticks(horizons)
|
||||
ax.set_xticklabels([str(h) for h in horizons])
|
||||
ax.grid(True, alpha=0.25, linewidth=0.4)
|
||||
ax.spines["top"].set_visible(False)
|
||||
ax.spines["right"].set_visible(False)
|
||||
|
||||
for ax in axes[:, 0]:
|
||||
ax.set_ylabel("MSE")
|
||||
for ax in axes[-1, :]:
|
||||
ax.set_xlabel(r"Horizon $H$")
|
||||
|
||||
handles, labels = axes[0, 0].get_legend_handles_labels()
|
||||
fig.legend(handles, labels, loc="upper center", bbox_to_anchor=(0.5, 1.02),
|
||||
ncol=2, frameon=False)
|
||||
|
||||
for ax, label in zip(axes.flat, "abcd"):
|
||||
ax.text(-0.15, 1.05, f"({label})", transform=ax.transAxes,
|
||||
fontweight="bold", fontsize=10)
|
||||
|
||||
add_simulated_watermark(fig.axes[-1], y=-0.22)
|
||||
|
||||
plt.tight_layout(rect=(0.0, 0.01, 1.0, 0.95))
|
||||
out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fig_04_ablation_panels")
|
||||
save_publication_bundle(fig, out)
|
||||
plt.close(fig)
|
||||
print(f"Wrote {out}.pdf/.svg/.tiff")
|
||||
Reference in new issue
Block a user