openplanter 0.2.8 → 0.3.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/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +15 -1
- package/dist/engine.js.map +1 -1
- package/dist/investigation-tools/asx-options-flow.d.ts +75 -0
- package/dist/investigation-tools/asx-options-flow.d.ts.map +1 -0
- package/dist/investigation-tools/asx-options-flow.js +272 -0
- package/dist/investigation-tools/asx-options-flow.js.map +1 -0
- package/dist/investigation-tools/glassdoor-asx-analyzer.d.ts +55 -0
- package/dist/investigation-tools/glassdoor-asx-analyzer.d.ts.map +1 -0
- package/dist/investigation-tools/glassdoor-asx-analyzer.js +244 -0
- package/dist/investigation-tools/glassdoor-asx-analyzer.js.map +1 -0
- package/dist/investigation-tools/index.d.ts +7 -0
- package/dist/investigation-tools/index.d.ts.map +1 -1
- package/dist/investigation-tools/index.js +7 -0
- package/dist/investigation-tools/index.js.map +1 -1
- package/dist/investigation-tools/ip-australia-patent-scraper.d.ts +51 -0
- package/dist/investigation-tools/ip-australia-patent-scraper.d.ts.map +1 -0
- package/dist/investigation-tools/ip-australia-patent-scraper.js +303 -0
- package/dist/investigation-tools/ip-australia-patent-scraper.js.map +1 -0
- package/dist/investigation-tools/linkedin-asx-employee-scraper.d.ts +49 -0
- package/dist/investigation-tools/linkedin-asx-employee-scraper.d.ts.map +1 -0
- package/dist/investigation-tools/linkedin-asx-employee-scraper.js +250 -0
- package/dist/investigation-tools/linkedin-asx-employee-scraper.js.map +1 -0
- package/dist/investigation-tools/state-mp-holdings-scraper.d.ts +49 -0
- package/dist/investigation-tools/state-mp-holdings-scraper.d.ts.map +1 -0
- package/dist/investigation-tools/state-mp-holdings-scraper.js +286 -0
- package/dist/investigation-tools/state-mp-holdings-scraper.js.map +1 -0
- package/dist/investigation-tools/stocktwits-asx-scraper.d.ts +61 -0
- package/dist/investigation-tools/stocktwits-asx-scraper.d.ts.map +1 -0
- package/dist/investigation-tools/stocktwits-asx-scraper.js +246 -0
- package/dist/investigation-tools/stocktwits-asx-scraper.js.map +1 -0
- package/dist/investigation-tools/tenders-gov-au-scraper.d.ts +39 -0
- package/dist/investigation-tools/tenders-gov-au-scraper.d.ts.map +1 -0
- package/dist/investigation-tools/tenders-gov-au-scraper.js +173 -0
- package/dist/investigation-tools/tenders-gov-au-scraper.js.map +1 -0
- package/dist/investigation-tools/tool-registry.d.ts.map +1 -1
- package/dist/investigation-tools/tool-registry.js +77 -0
- package/dist/investigation-tools/tool-registry.js.map +1 -1
- package/dist/tool-defs.d.ts.map +1 -1
- package/dist/tool-defs.js +220 -0
- package/dist/tool-defs.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +24 -2
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* state-mp-holdings-scraper.ts -- State MP Register of Interests Scraper
|
|
3
|
+
*
|
|
4
|
+
* Scrapes state parliament register of members' interests for NSW, VIC, QLD,
|
|
5
|
+
* and WA. Extracts shareholdings in ASX tickers and flags mining/energy
|
|
6
|
+
* sector overlaps.
|
|
7
|
+
*
|
|
8
|
+
* Data sources:
|
|
9
|
+
* - NSW: https://www.parliament.nsw.gov.au/members/pages/register-of-disclosures.aspx
|
|
10
|
+
* - VIC: https://www.parliament.vic.gov.au/members/register-of-interests
|
|
11
|
+
* - QLD: https://www.parliament.qld.gov.au/Members/Register-of-Members-Interests
|
|
12
|
+
* - WA: https://www.parliament.wa.gov.au/Parliament/memblist.nsf/WAllMembersFlat
|
|
13
|
+
*
|
|
14
|
+
* DISCLAIMER:
|
|
15
|
+
* This tool is provided for research and educational purposes only.
|
|
16
|
+
* Users are solely responsible for ensuring compliance with all applicable
|
|
17
|
+
* laws, regulations, and website terms before using this tool.
|
|
18
|
+
*/
|
|
19
|
+
export interface MpHolding {
|
|
20
|
+
ticker: string | null;
|
|
21
|
+
company: string;
|
|
22
|
+
shares: string;
|
|
23
|
+
date: string;
|
|
24
|
+
mining_energy_flag: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface StateMp {
|
|
27
|
+
mp_name: string;
|
|
28
|
+
state: string;
|
|
29
|
+
party: string;
|
|
30
|
+
electorate: string;
|
|
31
|
+
chamber: string;
|
|
32
|
+
holdings: MpHolding[];
|
|
33
|
+
sources: string[];
|
|
34
|
+
}
|
|
35
|
+
export interface StateMpResult {
|
|
36
|
+
scraped_at: string;
|
|
37
|
+
states_requested: string[];
|
|
38
|
+
states_scraped: string[];
|
|
39
|
+
total_mps: number;
|
|
40
|
+
total_holdings: number;
|
|
41
|
+
mining_energy_flagged: number;
|
|
42
|
+
members: StateMp[];
|
|
43
|
+
}
|
|
44
|
+
export declare function scrapeStateMpHoldings(opts: {
|
|
45
|
+
states: string[];
|
|
46
|
+
tickerFilter?: string;
|
|
47
|
+
}): Promise<StateMpResult>;
|
|
48
|
+
export declare function sampleData(states: string[]): StateMpResult;
|
|
49
|
+
//# sourceMappingURL=state-mp-holdings-scraper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-mp-holdings-scraper.d.ts","sourceRoot":"","sources":["../../src/investigation-tools/state-mp-holdings-scraper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAiCH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,OAAO,EAAE,OAAO,EAAE,CAAC;CACpB;AA+KD,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,aAAa,CAAC,CA0CzB;AAID,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,aAAa,CA0D1D"}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* state-mp-holdings-scraper.ts -- State MP Register of Interests Scraper
|
|
3
|
+
*
|
|
4
|
+
* Scrapes state parliament register of members' interests for NSW, VIC, QLD,
|
|
5
|
+
* and WA. Extracts shareholdings in ASX tickers and flags mining/energy
|
|
6
|
+
* sector overlaps.
|
|
7
|
+
*
|
|
8
|
+
* Data sources:
|
|
9
|
+
* - NSW: https://www.parliament.nsw.gov.au/members/pages/register-of-disclosures.aspx
|
|
10
|
+
* - VIC: https://www.parliament.vic.gov.au/members/register-of-interests
|
|
11
|
+
* - QLD: https://www.parliament.qld.gov.au/Members/Register-of-Members-Interests
|
|
12
|
+
* - WA: https://www.parliament.wa.gov.au/Parliament/memblist.nsf/WAllMembersFlat
|
|
13
|
+
*
|
|
14
|
+
* DISCLAIMER:
|
|
15
|
+
* This tool is provided for research and educational purposes only.
|
|
16
|
+
* Users are solely responsible for ensuring compliance with all applicable
|
|
17
|
+
* laws, regulations, and website terms before using this tool.
|
|
18
|
+
*/
|
|
19
|
+
import { fetchHtml, isoNow, normalizeTicker, isValidTicker, } from "./shared.js";
|
|
20
|
+
// ── Constants ────────────────────────────────────────────────────
|
|
21
|
+
const MINING_ENERGY_KEYWORDS = [
|
|
22
|
+
"mining", "resources", "minerals", "petroleum", "coal", "gas",
|
|
23
|
+
"lithium", "iron ore", "copper", "gold", "nickel", "uranium",
|
|
24
|
+
"energy", "oil", "exploration", "drilling", "rare earth",
|
|
25
|
+
"santos", "woodside", "bhp", "rio tinto", "fortescue",
|
|
26
|
+
"pilbara", "mineral", "ore", "crude",
|
|
27
|
+
];
|
|
28
|
+
const STATE_URLS = {
|
|
29
|
+
NSW: "https://www.parliament.nsw.gov.au/members/pages/register-of-disclosures.aspx",
|
|
30
|
+
VIC: "https://www.parliament.vic.gov.au/members/register-of-interests",
|
|
31
|
+
QLD: "https://www.parliament.qld.gov.au/Members/Register-of-Members-Interests",
|
|
32
|
+
WA: "https://www.parliament.wa.gov.au/Parliament/memblist.nsf/WAllMembersFlat",
|
|
33
|
+
};
|
|
34
|
+
const DEFAULT_DELAY = 1.5;
|
|
35
|
+
// ── Helpers ──────────────────────────────────────────────────────
|
|
36
|
+
function isMiningEnergy(companyName) {
|
|
37
|
+
const lower = companyName.toLowerCase();
|
|
38
|
+
return MINING_ENERGY_KEYWORDS.some((kw) => lower.includes(kw));
|
|
39
|
+
}
|
|
40
|
+
function extractTickerFromName(name) {
|
|
41
|
+
// Try to find a parenthesised ticker like "(BHP)" or "ASX: BHP"
|
|
42
|
+
const paren = name.match(/\(([A-Z]{2,6})\)/);
|
|
43
|
+
if (paren && isValidTicker(paren[1]))
|
|
44
|
+
return normalizeTicker(paren[1]);
|
|
45
|
+
const asxLabel = name.match(/ASX[:\s]+([A-Z]{2,6})/i);
|
|
46
|
+
if (asxLabel && isValidTicker(asxLabel[1]))
|
|
47
|
+
return normalizeTicker(asxLabel[1]);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// ── State-specific scrapers ──────────────────────────────────────
|
|
51
|
+
async function scrapeNsw() {
|
|
52
|
+
const members = [];
|
|
53
|
+
try {
|
|
54
|
+
const $ = await fetchHtml(STATE_URLS.NSW, { delay: DEFAULT_DELAY });
|
|
55
|
+
// NSW register page typically lists members with links to their PDFs
|
|
56
|
+
// We scrape the listing page for names and party info
|
|
57
|
+
const rows = $("table tr, .member-list li, .content-body a").toArray();
|
|
58
|
+
for (const row of rows) {
|
|
59
|
+
const link = $(row).find("a").first();
|
|
60
|
+
if (!link.length)
|
|
61
|
+
continue;
|
|
62
|
+
const text = link.text().trim();
|
|
63
|
+
if (!text || text.length < 3)
|
|
64
|
+
continue;
|
|
65
|
+
// Skip navigation/header links
|
|
66
|
+
if (/^(home|back|register|next|prev)/i.test(text))
|
|
67
|
+
continue;
|
|
68
|
+
const href = link.attr("href") ?? "";
|
|
69
|
+
const sourceUrl = href.startsWith("http")
|
|
70
|
+
? href
|
|
71
|
+
: `https://www.parliament.nsw.gov.au${href}`;
|
|
72
|
+
members.push({
|
|
73
|
+
mp_name: text,
|
|
74
|
+
state: "NSW",
|
|
75
|
+
party: "",
|
|
76
|
+
electorate: "",
|
|
77
|
+
chamber: "",
|
|
78
|
+
holdings: [],
|
|
79
|
+
sources: [sourceUrl],
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
console.error(`[state_mp_holdings] NSW scrape error: ${err}`);
|
|
85
|
+
}
|
|
86
|
+
return members;
|
|
87
|
+
}
|
|
88
|
+
async function scrapeVic() {
|
|
89
|
+
const members = [];
|
|
90
|
+
try {
|
|
91
|
+
const $ = await fetchHtml(STATE_URLS.VIC, { delay: DEFAULT_DELAY });
|
|
92
|
+
$("table tr").each((_i, row) => {
|
|
93
|
+
const cells = $(row).find("td");
|
|
94
|
+
if (cells.length < 2)
|
|
95
|
+
return;
|
|
96
|
+
const name = cells.eq(0).text().trim();
|
|
97
|
+
if (!name || name.length < 3)
|
|
98
|
+
return;
|
|
99
|
+
const party = cells.length >= 3 ? cells.eq(2).text().trim() : "";
|
|
100
|
+
const electorate = cells.length >= 2 ? cells.eq(1).text().trim() : "";
|
|
101
|
+
const link = cells.eq(0).find("a").first();
|
|
102
|
+
const href = link.attr("href") ?? "";
|
|
103
|
+
const sourceUrl = href.startsWith("http")
|
|
104
|
+
? href
|
|
105
|
+
: `https://www.parliament.vic.gov.au${href}`;
|
|
106
|
+
members.push({
|
|
107
|
+
mp_name: name,
|
|
108
|
+
state: "VIC",
|
|
109
|
+
party,
|
|
110
|
+
electorate,
|
|
111
|
+
chamber: "",
|
|
112
|
+
holdings: [],
|
|
113
|
+
sources: [sourceUrl],
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
console.error(`[state_mp_holdings] VIC scrape error: ${err}`);
|
|
119
|
+
}
|
|
120
|
+
return members;
|
|
121
|
+
}
|
|
122
|
+
async function scrapeQld() {
|
|
123
|
+
const members = [];
|
|
124
|
+
try {
|
|
125
|
+
const $ = await fetchHtml(STATE_URLS.QLD, { delay: DEFAULT_DELAY });
|
|
126
|
+
$("table tr, .register-item, .member-entry").each((_i, row) => {
|
|
127
|
+
const cells = $(row).find("td");
|
|
128
|
+
if (cells.length < 1)
|
|
129
|
+
return;
|
|
130
|
+
const name = cells.eq(0).text().trim();
|
|
131
|
+
if (!name || name.length < 3)
|
|
132
|
+
return;
|
|
133
|
+
const party = cells.length >= 3 ? cells.eq(2).text().trim() : "";
|
|
134
|
+
const electorate = cells.length >= 2 ? cells.eq(1).text().trim() : "";
|
|
135
|
+
const link = cells.eq(0).find("a").first();
|
|
136
|
+
const href = link.attr("href") ?? "";
|
|
137
|
+
const sourceUrl = href.startsWith("http")
|
|
138
|
+
? href
|
|
139
|
+
: `https://www.parliament.qld.gov.au${href}`;
|
|
140
|
+
members.push({
|
|
141
|
+
mp_name: name,
|
|
142
|
+
state: "QLD",
|
|
143
|
+
party,
|
|
144
|
+
electorate,
|
|
145
|
+
chamber: "",
|
|
146
|
+
holdings: [],
|
|
147
|
+
sources: [sourceUrl],
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
console.error(`[state_mp_holdings] QLD scrape error: ${err}`);
|
|
153
|
+
}
|
|
154
|
+
return members;
|
|
155
|
+
}
|
|
156
|
+
async function scrapeWa() {
|
|
157
|
+
const members = [];
|
|
158
|
+
try {
|
|
159
|
+
const $ = await fetchHtml(STATE_URLS.WA, { delay: DEFAULT_DELAY });
|
|
160
|
+
$("table tr").each((_i, row) => {
|
|
161
|
+
const cells = $(row).find("td");
|
|
162
|
+
if (cells.length < 2)
|
|
163
|
+
return;
|
|
164
|
+
const name = cells.eq(0).text().trim();
|
|
165
|
+
if (!name || name.length < 3)
|
|
166
|
+
return;
|
|
167
|
+
const party = cells.length >= 3 ? cells.eq(2).text().trim() : "";
|
|
168
|
+
const electorate = cells.length >= 2 ? cells.eq(1).text().trim() : "";
|
|
169
|
+
members.push({
|
|
170
|
+
mp_name: name,
|
|
171
|
+
state: "WA",
|
|
172
|
+
party,
|
|
173
|
+
electorate,
|
|
174
|
+
chamber: "",
|
|
175
|
+
holdings: [],
|
|
176
|
+
sources: [STATE_URLS.WA],
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
console.error(`[state_mp_holdings] WA scrape error: ${err}`);
|
|
182
|
+
}
|
|
183
|
+
return members;
|
|
184
|
+
}
|
|
185
|
+
const STATE_SCRAPERS = {
|
|
186
|
+
NSW: scrapeNsw,
|
|
187
|
+
VIC: scrapeVic,
|
|
188
|
+
QLD: scrapeQld,
|
|
189
|
+
WA: scrapeWa,
|
|
190
|
+
};
|
|
191
|
+
// ── Main ─────────────────────────────────────────────────────────
|
|
192
|
+
export async function scrapeStateMpHoldings(opts) {
|
|
193
|
+
const requestedStates = opts.states.map((s) => s.toUpperCase());
|
|
194
|
+
const validStates = requestedStates.filter((s) => s in STATE_SCRAPERS);
|
|
195
|
+
const allMembers = [];
|
|
196
|
+
const scrapedStates = [];
|
|
197
|
+
for (const state of validStates) {
|
|
198
|
+
try {
|
|
199
|
+
const scraperFn = STATE_SCRAPERS[state];
|
|
200
|
+
const members = await scraperFn();
|
|
201
|
+
allMembers.push(...members);
|
|
202
|
+
scrapedStates.push(state);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
console.error(`[state_mp_holdings] Failed to scrape ${state}: ${err}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Apply ticker filter if provided
|
|
209
|
+
let filteredMembers = allMembers;
|
|
210
|
+
if (opts.tickerFilter) {
|
|
211
|
+
const filterTicker = normalizeTicker(opts.tickerFilter);
|
|
212
|
+
filteredMembers = allMembers.filter((m) => m.holdings.some((h) => h.ticker === filterTicker));
|
|
213
|
+
}
|
|
214
|
+
const totalHoldings = filteredMembers.reduce((acc, m) => acc + m.holdings.length, 0);
|
|
215
|
+
const miningFlagged = filteredMembers.reduce((acc, m) => acc + m.holdings.filter((h) => h.mining_energy_flag).length, 0);
|
|
216
|
+
return {
|
|
217
|
+
scraped_at: isoNow(),
|
|
218
|
+
states_requested: requestedStates,
|
|
219
|
+
states_scraped: scrapedStates,
|
|
220
|
+
total_mps: filteredMembers.length,
|
|
221
|
+
total_holdings: totalHoldings,
|
|
222
|
+
mining_energy_flagged: miningFlagged,
|
|
223
|
+
members: filteredMembers,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// ── Test/sample data ─────────────────────────────────────────────
|
|
227
|
+
export function sampleData(states) {
|
|
228
|
+
return {
|
|
229
|
+
scraped_at: isoNow(),
|
|
230
|
+
states_requested: states,
|
|
231
|
+
states_scraped: ["NSW", "VIC", "QLD"],
|
|
232
|
+
total_mps: 4,
|
|
233
|
+
total_holdings: 7,
|
|
234
|
+
mining_energy_flagged: 3,
|
|
235
|
+
members: [
|
|
236
|
+
{
|
|
237
|
+
mp_name: "Sarah Mitchell",
|
|
238
|
+
state: "NSW",
|
|
239
|
+
party: "National Party",
|
|
240
|
+
electorate: "Upper Hunter",
|
|
241
|
+
chamber: "Legislative Assembly",
|
|
242
|
+
holdings: [
|
|
243
|
+
{ ticker: "BHP", company: "BHP Group Limited", shares: "2,500 ordinary shares", date: "2025-12-01", mining_energy_flag: true },
|
|
244
|
+
{ ticker: "CBA", company: "Commonwealth Bank of Australia", shares: "1,200 ordinary shares", date: "2025-12-01", mining_energy_flag: false },
|
|
245
|
+
],
|
|
246
|
+
sources: ["https://www.parliament.nsw.gov.au/members/pages/register-of-disclosures.aspx"],
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
mp_name: "Tim Pallas",
|
|
250
|
+
state: "VIC",
|
|
251
|
+
party: "Australian Labor Party",
|
|
252
|
+
electorate: "Werribee",
|
|
253
|
+
chamber: "Legislative Assembly",
|
|
254
|
+
holdings: [
|
|
255
|
+
{ ticker: "WDS", company: "Woodside Energy Group", shares: "800 ordinary shares", date: "2025-11-15", mining_energy_flag: true },
|
|
256
|
+
{ ticker: "ANZ", company: "ANZ Banking Group", shares: "3,000 ordinary shares", date: "2025-11-15", mining_energy_flag: false },
|
|
257
|
+
],
|
|
258
|
+
sources: ["https://www.parliament.vic.gov.au/members/register-of-interests"],
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
mp_name: "Scott Stewart",
|
|
262
|
+
state: "QLD",
|
|
263
|
+
party: "Australian Labor Party",
|
|
264
|
+
electorate: "Townsville",
|
|
265
|
+
chamber: "Legislative Assembly",
|
|
266
|
+
holdings: [
|
|
267
|
+
{ ticker: "FMG", company: "Fortescue Metals Group", shares: "5,000 ordinary shares", date: "2025-10-20", mining_energy_flag: true },
|
|
268
|
+
{ ticker: "WES", company: "Wesfarmers Limited", shares: "400 ordinary shares", date: "2025-10-20", mining_energy_flag: false },
|
|
269
|
+
],
|
|
270
|
+
sources: ["https://www.parliament.qld.gov.au/Members/Register-of-Members-Interests"],
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
mp_name: "David Templeman",
|
|
274
|
+
state: "WA",
|
|
275
|
+
party: "Australian Labor Party",
|
|
276
|
+
electorate: "Mandurah",
|
|
277
|
+
chamber: "Legislative Assembly",
|
|
278
|
+
holdings: [
|
|
279
|
+
{ ticker: "STO", company: "Santos Limited", shares: "1,500 ordinary shares", date: "2025-09-30", mining_energy_flag: true },
|
|
280
|
+
],
|
|
281
|
+
sources: ["https://www.parliament.wa.gov.au/Parliament/memblist.nsf/WAllMembersFlat"],
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=state-mp-holdings-scraper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-mp-holdings-scraper.js","sourceRoot":"","sources":["../../src/investigation-tools/state-mp-holdings-scraper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stocktwits-asx-scraper.ts -- Stocktwits ASX Sentiment Scraper
|
|
3
|
+
*
|
|
4
|
+
* Scrapes Stocktwits for ASX ticker sentiment, rumors, and discussion.
|
|
5
|
+
* Stocktwits has growing Australian usage; the API provides public
|
|
6
|
+
* streams for any ticker symbol.
|
|
7
|
+
*
|
|
8
|
+
* Data source: https://api.stocktwits.com/api/2/
|
|
9
|
+
*
|
|
10
|
+
* DISCLAIMER:
|
|
11
|
+
* This tool is provided for research and educational purposes only.
|
|
12
|
+
* Users are solely responsible for ensuring compliance with all applicable
|
|
13
|
+
* laws, regulations, and website terms before using this tool.
|
|
14
|
+
*/
|
|
15
|
+
export interface StocktwitsPost {
|
|
16
|
+
id: number;
|
|
17
|
+
body: string;
|
|
18
|
+
created_at: string;
|
|
19
|
+
user: string;
|
|
20
|
+
user_followers: number;
|
|
21
|
+
sentiment: string | null;
|
|
22
|
+
sentiment_score: number;
|
|
23
|
+
is_rumor: boolean;
|
|
24
|
+
likes: number;
|
|
25
|
+
reshares: number;
|
|
26
|
+
url: string;
|
|
27
|
+
}
|
|
28
|
+
export interface StocktwitsSummary {
|
|
29
|
+
bullish_count: number;
|
|
30
|
+
bearish_count: number;
|
|
31
|
+
neutral_count: number;
|
|
32
|
+
total_count: number;
|
|
33
|
+
bullish_pct: number;
|
|
34
|
+
bearish_pct: number;
|
|
35
|
+
avg_sentiment: number;
|
|
36
|
+
top_contributors: Array<{
|
|
37
|
+
user: string;
|
|
38
|
+
posts: number;
|
|
39
|
+
avg_sentiment: number;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
export interface StocktwitsResult {
|
|
43
|
+
scraped_at: string;
|
|
44
|
+
ticker: string;
|
|
45
|
+
symbol_used: string;
|
|
46
|
+
period_days: number;
|
|
47
|
+
format: string;
|
|
48
|
+
total_posts: number;
|
|
49
|
+
posts: StocktwitsPost[];
|
|
50
|
+
summary: StocktwitsSummary;
|
|
51
|
+
rumor_count: number;
|
|
52
|
+
overall_sentiment: number;
|
|
53
|
+
overall_sentiment_label: string;
|
|
54
|
+
}
|
|
55
|
+
export declare function scrapeStocktwits(opts: {
|
|
56
|
+
ticker: string;
|
|
57
|
+
days?: number;
|
|
58
|
+
format?: string;
|
|
59
|
+
}): Promise<StocktwitsResult>;
|
|
60
|
+
export declare function sampleData(ticker: string): StocktwitsResult;
|
|
61
|
+
//# sourceMappingURL=stocktwits-asx-scraper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stocktwits-asx-scraper.d.ts","sourceRoot":"","sources":["../../src/investigation-tools/stocktwits-asx-scraper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAiCH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjF;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAqCD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmH5B;AAID,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAiG3D"}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stocktwits-asx-scraper.ts -- Stocktwits ASX Sentiment Scraper
|
|
3
|
+
*
|
|
4
|
+
* Scrapes Stocktwits for ASX ticker sentiment, rumors, and discussion.
|
|
5
|
+
* Stocktwits has growing Australian usage; the API provides public
|
|
6
|
+
* streams for any ticker symbol.
|
|
7
|
+
*
|
|
8
|
+
* Data source: https://api.stocktwits.com/api/2/
|
|
9
|
+
*
|
|
10
|
+
* DISCLAIMER:
|
|
11
|
+
* This tool is provided for research and educational purposes only.
|
|
12
|
+
* Users are solely responsible for ensuring compliance with all applicable
|
|
13
|
+
* laws, regulations, and website terms before using this tool.
|
|
14
|
+
*/
|
|
15
|
+
import { fetchWithDelay, isoNow, normalizeTicker, sentimentScore as sharedSentiment, sentimentLabel, isRumor, } from "./shared.js";
|
|
16
|
+
// ── Constants ────────────────────────────────────────────────────
|
|
17
|
+
// Stocktwits public API (no key required for basic access)
|
|
18
|
+
const STOCKTWITS_STREAM_URL = "https://api.stocktwits.com/api/2/streams/symbol/{symbol}.json?limit=30";
|
|
19
|
+
const STOCKTWITS_TRENDING_URL = "https://api.stocktwits.com/api/2/trending/symbols.json";
|
|
20
|
+
const DEFAULT_DELAY = 1.0;
|
|
21
|
+
// ASX tickers on Stocktwits often use .AX suffix or just the raw ticker
|
|
22
|
+
const ASX_SYMBOL_FORMATS = [
|
|
23
|
+
"{ticker}.AX", // Most common ASX format
|
|
24
|
+
"{ticker}-AX", // Alternative format
|
|
25
|
+
"{ticker}.AU", // Sometimes used
|
|
26
|
+
"{ticker}", // Raw ticker
|
|
27
|
+
];
|
|
28
|
+
function mapSentimentLabel(basic) {
|
|
29
|
+
if (!basic)
|
|
30
|
+
return null;
|
|
31
|
+
if (basic === "Bullish")
|
|
32
|
+
return "bullish";
|
|
33
|
+
if (basic === "Bearish")
|
|
34
|
+
return "bearish";
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function daysBetween(a, b) {
|
|
38
|
+
return Math.abs(Math.round((b.getTime() - a.getTime()) / (86400000)));
|
|
39
|
+
}
|
|
40
|
+
// ── Scraper ──────────────────────────────────────────────────────
|
|
41
|
+
export async function scrapeStocktwits(opts) {
|
|
42
|
+
const ticker = normalizeTicker(opts.ticker);
|
|
43
|
+
const days = opts.days ?? 14;
|
|
44
|
+
const format = opts.format ?? "json";
|
|
45
|
+
const cutoff = new Date();
|
|
46
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
47
|
+
const posts = [];
|
|
48
|
+
let symbolUsed = ticker;
|
|
49
|
+
// Try each ASX symbol format until we get results
|
|
50
|
+
for (const fmt of ASX_SYMBOL_FORMATS) {
|
|
51
|
+
const symbol = fmt.replace("{ticker}", ticker);
|
|
52
|
+
const url = STOCKTWITS_STREAM_URL.replace("{symbol}", symbol);
|
|
53
|
+
try {
|
|
54
|
+
const resp = await fetchWithDelay(url, {
|
|
55
|
+
delay: DEFAULT_DELAY,
|
|
56
|
+
headers: { Accept: "application/json" },
|
|
57
|
+
});
|
|
58
|
+
if (!resp.ok)
|
|
59
|
+
continue;
|
|
60
|
+
const data = (await resp.json());
|
|
61
|
+
if (!data.messages || data.messages.length === 0)
|
|
62
|
+
continue;
|
|
63
|
+
symbolUsed = symbol;
|
|
64
|
+
for (const msg of data.messages) {
|
|
65
|
+
const createdAt = msg.created_at ? new Date(msg.created_at) : new Date();
|
|
66
|
+
if (createdAt < cutoff)
|
|
67
|
+
continue;
|
|
68
|
+
const body = msg.body ?? "";
|
|
69
|
+
const userSentiment = mapSentimentLabel(msg.entities?.sentiment?.basic);
|
|
70
|
+
// Combine Stocktwits native sentiment with our keyword-based scoring
|
|
71
|
+
const kwScore = sharedSentiment(body);
|
|
72
|
+
const finalScore = userSentiment === "bullish"
|
|
73
|
+
? Math.max(kwScore, 0.5)
|
|
74
|
+
: userSentiment === "bearish"
|
|
75
|
+
? Math.min(kwScore, -0.5)
|
|
76
|
+
: kwScore;
|
|
77
|
+
posts.push({
|
|
78
|
+
id: msg.id ?? 0,
|
|
79
|
+
body: body.slice(0, 500),
|
|
80
|
+
created_at: msg.created_at ?? isoNow(),
|
|
81
|
+
user: msg.user?.username ?? "unknown",
|
|
82
|
+
user_followers: msg.user?.followers ?? 0,
|
|
83
|
+
sentiment: userSentiment ?? sentimentLabel(finalScore),
|
|
84
|
+
sentiment_score: Math.round(finalScore * 100) / 100,
|
|
85
|
+
is_rumor: isRumor(body),
|
|
86
|
+
likes: msg.likes?.total ?? 0,
|
|
87
|
+
reshares: msg.reshares?.reshared_count ?? 0,
|
|
88
|
+
url: `https://stocktwits.com/symbol/${symbol}/message/${msg.id}`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// We got results; stop trying other formats
|
|
92
|
+
if (posts.length > 0)
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
// Try next format
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Sort by date descending
|
|
101
|
+
posts.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
102
|
+
// Build summary
|
|
103
|
+
const bullish = posts.filter((p) => p.sentiment === "bullish").length;
|
|
104
|
+
const bearish = posts.filter((p) => p.sentiment === "bearish").length;
|
|
105
|
+
const neutral = posts.length - bullish - bearish;
|
|
106
|
+
const avgSentiment = posts.length > 0
|
|
107
|
+
? Math.round((posts.reduce((s, p) => s + p.sentiment_score, 0) / posts.length) * 100) / 100
|
|
108
|
+
: 0;
|
|
109
|
+
// Top contributors
|
|
110
|
+
const userPosts = {};
|
|
111
|
+
for (const p of posts) {
|
|
112
|
+
if (!userPosts[p.user])
|
|
113
|
+
userPosts[p.user] = { count: 0, totalSentiment: 0 };
|
|
114
|
+
userPosts[p.user].count++;
|
|
115
|
+
userPosts[p.user].totalSentiment += p.sentiment_score;
|
|
116
|
+
}
|
|
117
|
+
const topContributors = Object.entries(userPosts)
|
|
118
|
+
.map(([user, data]) => ({
|
|
119
|
+
user,
|
|
120
|
+
posts: data.count,
|
|
121
|
+
avg_sentiment: Math.round((data.totalSentiment / data.count) * 100) / 100,
|
|
122
|
+
}))
|
|
123
|
+
.sort((a, b) => b.posts - a.posts)
|
|
124
|
+
.slice(0, 5);
|
|
125
|
+
return {
|
|
126
|
+
scraped_at: isoNow(),
|
|
127
|
+
ticker,
|
|
128
|
+
symbol_used: symbolUsed,
|
|
129
|
+
period_days: days,
|
|
130
|
+
format,
|
|
131
|
+
total_posts: posts.length,
|
|
132
|
+
posts,
|
|
133
|
+
summary: {
|
|
134
|
+
bullish_count: bullish,
|
|
135
|
+
bearish_count: bearish,
|
|
136
|
+
neutral_count: neutral,
|
|
137
|
+
total_count: posts.length,
|
|
138
|
+
bullish_pct: posts.length > 0 ? Math.round((bullish / posts.length) * 100) : 0,
|
|
139
|
+
bearish_pct: posts.length > 0 ? Math.round((bearish / posts.length) * 100) : 0,
|
|
140
|
+
avg_sentiment: avgSentiment,
|
|
141
|
+
top_contributors: topContributors,
|
|
142
|
+
},
|
|
143
|
+
rumor_count: posts.filter((p) => p.is_rumor).length,
|
|
144
|
+
overall_sentiment: avgSentiment,
|
|
145
|
+
overall_sentiment_label: sentimentLabel(avgSentiment),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// ── Test/sample data ─────────────────────────────────────────────
|
|
149
|
+
export function sampleData(ticker) {
|
|
150
|
+
const t = normalizeTicker(ticker || "BHP");
|
|
151
|
+
return {
|
|
152
|
+
scraped_at: isoNow(),
|
|
153
|
+
ticker: t,
|
|
154
|
+
symbol_used: `${t}.AX`,
|
|
155
|
+
period_days: 14,
|
|
156
|
+
format: "json",
|
|
157
|
+
total_posts: 5,
|
|
158
|
+
posts: [
|
|
159
|
+
{
|
|
160
|
+
id: 501234567,
|
|
161
|
+
body: `$${t}.AX about to moon! Insider buying before the ann. Load up! 🚀`,
|
|
162
|
+
created_at: "2026-02-25T10:30:00Z",
|
|
163
|
+
user: "AusTrader99",
|
|
164
|
+
user_followers: 1250,
|
|
165
|
+
sentiment: "bullish",
|
|
166
|
+
sentiment_score: 0.83,
|
|
167
|
+
is_rumor: true,
|
|
168
|
+
likes: 24,
|
|
169
|
+
reshares: 8,
|
|
170
|
+
url: `https://stocktwits.com/symbol/${t}.AX/message/501234567`,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 501234568,
|
|
174
|
+
body: `$${t}.AX strong quarterly results expected. Accumulating at these levels. Catalyst incoming.`,
|
|
175
|
+
created_at: "2026-02-24T14:15:00Z",
|
|
176
|
+
user: "ValueInvest_AU",
|
|
177
|
+
user_followers: 3400,
|
|
178
|
+
sentiment: "bullish",
|
|
179
|
+
sentiment_score: 0.67,
|
|
180
|
+
is_rumor: false,
|
|
181
|
+
likes: 45,
|
|
182
|
+
reshares: 12,
|
|
183
|
+
url: `https://stocktwits.com/symbol/${t}.AX/message/501234568`,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 501234569,
|
|
187
|
+
body: `$${t}.AX management dumping shares. This is the top. Short it.`,
|
|
188
|
+
created_at: "2026-02-23T08:45:00Z",
|
|
189
|
+
user: "BearishBob",
|
|
190
|
+
user_followers: 890,
|
|
191
|
+
sentiment: "bearish",
|
|
192
|
+
sentiment_score: -0.67,
|
|
193
|
+
is_rumor: false,
|
|
194
|
+
likes: 8,
|
|
195
|
+
reshares: 2,
|
|
196
|
+
url: `https://stocktwits.com/symbol/${t}.AX/message/501234569`,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: 501234570,
|
|
200
|
+
body: `$${t}.AX heard speculation about a potential takeover target. Unconfirmed but watching closely.`,
|
|
201
|
+
created_at: "2026-02-22T16:00:00Z",
|
|
202
|
+
user: "MarketWhispers",
|
|
203
|
+
user_followers: 5600,
|
|
204
|
+
sentiment: "bullish",
|
|
205
|
+
sentiment_score: 0.50,
|
|
206
|
+
is_rumor: true,
|
|
207
|
+
likes: 67,
|
|
208
|
+
reshares: 23,
|
|
209
|
+
url: `https://stocktwits.com/symbol/${t}.AX/message/501234570`,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: 501234571,
|
|
213
|
+
body: `$${t}.AX holding steady. Waiting for the next earnings report before making a move.`,
|
|
214
|
+
created_at: "2026-02-21T12:30:00Z",
|
|
215
|
+
user: "PatientCapital",
|
|
216
|
+
user_followers: 2100,
|
|
217
|
+
sentiment: "neutral",
|
|
218
|
+
sentiment_score: 0.0,
|
|
219
|
+
is_rumor: false,
|
|
220
|
+
likes: 12,
|
|
221
|
+
reshares: 3,
|
|
222
|
+
url: `https://stocktwits.com/symbol/${t}.AX/message/501234571`,
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
summary: {
|
|
226
|
+
bullish_count: 3,
|
|
227
|
+
bearish_count: 1,
|
|
228
|
+
neutral_count: 1,
|
|
229
|
+
total_count: 5,
|
|
230
|
+
bullish_pct: 60,
|
|
231
|
+
bearish_pct: 20,
|
|
232
|
+
avg_sentiment: 0.27,
|
|
233
|
+
top_contributors: [
|
|
234
|
+
{ user: "MarketWhispers", posts: 1, avg_sentiment: 0.50 },
|
|
235
|
+
{ user: "ValueInvest_AU", posts: 1, avg_sentiment: 0.67 },
|
|
236
|
+
{ user: "AusTrader99", posts: 1, avg_sentiment: 0.83 },
|
|
237
|
+
{ user: "BearishBob", posts: 1, avg_sentiment: -0.67 },
|
|
238
|
+
{ user: "PatientCapital", posts: 1, avg_sentiment: 0.0 },
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
rumor_count: 2,
|
|
242
|
+
overall_sentiment: 0.27,
|
|
243
|
+
overall_sentiment_label: "bullish",
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=stocktwits-asx-scraper.js.map
|