alphacouncil-agent 0.5.5 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "AlphaCouncil plugins for public-equity research.",
9
- "version": "0.5.5"
9
+ "version": "0.6.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "alphacouncil-agent",
14
14
  "source": "./",
15
15
  "description": "Multi-agent equity research: evidence packets, bull/bear debate, portfolio-manager decision.",
16
- "version": "0.5.5",
16
+ "version": "0.6.0",
17
17
  "author": {
18
18
  "name": "Zhao73"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alphacouncil-agent",
3
- "version": "0.5.5",
3
+ "version": "0.6.0",
4
4
  "description": "Multi-agent public-equity research workflow for Claude Code: spawns analyst workers, gathers sourced JSON evidence packets, runs bull/bear debate, and produces a portfolio-manager Buy/Overweight/Hold/Underweight/Sell decision with a full report.",
5
5
  "author": {
6
6
  "name": "Zhao73"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alphacouncil-agent",
3
- "version": "0.5.5",
3
+ "version": "0.6.0",
4
4
  "description": "Host-visible or background public-equity research workflow with shared evidence packets.",
5
5
  "author": {
6
6
  "name": "Zhao73"
package/CHANGELOG.md CHANGED
@@ -2,6 +2,37 @@
2
2
 
3
3
  Notable changes per release. Dates are UTC.
4
4
 
5
+ ## [0.6.0] — 2026-07-26
6
+
7
+ Minor rather than patch: **every screened number changes.** Anyone comparing a result from
8
+ 0.5.x will see different figures, and the 0.5.x ones were wrong.
9
+
10
+ ### Fixed
11
+
12
+ - **Quarterly and stub periods were counted as years.** A 10-K reports quarterly and
13
+ acquisition-stub periods alongside annual ones, and the reader keyed only on the end date.
14
+ Lumentum's fiscal 2015 became ten separate "years" — nine quarters and stubs plus the real
15
+ 363-day period — so every multi-year rule averaged quarterly income against annual equity.
16
+ LITE's ten-year ROE read 2.26% and is **-1.64%**; MU's read 10.03% and is **12.83%**.
17
+ Periods must now span 300–400 days, wide enough for a 53-week fiscal year, with instant
18
+ balance-sheet facts exempt because they have no duration.
19
+ - **Tag aliases stopped at the first match instead of merging.** Revenue moved to the ASC 606
20
+ tag in 2022, so a company filing since 2013 appeared to have four years of history — which
21
+ then fired a "listed under ten years" exemption on a decade-old filer. Aliases now merge,
22
+ the preferred one winning a contested year and a later filing winning within one alias.
23
+ - **The dilution rule had never once been computed.** Share counts live under the XBRL
24
+ `shares` unit and the reader asked for `USD`, so it reported `skipped` for every company
25
+ ever screened — seven rules advertised, six ever computed, and a skip is indistinguishable
26
+ from a genuine data gap. AAPL now computes 7 of 7.
27
+
28
+ ### Testing
29
+
30
+ - The existing dilution test passed throughout because its fixture also put share counts
31
+ under `USD` — the same mistake as the reader, so the two agreed with each other. The
32
+ fixture now uses the unit XBRL actually uses.
33
+ - A regression test pins that one company's filings cannot leak into another's result.
34
+ Verified live as well: LITE, then AAPL, then LITE again, identical to the digit.
35
+
5
36
  ## [0.5.5] — 2026-07-26
6
37
 
7
38
  ### Fixed
@@ -17,8 +17,16 @@ const pct = (x) => (x === null ? null : Number((x * 100).toFixed(2)));
17
17
  const last = (series, n) => series.slice(-n);
18
18
  const sum = (xs) => xs.reduce((a, b) => a + b, 0);
19
19
 
20
+ /**
21
+ * XBRL reports share counts under the `shares` unit, not `USD`. Requesting the default unit
22
+ * for a share concept returns nothing, so the dilution rule reported `skipped` for every
23
+ * company that has ever been screened -- seven rules advertised, six ever computed, and the
24
+ * skip looked exactly like a genuine data gap.
25
+ */
26
+ const UNIT_BY_CONCEPT = { sharesOutstanding: "shares" };
27
+
20
28
  function values(facts, key, asOf) {
21
- const found = annualSeries(facts, CONCEPTS[key], { asOf });
29
+ const found = annualSeries(facts, CONCEPTS[key], { asOf, unit: UNIT_BY_CONCEPT[key] || "USD" });
22
30
  return found ? found.series.map((e) => ({ end: e.end, filed: e.filed, val: e.val })) : [];
23
31
  }
24
32
 
package/mcp/lib/sec.mjs CHANGED
@@ -70,26 +70,80 @@ export async function fetchCompanyFacts(cik) {
70
70
  * Tries several tags because the same economic quantity has different names depending on
71
71
  * when and under which taxonomy a company filed.
72
72
  */
73
+ /**
74
+ * How long a reported period is, in days. Instant facts (balance-sheet items) have no start.
75
+ */
76
+ const spanDays = (entry) =>
77
+ entry.start ? Math.round((Date.parse(entry.end) - Date.parse(entry.start)) / 86400000) : null;
78
+
79
+ /**
80
+ * Is this entry an annual figure?
81
+ *
82
+ * A 10-K carries quarterly and stub periods alongside the annual ones. Keying only on the
83
+ * end date treated each as its own year: Lumentum's 2015 produced ten "years" from one
84
+ * fiscal year -- nine quarters and stubs plus the real 363-day period. Every multi-year rule
85
+ * then averaged quarterly income against annual equity, silently.
86
+ *
87
+ * Fiscal years run 52 or 53 weeks, so the window has to be wider than 365 exactly.
88
+ */
89
+ const ANNUAL_MIN_DAYS = 300;
90
+ const ANNUAL_MAX_DAYS = 400;
91
+ const isAnnual = (entry) => {
92
+ const days = spanDays(entry);
93
+ // Instant facts have no duration: shares outstanding, equity, total assets.
94
+ if (days === null) return true;
95
+ return days >= ANNUAL_MIN_DAYS && days <= ANNUAL_MAX_DAYS;
96
+ };
97
+
98
+ /** The fiscal year a period belongs to, taken from its end date. */
99
+ const fiscalYear = (entry) => Number(String(entry.end).slice(0, 4));
100
+
101
+ /**
102
+ * Annual history for a concept, merged across every alias.
103
+ *
104
+ * Merging matters as much as the annual filter. The function used to return at the first
105
+ * alias with any data, and revenue moved to RevenueFromContractWithCustomerExcludingAssessedTax
106
+ * when ASC 606 was adopted -- so a company reporting under the new tag since 2022 looked like
107
+ * it had four years of history, which then fired a "listed under ten years" exemption on a
108
+ * company that had been public for a decade.
109
+ *
110
+ * Aliases are ordered by preference, so an earlier alias wins where both cover a year.
111
+ */
73
112
  export function annualSeries(facts, tags, { asOf = null, unit = "USD" } = {}) {
74
113
  const cutoff = asOf ? new Date(asOf).getTime() : null;
114
+ const byYear = new Map();
115
+ let usedTags = [];
116
+
75
117
  for (const tag of tags) {
76
118
  const entries = facts?.facts?.["us-gaap"]?.[tag]?.units?.[unit];
77
119
  if (!Array.isArray(entries) || entries.length === 0) continue;
120
+ let contributed = false;
78
121
 
79
- const byPeriod = new Map();
80
122
  for (const entry of entries) {
81
123
  if (entry.form !== "10-K" || !entry.end || !Number.isFinite(entry.val)) continue;
124
+ if (!isAnnual(entry)) continue;
82
125
  // Look-ahead guard: a filing is only usable once it was actually filed.
83
126
  if (cutoff && new Date(entry.filed).getTime() > cutoff) continue;
84
- const prior = byPeriod.get(entry.end);
85
- // Keep the most recently filed value for a period: restatements supersede.
86
- if (!prior || new Date(entry.filed) > new Date(prior.filed)) byPeriod.set(entry.end, entry);
127
+
128
+ const year = fiscalYear(entry);
129
+ const prior = byYear.get(year);
130
+ if (!prior) {
131
+ byYear.set(year, { ...entry, tag });
132
+ contributed = true;
133
+ continue;
134
+ }
135
+ // An earlier alias always wins the year; within one alias, the latest filing wins,
136
+ // because a restatement supersedes what it restates.
137
+ if (prior.tag === tag && new Date(entry.filed) > new Date(prior.filed)) {
138
+ byYear.set(year, { ...entry, tag });
139
+ }
87
140
  }
88
- if (byPeriod.size === 0) continue;
89
- const series = [...byPeriod.values()].sort((a, b) => new Date(a.end) - new Date(b.end));
90
- return { tag, unit, series };
141
+ if (contributed) usedTags.push(tag);
91
142
  }
92
- return null;
143
+
144
+ if (byYear.size === 0) return null;
145
+ const series = [...byYear.values()].sort((a, b) => new Date(a.end) - new Date(b.end));
146
+ return { tag: usedTags[0], tags: usedTags, unit, series };
93
147
  }
94
148
 
95
149
  /** Concept aliases, ordered by preference. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alphacouncil-agent",
3
- "version": "0.5.5",
3
+ "version": "0.6.0",
4
4
  "description": "Multi-agent public-equity research workflow plugin for Codex & Claude Code: sourced evidence packets, bull/bear debate, and a portfolio-manager Buy/Overweight/Hold/Underweight/Sell decision.",
5
5
  "type": "module",
6
6
  "license": "MIT",