sentisense 0.35.0 → 0.38.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.
package/README.md CHANGED
@@ -263,6 +263,41 @@ client.stocks.getKpis("AAPL") // Product metrics and segment revenue time-
263
263
  client.stocks.listKpiCoverage() // All tickers with curated KPI data (free, no quota cost)
264
264
  ```
265
265
 
266
+ ### Earnings
267
+
268
+ The earnings analysis report is the assembled version of a quarter: one object per fiscal period carrying the editorial headline, the KPI cards with year-over-year deltas, the guidance language as management phrased it, and a summary of the earnings call. Pair it with the recent-reporters feed to drive a post-earnings sweep. Both return the preview envelope.
269
+
270
+ ```typescript
271
+ client.earnings.getSummaries("AAPL", { limit: 4 }) // Per-quarter analysis report, newest first. FREE: latest quarter, shaped. PRO: every hydrated quarter in full.
272
+ client.earnings.getRecent({ days: 7, limit: 25 }) // Who reported in the last N days. Full window on every key.
273
+ ```
274
+
275
+ ```typescript
276
+ import SentiSense from "sentisense";
277
+
278
+ const client = new SentiSense({ apiKey: process.env.SENTISENSE_API_KEY });
279
+
280
+ const res = await client.earnings.getSummaries("AAPL", { limit: 1 });
281
+ const quarter = res.data[0];
282
+
283
+ if (quarter) {
284
+ console.log(quarter.fiscalPeriod, quarter.reportDate);
285
+ console.log(quarter.headline);
286
+ for (const kpi of quarter.kpiHighlights ?? []) {
287
+ console.log(` ${kpi.label}: ${kpi.value} (${kpi.yoy ?? "no YoY"})`);
288
+ }
289
+
290
+ if (res.isPreview) {
291
+ // Free key: section titles stand in for the bodies.
292
+ console.log("Summary covers:", quarter.summaryTopics?.join(", "));
293
+ } else {
294
+ console.log(quarter.summaryMd);
295
+ }
296
+ }
297
+ ```
298
+
299
+ The forward-looking half of the family is `client.calendar.getEarnings()`, which covers scheduled dates and consensus EPS rather than results.
300
+
266
301
  ### ETFs (beta)
267
302
 
268
303
  Composition data is public; the holdings-weighted aggregate views follow the same PRO-with-preview pattern as Analyst/Insider. Aggregates synthesize fund-level views from each constituent's per-stock data, weighted by allocation, with a `coverage` block on every response.
@@ -281,6 +316,55 @@ client.etfs.sentimentAggregate("QQQ") // SentiSense re
281
316
  client.marketMood.get()
282
317
  ```
283
318
 
319
+ ### Screener
320
+
321
+ Filter the tracked universe on the SentiSense Score, attention, analyst consensus, technicals and price in one query. Screening on analyst ratings alone is something a dozen free tools do; screening on analyst ratings *where the Score disagrees* is not.
322
+
323
+ ```typescript
324
+ client.screener.fields() // Every filterable field, both universes, with units + operators
325
+ client.screener.screens() // The curated screens shipped in the product, each with a runnable plan
326
+ client.screener.run({ plan, tickers, limit }) // Run a screen against the stock universe
327
+ client.screener.runEtfs({ plan, limit }) // Run a screen against the ETF universe
328
+ ```
329
+
330
+ ```typescript
331
+ // Run a curated screen as-is
332
+ const { screens } = await client.screener.screens();
333
+ const crowdVsStreet = screens.find((s) => s.id === "crowd-vs-street")!;
334
+ const curated = await client.screener.run({ plan: crowdVsStreet.plan, limit: 25 });
335
+ console.log(`${curated.matched} matched, showing ${curated.results.length}`);
336
+
337
+ // Or build your own: bullish Score, thin analyst enthusiasm
338
+ const res = await client.screener.run({
339
+ plan: {
340
+ filters: [
341
+ { fieldName: "SENTI_SCORE_7D", op: "GTE", value: 13 },
342
+ { fieldName: "ANALYST_BUY_RATIO_PCT", op: "LTE", value: 30 },
343
+ { fieldName: "ANALYST_COUNT", op: "GTE", value: 5 },
344
+ ],
345
+ sort: { fieldName: "SENTI_SCORE_7D", dir: "DESC" },
346
+ },
347
+ limit: 25,
348
+ });
349
+ for (const row of res.results) {
350
+ console.log(row.ticker, row.sentiSenseScore7D, row.analystBuyRatioPct);
351
+ }
352
+ ```
353
+
354
+ `limit` rides next to the plan rather than inside it, because a plan is a stored object and paging is a transport concern. It defaults to 100 and caps at 500. `matched` is the count before `limit` was applied, so truncation is visible. `tickers` is optional: omit it to screen the whole tracked universe, pass a list to screen a watchlist.
355
+
356
+ Three field semantics are worth stating outright, because guessing them wrong produces a screen that looks fine and means nothing:
357
+
358
+ - **`ANALYST_RATING_MEAN` is inverted.** It is the vendor's 1-to-5 scale where **1.0 is strong buy**, so bullish is `LTE 2.5`. Prefer `ANALYST_BUY_RATIO_PCT`, which runs the intuitive direction.
359
+ - **`MA_CROSS_STATE` is ordinal**, not a percentage: `1` golden cross, `-1` death cross, `0` neither. Use `EQ`.
360
+ - **`SENTIMENT_DIRECTION` is the sign of the 7-day SentiSense Score** (`1` / `0` / `-1`) with a neutral band of plus-or-minus 5. Despite the name it is not sentiment polarity, and `0` matches only an exact zero.
361
+
362
+ The Score fields (`SENTI_SCORE_7D`, `SENTI_SCORE_1M`, `SCORE_CHANGE_7D`) are the SentiSense Score, not polarity: unbounded, banded at 5 / 13 / 23 either side of zero. Filter on those band edges, not on values like `0.5`, which behave as "any positive score". Nulls never match in either direction, so `RETURN_1Y >= 0` and `RETURN_1Y < 0` do not partition the universe: a stock listed four months ago is in neither result. If a screen returns fewer rows than you expect, check coverage before you check your thresholds.
363
+
364
+ On the ETF side, `CONSTITUENTS_WEIGHTED_SENTISENSE` is the holdings-weighted Score across what the fund owns and is usually the one you want; `DIRECT_SENTISENSE` is the Score from chatter about the fund ticker itself. `WEIGHT_COVERED_PCT` tells you how much of the fund's weight had constituent data behind the weighted number.
365
+
366
+ Screens read a snapshot that refreshes every 20 minutes, so this is not a quote feed. Use `client.stocks.getQuote()` for live prices.
367
+
284
368
  ## Error Handling
285
369
 
286
370
  ```typescript
package/dist/index.cjs CHANGED
@@ -185,6 +185,53 @@ var Documents = class {
185
185
  }
186
186
  };
187
187
 
188
+ // src/resources/earnings.ts
189
+ var Earnings = class {
190
+ constructor(client) {
191
+ this.client = client;
192
+ }
193
+ /**
194
+ * Per-quarter earnings analysis report for one ticker, newest first.
195
+ *
196
+ * Each quarter carries the editorial headline, the KPI cards that matter for
197
+ * that company with year-over-year deltas, the guidance language as
198
+ * management phrased it, and a summary of the earnings call.
199
+ *
200
+ * Branch on `isPreview`: a PRO key receives every hydrated quarter in full, a
201
+ * FREE key receives the latest quarter shaped rather than truncated, plus
202
+ * `totalCount`. {@link EarningsQuarter} documents which fields each tier
203
+ * carries.
204
+ *
205
+ * A quarter typically appears within 48 hours of the company reporting, and
206
+ * the call summary can arrive after the press-release content for the same
207
+ * quarter, so read `generatedAt` and `transcriptGeneratedAt` rather than
208
+ * assuming a fixed lag. A ticker with no stored quarter answers with an empty
209
+ * `data` array, not a 404.
210
+ *
211
+ * Use canonical ticker symbols: `GOOGL` (not `GOOG`), `BRK.B` (not `BRK-B`).
212
+ */
213
+ async getSummaries(ticker, options) {
214
+ return this.client.get(
215
+ `/api/v1/stocks/${encodeURIComponent(ticker.toUpperCase())}/earnings-summaries`,
216
+ options
217
+ );
218
+ }
219
+ /**
220
+ * Which covered companies reported on or after `today - days`, newest first.
221
+ *
222
+ * Every API key receives the full window it asks for, so `isPreview` is
223
+ * always `false` here. The window is bounded by `reportDate`, so a quarter
224
+ * reported inside it appears even when its call summary lands later, and an
225
+ * empty `data` array means nobody in the covered set reported in that window.
226
+ *
227
+ * This is the backward-looking feed; `client.calendar.getEarnings()` is the
228
+ * forward-looking one.
229
+ */
230
+ async getRecent(options) {
231
+ return this.client.get("/api/v1/earnings/recent", options);
232
+ }
233
+ };
234
+
188
235
  // src/resources/entityMetrics.ts
189
236
  var EntityMetrics = class {
190
237
  constructor(client) {
@@ -285,7 +332,7 @@ var Insider = class {
285
332
  /**
286
333
  * Get market-wide insider activity: top buys and sells aggregated by ticker.
287
334
  *
288
- * PRO-gated. Free/unauthenticated users receive a preview (top 5 per direction)
335
+ * PRO-gated. Free-tier users receive a preview (top 5 per direction)
289
336
  * with `isPreview: true` in the response.
290
337
  */
291
338
  async getActivity(options) {
@@ -320,7 +367,7 @@ var Politicians = class {
320
367
  /**
321
368
  * Get recent congressional STOCK Act trading activity across all politicians.
322
369
  *
323
- * PRO-gated. Free/unauthenticated users receive a preview (top 5 trades)
370
+ * PRO-gated. Free-tier users receive a preview (top 5 trades)
324
371
  * with `isPreview: true` in the response.
325
372
  *
326
373
  * The feed is longer than one response: a default 90-day window is routinely well over a
@@ -432,7 +479,7 @@ var Insights = class {
432
479
  }
433
480
  /**
434
481
  * Get available insight types for a specific stock.
435
- * No authentication required.
482
+ * API key required.
436
483
  *
437
484
  * Returns an array of insight type strings (e.g., `["sentiment_shift", "options_activity"]`).
438
485
  */
@@ -571,6 +618,86 @@ var MarketSummaryResource = class {
571
618
  }
572
619
  };
573
620
 
621
+ // src/resources/screener.ts
622
+ var Screener = class {
623
+ constructor(client) {
624
+ this.client = client;
625
+ }
626
+ /**
627
+ * Every filterable field, with its unit, operators and description, for both
628
+ * universes.
629
+ *
630
+ * Build a filter UI from this rather than hardcoding the list and you inherit
631
+ * new fields as they ship. The ETF `STRING` fields (`ISSUER`, `ASSET_CLASS`,
632
+ * `TRACKED_INDEX`) come back with their `values` populated from the live
633
+ * universe, so pickers stay current without a redeploy.
634
+ */
635
+ async fields() {
636
+ return this.client.get("/api/v1/screener/fields");
637
+ }
638
+ /**
639
+ * The curated screens shipped in the product, each with a runnable plan.
640
+ *
641
+ * Each `plan` round-trips straight into {@link run} (or {@link runEtfs} when
642
+ * `plan.universe === "ETF"`) with nothing to rebuild.
643
+ *
644
+ * Their filters identify the field with `field` rather than `fieldName`.
645
+ * Both keys are accepted on the way in, so read either when inspecting a plan
646
+ * you did not build yourself.
647
+ */
648
+ async screens() {
649
+ return this.client.get("/api/v1/screener/screens");
650
+ }
651
+ /**
652
+ * Run a screen against the stock universe.
653
+ *
654
+ * `tickers` is optional: omit it to screen the whole tracked universe, pass a
655
+ * list to screen a watchlist. `limit` sits next to the plan rather than
656
+ * inside it, because a plan is a stored object and paging is a transport
657
+ * concern; it defaults to 100 and caps at 500.
658
+ *
659
+ * Read `matched` before you read `results`: it is the count before `limit`
660
+ * was applied, so a `matched` above your `limit` means you are holding the
661
+ * top slice under the plan's sort, not the whole answer.
662
+ *
663
+ * @example
664
+ * ```ts
665
+ * const res = await client.screener.run({
666
+ * plan: {
667
+ * filters: [
668
+ * { fieldName: "SENTI_SCORE_7D", op: "GTE", value: 13 },
669
+ * { fieldName: "ANALYST_BUY_RATIO_PCT", op: "LTE", value: 30 },
670
+ * { fieldName: "ANALYST_COUNT", op: "GTE", value: 5 },
671
+ * ],
672
+ * sort: { fieldName: "SENTI_SCORE_7D", dir: "DESC" },
673
+ * },
674
+ * limit: 25,
675
+ * });
676
+ * ```
677
+ */
678
+ async run(options) {
679
+ return this.client.post("/api/v1/screener/execute", options);
680
+ }
681
+ /**
682
+ * Run a screen against the ETF universe.
683
+ *
684
+ * Same request shape as {@link run}, against a different field vocabulary:
685
+ * take the ETF names from `fields().etf`. `IN` / `NOT_IN` take a `values`
686
+ * array instead of `value` and are the operators for the string fields
687
+ * (`ISSUER`, `ASSET_CLASS`, `TRACKED_INDEX`).
688
+ *
689
+ * `CONSTITUENTS_WEIGHTED_SENTISENSE` is the holdings-weighted SentiSense
690
+ * Score across what the fund owns and is usually the one you want;
691
+ * `DIRECT_SENTISENSE` is the Score from chatter about the fund ticker itself,
692
+ * which on a broad index fund is mostly macro noise. `WEIGHT_COVERED_PCT`
693
+ * tells you how much of the fund's weight had constituent data behind the
694
+ * weighted number.
695
+ */
696
+ async runEtfs(options) {
697
+ return this.client.post("/api/v1/screener/etfs/execute", options);
698
+ }
699
+ };
700
+
574
701
  // src/resources/stocks.ts
575
702
  var Stocks = class {
576
703
  constructor(client) {
@@ -844,7 +971,7 @@ var Trackers = class {
844
971
  };
845
972
 
846
973
  // src/version.ts
847
- var VERSION = "0.35.0";
974
+ var VERSION = "0.38.0";
848
975
 
849
976
  // src/client.ts
850
977
  var DEFAULT_BASE_URL = "https://app.sentisense.ai";
@@ -889,6 +1016,8 @@ var SentiSense = class {
889
1016
  this.indexes = new Indexes(this);
890
1017
  this.trackers = new Trackers(this);
891
1018
  this.calendar = new Calendar(this);
1019
+ this.earnings = new Earnings(this);
1020
+ this.screener = new Screener(this);
892
1021
  }
893
1022
  /** @internal */
894
1023
  async get(path, params) {