Add v2 rewrite: monorepo with desktop and web apps, shared packages, docs, and wireframes

This commit is contained in:
2026-05-14 13:13:21 -04:00
parent 6d7eed9230
commit 379c07b50c
49 changed files with 11607 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
{
"name": "@mosaiciq/shared",
"private": true,
"type": "module",
"exports": {
"./demoData": {
"types": "./src/demoData.ts",
"import": "./src/demoData.ts"
},
"./extraData": {
"types": "./src/extraData.ts",
"import": "./src/extraData.ts"
},
"./mockRpc": {
"types": "./src/mockRpc.ts",
"import": "./src/mockRpc.ts"
}
}
}

View File

@@ -0,0 +1,162 @@
import type { Agent, Company, Holding, MemoAnnotation, MemoCitation, MemoSection, MemoSectionReview, ModelRow } from "../../contracts/src/rpc.js";
export const activeCompanyId = "cost";
export const holdings: Holding[] = [
{ ticker: "COST", name: "Costco Wholesale Corp", price: 921.4, changePct: 1.2, weight: 32 },
{ ticker: "AMZN", name: "Amazon.com Inc", price: 186.5, changePct: 0.8, weight: 28 },
{ ticker: "WMT", name: "Walmart Inc", price: 168.3, changePct: -0.3, weight: 22 },
{ ticker: "TGT", name: "Target Corp", price: 142.8, changePct: -1.1, weight: 18 }
];
export const companies: Record<string, Company> = {
cost: {
id: "cost",
ticker: "COST",
name: "Costco Wholesale Corporation",
sector: "Consumer Staples",
subIndustry: "Membership Warehouse Clubs",
price: 921.4,
changePct: 1.2,
thesis: "Membership renewal durability and traffic resilience support premium multiple, while fuel normalization and wage pressure remain the key model sensitivities.",
founded: "1983",
headquarters: "Issaquah, WA",
employees: 320000
},
amzn: {
id: "amzn",
ticker: "AMZN",
name: "Amazon.com Inc",
sector: "Consumer Discretionary",
subIndustry: "Internet Retail",
price: 186.5,
changePct: 0.8,
thesis: "AWS re-acceleration and advertising monetization drive margin expansion, while retail competition and regulatory risk remain.",
founded: "1994",
headquarters: "Seattle, WA",
employees: 1540000
},
wmt: {
id: "wmt",
ticker: "WMT",
name: "Walmart Inc",
sector: "Consumer Staples",
subIndustry: "Hypermarkets",
price: 168.3,
changePct: -0.3,
thesis: "Grocery share gains and e-commerce profitability inflection offset margin pressure from price investment strategy.",
founded: "1962",
headquarters: "Bentonville, AR",
employees: 2100000
},
tgt: {
id: "tgt",
ticker: "TGT",
name: "Target Corp",
sector: "Consumer Discretionary",
subIndustry: "General Merchandise Stores",
price: 142.8,
changePct: -1.1,
thesis: "Traffic recovery and margin normalization potential, but discretionary headwinds and inventory risk weigh on near-term.",
founded: "1902",
headquarters: "Minneapolis, MN",
employees: 415000
}
};
export const agents: Agent[] = [
{ id: "sf", name: "SEC Filings Agent", status: "running", progress: 45, action: "Extracting segment data from 10-K", pipeline: "research" },
{ id: "fm", name: "Financial Modeling Agent", status: "running", progress: 62, action: "Building revenue schedule", pipeline: "research" },
{ id: "ec", name: "Earnings Call Agent", status: "running", progress: 78, action: "Summarizing Q2 FY25 call", pipeline: "competitive" },
{ id: "cr", name: "Company Research Agent", status: "completed", progress: 100, action: "Company overview complete", pipeline: "research" },
{ id: "va", name: "Valuation Agent", status: "queued", progress: 0, action: "Waiting for model outputs", pipeline: "research" },
{ id: "ci", name: "Competitive Intel Agent", status: "queued", progress: 0, action: "Waiting for earnings analysis", pipeline: "competitive" },
{ id: "rk", name: "Risk Agent", status: "idle", progress: 0, action: "Risk analysis pending", pipeline: "competitive" },
{ id: "mw", name: "Memo Writing Agent", status: "idle", progress: 0, action: "Memo drafting pending", pipeline: "research" },
{ id: "pa", name: "Presentation Agent", status: "idle", progress: 0, action: "Presentation pending", pipeline: "research" },
{ id: "mn", name: "Monitoring Agent", status: "idle", progress: 0, action: "Monitoring pipeline idle", pipeline: "cross-cutting" },
{ id: "sv", name: "Source Verification Agent", status: "idle", progress: 0, action: "Verification pending", pipeline: "cross-cutting" },
{ id: "rt", name: "Red Team Agent", status: "idle", progress: 0, action: "Adversarial review pending", pipeline: "competitive" },
{ id: "ex", name: "Export Agent", status: "idle", progress: 0, action: "Export pipeline idle", pipeline: "cross-cutting" },
{ id: "qa", name: "Model QA Agent", status: "idle", progress: 0, action: "QA review pending", pipeline: "cross-cutting" }
];
export const modelHeaders = ["FY2022A", "FY2023A", "FY2024A", "FY2025E", "FY2026E"];
export const modelRows: ModelRow[] = [
{ label: "Revenue", kind: "actual", values: ["$226.9B", "$242.3B", "$254.5B", "$270.4B", "$286.1B"] },
{ label: "Gross Margin", kind: "actual", values: ["12.1%", "12.3%", "12.6%", "12.7%", "12.8%"] },
{ label: "Operating Income", kind: "forecast", values: ["$7.8B", "$8.1B", "$9.3B", "$10.1B", "$10.9B"] },
{ label: "EPS", kind: "forecast", values: ["$13.14", "$14.16", "$16.56", "$18.12", "$19.84"] }
];
export const memoSections: MemoSection[] = [
{
id: "thesis",
title: "Investment Thesis",
content: "Costco remains a high-quality compounder with unusually durable traffic, renewal, and private-label economics. The core thesis is that membership fee income, disciplined SKU curation, and steady warehouse productivity can support high-single-digit earnings growth over a multi-year horizon, even as the current valuation requires disciplined sensitivity work around renewal fees, wage inflation, and merchandise margin.",
primaryAgent: "mw"
},
{
id: "drivers",
title: "Key Drivers",
content: "The model is most sensitive to membership fee cadence, comparable sales excluding fuel, and operating leverage across warehouse labor and logistics. A 50 bps change in core merchandise margin or a one-year shift in fee timing drives a disproportionate share of the bear-to-bull spread.",
primaryAgent: "mw"
},
{
id: "variant",
title: "Variant Perception",
content: "Consensus treats Costco as a fully discovered quality compounder, but underweights the durability of traffic share gains in grocery and consumables. The variant view is that renewal behavior and executive member penetration create more operating resilience than the market is giving credit for during a slower discretionary cycle.",
primaryAgent: "mw"
},
{
id: "valuation",
title: "Valuation",
content: "The base case triangulates a premium earnings multiple, a DCF anchored on low-teens discount-rate sensitivity, and peer multiples against scaled staples and retail platforms. The current share price embeds limited margin for execution misses, so valuation work should frame upside through fee timing and downside through wage and shrink pressure.",
primaryAgent: "va"
},
{
id: "quality",
title: "Business Quality",
content: "Costco's moat is built on purchasing scale, a low-markup operating philosophy, a limited-SKU model, and recurring membership economics. ROIC remains supported by high inventory turns and negative working capital dynamics, while management quality is reflected in disciplined capital allocation and consistent reinvestment in member value.",
primaryAgent: "cr"
},
{
id: "financials",
title: "Financial Summary",
content: "Revenue growth is expected to track warehouse expansion, comparable sales excluding fuel, and modest e-commerce contribution. Margin analysis should separate merchandise gross margin, membership fee income, wage inflation, logistics costs, and fuel volatility so the model does not overstate operating leverage.",
primaryAgent: "fm"
},
{
id: "risks",
title: "Risks & Mitigants",
content: "Key risks include valuation compression, delayed fee increases, labor cost inflation, weaker discretionary categories, and international execution risk. Mitigants include renewal-rate stability, grocery-led traffic, balance-sheet flexibility, and management's demonstrated willingness to protect the member value proposition through cycles.",
primaryAgent: "rk"
},
{
id: "catalysts",
title: "Catalysts",
content: "Near-term catalysts include membership fee announcements, monthly sales reports, executive member penetration updates, new warehouse openings, and quarterly commentary on traffic versus ticket. A clean fee-increase signal with stable renewal metrics would likely be the most important rerating event.",
primaryAgent: "mn"
}
];
export const memoCitations: MemoCitation[] = [
{ id: "citation-10k-membership", label: "[1]", sectionId: "thesis", type: "sec_filing", title: "FY2024 10-K - Membership economics", reference: "Annual report discussion of membership fee income and renewal rates", verificationStatus: "verified", sourceUrl: "https://investor.costco.com" },
{ id: "citation-call-executive", label: "[2]", sectionId: "drivers", type: "earnings_transcript", title: "Q2 FY2025 earnings call - Executive tier metrics", reference: "Management commentary on executive member penetration and traffic", verificationStatus: "verified" },
{ id: "citation-consensus-margin", label: "[3]", sectionId: "variant", type: "analyst_report", title: "Consensus margin sensitivity note", reference: "External analyst framing of merchandise margin risk", verificationStatus: "unverified" },
{ id: "citation-dcf-model", label: "[4]", sectionId: "valuation", type: "model", title: "Internal DCF sensitivity model", reference: "Discount-rate and terminal multiple sensitivity output", verificationStatus: "flagged" },
{ id: "citation-risk-note", label: "[5]", sectionId: "risks", type: "internal_note", title: "Risk register - Labor and fee timing", reference: "Internal notes from source verification pass", verificationStatus: "verified" }
];
export const memoAnnotations: MemoAnnotation[] = [
{ id: "annotation-thesis-target", sectionId: "thesis", kind: "comment", selectedText: "current valuation requires disciplined sensitivity work", comment: "Quantify target price range before IC circulation.", createdBy: "JD", createdAt: "2026-05-09T14:30:00.000Z", status: "open" },
{ id: "annotation-drivers-fee", sectionId: "drivers", kind: "comment", selectedText: "membership fee cadence", comment: "Tie fee cadence to the model sensitivity table.", createdBy: "MW", createdAt: "2026-05-09T15:10:00.000Z", status: "open" },
{ id: "annotation-valuation-dcf", sectionId: "valuation", kind: "highlight", selectedText: "low-teens discount-rate sensitivity", createdBy: "SV", createdAt: "2026-05-09T16:05:00.000Z", status: "open" }
];
export const memoSectionReviews: MemoSectionReview[] = memoSections.map((section) => ({
sectionId: section.id,
status: section.id === "thesis" ? "approved" : section.id === "drivers" || section.id === "variant" ? "in_review" : section.id === "valuation" ? "changes_requested" : "pending",
updatedAt: "2026-05-09T13:00:00.000Z"
}));

View File

@@ -0,0 +1,44 @@
import type { Alert, Catalyst, EarningsSchedule, ExportRecord, Filing, Risk } from "../../contracts/src/rpc.js";
export const demoCatalysts: Catalyst[] = [
{ id: "cat-1", date: "2026-06-05", event: "Q3 FY25 Earnings", impact: "high", thesisRelevance: "supports", source: "[4]" },
{ id: "cat-2", date: "2026-07-15", event: "Executive member update", impact: "medium", thesisRelevance: "supports", source: "[2]" },
{ id: "cat-3", date: "2026-08-01", event: "Annual membership fee review", impact: "high", thesisRelevance: "neutral", source: "[1]" },
{ id: "cat-4", date: "2026-09-10", event: "New warehouse openings (Q4)", impact: "low", thesisRelevance: "supports", source: "[1]" }
];
export const demoAlerts: Alert[] = [
{ id: "alert-1", companyId: "cost", timestamp: "2026-05-12T08:30:00Z", type: "earnings_surprise", description: "Q2 FY25 earnings beat (+7.5% comp)", thesisImpact: "positive", status: "new", targetSection: "thesis" },
{ id: "alert-2", companyId: "cost", timestamp: "2026-05-12T06:00:00Z", type: "filing", description: "New 8-K: Executive compensation update", thesisImpact: "neutral", status: "new" },
{ id: "alert-3", companyId: "wmt", timestamp: "2026-05-11T14:00:00Z", type: "peer_event", description: "WMT announces price investment in grocery", thesisImpact: "negative", status: "reviewed" },
{ id: "alert-4", companyId: "cost", timestamp: "2026-05-11T10:00:00Z", type: "price_move", description: "COST +2.1% on heavy volume", thesisImpact: "positive", status: "reviewed" }
];
export const demoRisks: Risk[] = [
{ id: "risk-1", companyId: "cost", risk: "Amazon enters warehouse club segment", category: "competitive", severity: "high", likelihood: "low", mitigation: "Costco's 93% renewal rate creates switching costs", status: "open" },
{ id: "risk-2", companyId: "cost", risk: "Wage inflation compresses operating margin", category: "financial", severity: "medium", likelihood: "high", mitigation: "Automation and productivity offset 40-60% of wage pressure", status: "open" },
{ id: "risk-3", companyId: "cost", risk: "Membership fee increase delayed beyond FY26", category: "financial", severity: "medium", likelihood: "medium", mitigation: "Fee income growth from executive tier conversion", status: "mitigated" },
{ id: "risk-4", companyId: "cost", risk: "Regulatory pressure on merchandise sourcing", category: "regulatory", severity: "low", likelihood: "low", mitigation: "Diversified supply chain across 14 countries", status: "accepted" },
{ id: "risk-5", companyId: "cost", risk: "E-commerce disruption of warehouse model", category: "competitive", severity: "medium", likelihood: "medium", mitigation: "Costco.com growth and Instacart partnership", status: "open" },
{ id: "risk-6", companyId: "cost", risk: "Valuation compression on growth deceleration", category: "financial", severity: "high", likelihood: "medium", mitigation: "High-single-digit earnings growth supports premium", status: "open" }
];
export const demoEarnings: EarningsSchedule[] = [
{ id: "earn-1", companyId: "cost", quarter: "Q3 FY25", expectedDate: "2026-06-05", timing: "bmo" },
{ id: "earn-2", companyId: "cost", quarter: "Q4 FY25", expectedDate: "2026-09-25", timing: "bmo" },
{ id: "earn-3", companyId: "cost", quarter: "Q2 FY25", expectedDate: "2026-03-06", timing: "bmo", actualRevenue: "$62.5B", expectedRevenue: "$61.2B", actualEps: "$4.28", expectedEps: "$4.05" },
{ id: "earn-4", companyId: "cost", quarter: "Q1 FY25", expectedDate: "2025-12-12", timing: "bmo", actualRevenue: "$60.2B", expectedRevenue: "$59.8B", actualEps: "$4.04", expectedEps: "$3.98" }
];
export const demoFilings: Filing[] = [
{ id: "filing-1", companyId: "cost", formType: "10-K", filedDate: "2024-10-10", title: "Annual Report FY2024", keyChanges: "Updated segment reporting, new warehouse commitments ($2.1B)", reviewed: true },
{ id: "filing-2", companyId: "cost", formType: "10-Q", filedDate: "2026-03-10", title: "Quarterly Report Q2 FY25", keyChanges: "Membership fee income growth, margin expansion", reviewed: true },
{ id: "filing-3", companyId: "cost", formType: "8-K", filedDate: "2026-05-12", title: "Executive Compensation Update", keyChanges: "New CEO compensation structure", reviewed: false }
];
export const demoExports: ExportRecord[] = [
{ id: "export-1", type: "excel", title: "COST Model — Revenue Build FY25-FY27", companyId: "cost", format: "Excel", fileSize: "2.4 MB", status: "complete", createdAt: "2026-05-10T14:30:00Z" },
{ id: "export-2", type: "pdf", title: "COST Investment Memo — Draft", companyId: "cost", format: "PDF", fileSize: "1.8 MB", status: "complete", createdAt: "2026-05-09T16:00:00Z" },
{ id: "export-3", type: "ppt", title: "COST IC Presentation", companyId: "cost", format: "PowerPoint", fileSize: "4.2 MB", status: "processing", createdAt: "2026-05-12T09:00:00Z" },
{ id: "export-4", type: "pdf", title: "Peer Comparison Report", companyId: "cost", format: "PDF", fileSize: "980 KB", status: "complete", createdAt: "2026-05-08T11:00:00Z" }
];

View File

@@ -0,0 +1,242 @@
import type { MemoAnnotation, MemoCitation, MemoSection, MemoSectionReview, RpcMethod, RpcRequestMap, RpcResponseMap, RpcResult } from "../../contracts/src/rpc.js";
import { activeCompanyId, agents, companies, holdings, memoAnnotations, memoCitations, memoSectionReviews, memoSections, modelHeaders, modelRows } from "./demoData.js";
import { demoAlerts, demoCatalysts, demoEarnings, demoExports, demoFilings, demoRisks } from "./extraData.js";
type MemoStoreEntry = {
status: "draft" | "review" | "final";
sections: MemoSection[];
citations: MemoCitation[];
annotations: MemoAnnotation[];
sectionReviews: MemoSectionReview[];
};
const memoStore = new Map<string, MemoStoreEntry>();
export async function handleMockRpc<T extends RpcMethod>(
method: T,
payload: RpcRequestMap[T]
): Promise<RpcResult<T>> {
try {
switch (method) {
case "portfolio.get":
return ok("portfolio.get", { id: "core", name: "Core Retail Coverage", holdings, activeCompanyId }) as RpcResult<T>;
case "portfolio.addHolding": {
const { ticker } = payload as RpcRequestMap["portfolio.addHolding"];
const company = Object.values(companies).find((c) => c.ticker === ticker.toUpperCase());
if (!company) return fail("NOT_FOUND", `Company with ticker "${ticker}" not found.`) as RpcResult<T>;
const holding = { ticker: company.ticker, name: company.name, price: company.price, changePct: company.changePct, weight: 5 };
return ok("portfolio.addHolding", { holding }) as RpcResult<T>;
}
case "portfolio.removeHolding":
return ok("portfolio.removeHolding", { ok: true }) as RpcResult<T>;
case "company.get": {
const { companyId } = payload as RpcRequestMap["company.get"];
const company = companies[companyId];
if (!company) return fail("NOT_FOUND", `Company "${companyId}" was not found.`) as RpcResult<T>;
return ok("company.get", { company }) as RpcResult<T>;
}
case "company.search": {
const { query } = payload as RpcRequestMap["company.search"];
const q = query.toLowerCase();
const results = Object.values(companies).filter((c) => c.ticker.toLowerCase().includes(q) || c.name.toLowerCase().includes(q) || c.sector.toLowerCase().includes(q)).map((c) => ({ ticker: c.ticker, name: c.name, sector: c.sector }));
return ok("company.search", { results }) as RpcResult<T>;
}
case "company.setActive":
return ok("company.setActive", { ok: true }) as RpcResult<T>;
case "workspace.getSection": {
const { companyId, section } = payload as RpcRequestMap["workspace.getSection"];
if (!companies[companyId]) return fail("NOT_FOUND", `Company "${companyId}" not found.`) as RpcResult<T>;
return ok("workspace.getSection", { content: { id: section, title: section, content: `Content for ${section}`, validationState: "verified" as const, sourceAgent: "cr" }, validationState: "verified" }) as RpcResult<T>;
}
case "workspace.listSources": {
const { companyId } = payload as RpcRequestMap["workspace.listSources"];
if (!companies[companyId]) return fail("NOT_FOUND", `Company "${companyId}" not found.`) as RpcResult<T>;
return ok("workspace.listSources", { sources: [{ type: "SEC Filing", title: "10-K FY2024", metadata: "Filed Oct 2024" }, { type: "Earnings Transcript", title: "Q2 FY25 Call", metadata: "Mar 2025" }] }) as RpcResult<T>;
}
case "catalyst.list":
return ok("catalyst.list", { catalysts: demoCatalysts }) as RpcResult<T>;
case "alert.list":
return ok("alert.list", { alerts: demoAlerts }) as RpcResult<T>;
case "risk.list":
return ok("risk.list", { risks: demoRisks }) as RpcResult<T>;
case "risk.add": {
const { companyId, risk } = payload as RpcRequestMap["risk.add"];
const newRisk = { ...risk, id: `risk-${Date.now()}`, companyId };
return ok("risk.add", { risk: newRisk }) as RpcResult<T>;
}
case "earnings.getSchedule":
return ok("earnings.getSchedule", { schedule: demoEarnings }) as RpcResult<T>;
case "filing.list":
return ok("filing.list", { filings: demoFilings }) as RpcResult<T>;
case "agent.list":
return ok("agent.list", { agents }) as RpcResult<T>;
case "agent.start":
return ok("agent.start", { runId: `run-${Date.now()}` }) as RpcResult<T>;
case "agent.pause":
return ok("agent.pause", { ok: true }) as RpcResult<T>;
case "agent.restart":
return ok("agent.restart", { runId: `run-${Date.now()}` }) as RpcResult<T>;
case "agent.chat":
return ok("agent.chat", { response: "I've analyzed the data. Key findings: revenue growth remains on track with 5.6% YoY. The main assumption driving the model is membership fee cadence." }) as RpcResult<T>;
case "agent.configure":
return ok("agent.configure", { ok: true }) as RpcResult<T>;
case "agent.getTrace":
return ok("agent.getTrace", { steps: [{ step: 1, label: "Load filings", detail: "Loaded 10-K and 3 quarterly reports" }, { step: 2, label: "Extract segments", detail: "Parsed 5 revenue segments" }, { step: 3, label: "Build model", detail: "Constructed revenue build with growth rates" }] }) as RpcResult<T>;
case "agent.runPipeline":
return ok("agent.runPipeline", { runIds: [`run-${Date.now()}`, `run-${Date.now() + 1}`, `run-${Date.now() + 2}`] }) as RpcResult<T>;
case "model.get":
return ok("model.get", { headers: modelHeaders, rows: modelRows }) as RpcResult<T>;
case "model.updateCell":
return ok("model.updateCell", { ok: true, affectedCells: [] }) as RpcResult<T>;
case "model.runScenario":
return ok("model.runScenario", { headers: [...modelHeaders], rows: modelRows.map((r) => ({ ...r })) }) as RpcResult<T>;
case "memo.get": {
const { companyId } = payload as RpcRequestMap["memo.get"];
const memo = getCompanyMemo(companyId);
if (!memo) return fail("NOT_FOUND", `Company "${companyId}" was not found.`) as RpcResult<T>;
return ok("memo.get", cloneMemo(memo)) as RpcResult<T>;
}
case "memo.updateSection": {
const { companyId, sectionId, title, content } = payload as RpcRequestMap["memo.updateSection"];
const memo = getCompanyMemo(companyId);
if (!memo) return fail("NOT_FOUND", `Company "${companyId}" not found.`) as RpcResult<T>;
if (content.trim().length === 0) return fail("VALIDATION_ERROR", "Memo section content cannot be empty.") as RpcResult<T>;
if (title !== undefined && title.trim().length === 0) return fail("VALIDATION_ERROR", "Memo section title cannot be empty.") as RpcResult<T>;
const idx = memo.sections.findIndex((s) => s.id === sectionId);
if (idx === -1) return fail("NOT_FOUND", `Section "${sectionId}" not found.`) as RpcResult<T>;
const savedAt = new Date().toISOString();
const section = { ...memo.sections[idx], ...(title === undefined ? {} : { title }), content, updatedAt: savedAt };
memo.sections[idx] = section;
return ok("memo.updateSection", { section: { ...section }, status: memo.status, savedAt }) as RpcResult<T>;
}
case "memo.addAnnotation": {
const { companyId, sectionId, kind, selectedText, comment } = payload as RpcRequestMap["memo.addAnnotation"];
const memo = getCompanyMemo(companyId);
if (!memo) return fail("NOT_FOUND", `Company "${companyId}" not found.`) as RpcResult<T>;
if (!memo.sections.some((s) => s.id === sectionId)) return fail("NOT_FOUND", `Section "${sectionId}" not found.`) as RpcResult<T>;
if (selectedText.trim().length === 0) return fail("VALIDATION_ERROR", "Annotation selected text cannot be empty.") as RpcResult<T>;
if (kind === "comment" && (!comment || comment.trim().length === 0)) return fail("VALIDATION_ERROR", "Comment annotation requires comment text.") as RpcResult<T>;
const annotation: MemoAnnotation = { id: `annotation-${Date.now()}`, sectionId, kind, selectedText: selectedText.trim(), ...(comment === undefined ? {} : { comment: comment.trim() }), createdBy: "JD", createdAt: new Date().toISOString(), status: "open" };
memo.annotations.unshift(annotation);
return ok("memo.addAnnotation", { annotation: { ...annotation } }) as RpcResult<T>;
}
case "memo.resolveAnnotation": {
const { companyId, annotationId } = payload as RpcRequestMap["memo.resolveAnnotation"];
const memo = getCompanyMemo(companyId);
if (!memo) return fail("NOT_FOUND", `Company "${companyId}" not found.`) as RpcResult<T>;
const ai = memo.annotations.findIndex((a) => a.id === annotationId);
if (ai === -1) return fail("NOT_FOUND", `Annotation "${annotationId}" not found.`) as RpcResult<T>;
const annotation = { ...memo.annotations[ai], status: "resolved" as const };
memo.annotations[ai] = annotation;
return ok("memo.resolveAnnotation", { annotation: { ...annotation } }) as RpcResult<T>;
}
case "memo.updateSectionReview": {
const { companyId, sectionId, status } = payload as RpcRequestMap["memo.updateSectionReview"];
const memo = getCompanyMemo(companyId);
if (!memo) return fail("NOT_FOUND", `Company "${companyId}" not found.`) as RpcResult<T>;
if (!memo.sections.some((s) => s.id === sectionId)) return fail("NOT_FOUND", `Section "${sectionId}" not found.`) as RpcResult<T>;
const updatedAt = new Date().toISOString();
const review = { sectionId, status, updatedAt };
const ri = memo.sectionReviews.findIndex((r) => r.sectionId === sectionId);
if (ri === -1) memo.sectionReviews.push(review);
else memo.sectionReviews[ri] = review;
return ok("memo.updateSectionReview", { review: { ...review } }) as RpcResult<T>;
}
case "memo.acceptEdit":
return ok("memo.acceptEdit", { ok: true }) as RpcResult<T>;
case "memo.rejectEdit":
return ok("memo.rejectEdit", { ok: true }) as RpcResult<T>;
case "export.list":
return ok("export.list", { exports: demoExports }) as RpcResult<T>;
case "export.create":
return ok("export.create", { exportId: `export-${Date.now()}` }) as RpcResult<T>;
case "export.download":
return ok("export.download", { data: new ArrayBuffer(0) }) as RpcResult<T>;
case "settings.get": {
const { scope } = payload as RpcRequestMap["settings.get"];
if (scope === "client") return ok("settings.get", { settings: { theme: "light" as const, density: "comfortable" as const, sidebarWidth: 240, navCollapsed: {}, keybindings: {} } }) as RpcResult<T>;
return ok("settings.get", { settings: { agentConfigs: {}, dataSources: { sec_filings: true, transcripts: true, market_data: false, analyst_reports: false, press_releases: true }, exportPipelines: {} } }) as RpcResult<T>;
}
case "settings.update":
return ok("settings.update", { ok: true }) as RpcResult<T>;
default:
return fail("NOT_FOUND", `Unknown RPC method: ${String(method)}`) as RpcResult<T>;
}
} catch (error) {
return fail("INTERNAL_ERROR", "Unhandled mock RPC failure.", error) as RpcResult<T>;
}
}
function ok<T extends RpcMethod>(_: T, data: RpcResponseMap[T]): RpcResult<T> {
return { ok: true, data };
}
function getCompanyMemo(companyId: string): MemoStoreEntry | null {
if (!companies[companyId]) return null;
const stored = memoStore.get(companyId);
if (stored) return stored;
const memo = {
status: "draft" as const,
sections: memoSections.map((s) => ({ ...s })),
citations: memoCitations.map((c) => ({ ...c })),
annotations: memoAnnotations.map((a) => ({ ...a })),
sectionReviews: memoSectionReviews.map((r) => ({ ...r }))
};
memoStore.set(companyId, memo);
return memo;
}
function cloneMemo(memo: MemoStoreEntry): RpcResponseMap["memo.get"] {
return {
status: memo.status,
sections: memo.sections.map((s) => ({ ...s })),
citations: memo.citations.map((c) => ({ ...c })),
annotations: memo.annotations.map((a) => ({ ...a })),
sectionReviews: memo.sectionReviews.map((r) => ({ ...r }))
};
}
function fail<T extends RpcMethod>(
code: "NOT_FOUND" | "VALIDATION_ERROR" | "INTERNAL_ERROR" | "AGENT_FAILED" | "CONFLICT" | "RATE_LIMITED",
message: string,
detail?: unknown
): RpcResult<T> {
return { ok: false, error: { code, message, detail } };
}