Playbook tech on website skill

Decides which named technologies a company actually runs on its website, verified against the live site.

by growthenginenowoslawski·MIT license·★ 736 Stars on the repo·GitHub ↗

Use now

Files of Playbook tech on website

growthenginenowoslawski/main1 file shown
SKILL.md
Show the full text247 lines

Playbook: Technology On Website

All rules here are best practice, not law. Override any of them when the campaign calls for it; note the best practice once and move on.

1. Trigger and scope

Use when: the offer only makes sense to companies running a specific piece of software on their site — a Shopify app, a Klaviyo migration, a HubSpot implementation, a WordPress maintenance offer. Works in both directions: discovery ("build me a list of every company on Shopify") and enrichment ("here are 4,000 domains, which run Klaviyo").

Do not use when: you want the software a company sells, or mail-infrastructure targeting.

Default output: tech_confirmed, tech_stack_verified (JSON array), tech_evidence, tech_checked_at. Abstain = false / [] / "". On request only: tech_on_website_line, 110 chars.

The sentence is opt-in. The job is the verdict: does this company run the technology, and how do we know. That is what a list gets gated on, and for most campaigns it is the entire deliverable. Write the clause only when the brief explicitly asks. Never let an unrequested sentence be the reason a row costs a model call.

The thing that makes this playbook necessary

A technographic database tells you what a crawler saw at some unstated point in the past, and it is wrong often enough to embarrass you.

In the graded test, 4 of the 10 companies a provider returned from its own Shopify filter were not on Shopify. Two were not ecommerce at all.

The homepage verifier is not a quality step. It is the gate. Nothing reaches copy without it. Plan for roughly 60% of a provider-sourced list to survive verification, so pull 1.7x what you need.

2. Run it: tandem discovery, then the gate

The two discovery lanes run IN TANDEM and the result is a union. Not a waterfall, not a choice.

Measured: two providers' first pages returned 74 domains and 60 domains with zero overlap. Whichever single provider you pick, you leave most of the market on the table.

Watch two things when you compare them:

  • A contact-oriented provider returns contacts, so page it with a per-company cap of 1. Never compare its raw count to a company-oriented provider's company count.
  • Both cap per filter combination (commonly 25,000 and 50,000). Shard by state or headcount band and let the union dedupe.

No cache. Recompute — it is cheap. Every source is free except the rendering proxy, which only touches rows flagged blocked, so re-verifying a domain a previous campaign saw costs one homepage GET.

# 1. TANDEM DISCOVERY. Both lanes, together.
discover-tech --provider a Shopify --all --out a.txt
discover-tech --provider b Shopify --all --out b.txt

# 1c. union + dedupe, printing the per-source overlap
union-domains a.txt b.txt --out domains.txt --provenance sources.csv

# 2. THE GATE
verify-tech-html domains.txt > verified.jsonl

# 3. rescue ONLY the blocked rows (metered, explicit confirm)
jq -r 'select(.blocked == true) | .domain' verified.jsonl > blocked.txt
proxy-rescue blocked.txt --confirm --max 500 > rescued.jsonl

# 4. merge: a rescued row replaces its blocked counterpart, one row per domain
jq -s -c 'group_by(.domain) | map(sort_by(.blocked) | .[0]) | .[]' \
  verified.jsonl rescued.jsonl > final.jsonl

# 5. put target_tech on every row
attach-row-context final.jsonl --target-tech Shopify > ready.jsonl

# 6. THE DEFAULT LAST STEP: the verdict. No model calls, no cost.
tech-verdict ready.jsonl > verdicts.jsonl

Stop there unless a sentence was requested. verdicts.jsonl is the deliverable.

3. The gate

A browser-UA homepage GET at concurrency 2, matching fingerprints over HTML and response headers. Two behaviors carry the whole playbook.

Blocked is a flag, never an inference

A bot wall is not an answer.

This is subtle and it bites: a common CDN matches on its own server: header, so a 403 used to come back as a successful fetch with a non-empty detection list — which reads downstream as "we looked and found nothing." A silent false abstain.

The verifier emits "blocked": true with a blocked_reason on HTTP 401/403/405/429/503, any other 4xx/5xx, a body under 2,000 bytes, or a transport error — and strips infrastructure and analytics hits out of the detection list.

Route on the flag, never on an empty list.

Platform hits are graded

A Shopify fingerprint fires on HTML-only strings like cdn.shopify.com and <store>.myshopify.com — which every Shopify agency portfolio, app-review roundup and Buy-Button B2B site also carries. So platform hits require header or oracle evidence:

Evidence Grade Result
response header (x-shopid, x-shopify-stage, powered-by: shopify) confirmed eligible for a clause
a technology oracle answered yes (/products.json, /wp-json/) confirmed eligible for a clause
HTML string only, no header, no oracle unconfirmed excluded from the stack, no clause

Only Shopify and WordPress have oracles today, so an HTML-only match on any other platform stays unconfirmed by design. That costs recall and buys correctness.

Three outcomes that are NOT negatives

Getting this wrong is how a campaign quietly mails the wrong people.

confidence Meaning Action
blocked the page was never read proxy rescue. Never a negative
unconfirmed matched HTML only — the agency / Buy-Button shape re-check by hand or with a tech-specific endpoint
api_error a model call failed; clause path only re-run those rows. A provider error is never a verdict

target_tech is read per row, with a global default only. If neither is set, the script should error rather than grade everything against the wrong technology.

Adding a technology

Never write a fingerprint from memory. Learn it from a live sample: rank candidate strings by specificity (support ≥30%, leakage ≤5%) against a control set.

⚠️ The control set must control for the co-occurring platform, not just "any website". A Klaviyo sample surfaced cdn.shopify.com at 78% support / 0% leakage — because most Klaviyo users are Shopify stores. Control against Shopify stores, not against the open web.

Then: never add a bare vendor domain (/klaviyo\.com/ matches every page that links to them); prefer response headers; a platform-kind technology also needs an oracle; and pass a 3-positives / 3-negatives ritual before marking it validated.

Downstream gate

If tech_confirmed is false: drop the clause through spintax and keep the row — unless the whole campaign premise is the technology (a Shopify app), in which case exclude the row rather than send a generic email a company will read as a mistake. The brief must state which applies before the list is built.

4. Locked prompt (OPT-IN)

Skip this whole section by default. If the brief did not ask for a personalization line, the run ends at the verdict: no prompt, no model calls, no guards.

Model: gpt-4o-mini inside Clay, a nano-class model elsewhere. Note the inversion: nano's ~512 reasoning tokens make it the more expensive option for this prompt inside Clay. Params: max_completion_tokens=2000, reasoning_effort="low", no temperature, JSON response format.

Everything above PER-ROW DATA is the static prefix and must stay byte-identical. It took five correction rounds to get clean, so start from this version rather than from scratch.

You write one short clause for a cold email, based only on technology that was verified on a company website.

Return JSON only: {"line": "...", "evidence": "...", "confidence": "high|low"}

Rules:
- The clause must read grammatically inside this exact sentence: "Noticed LINE."
- Write it in second person. Always "you" or "your". Never "our", "we", "they", or the company name.
- The clause must contain a verb.
- Do not include the word Noticed. The clause is only the part that follows it.
- Name each product at most once. Never repeat a product name inside one clause.
- Spell every product name exactly as it appears in VERIFIED_TECH, including its capital letters.
- Do not capitalize the first word unless that word is a product name.
- No trailing period. Maximum 110 characters.
- 5th-grade reading level. No em dashes. No exclamation marks.
- Only name technology that appears in VERIFIED_TECH. Never infer a tool that is not listed.
- If VERIFIED_TECH does not contain the technology named in TARGET_TECH, return "" for line and "none" for confidence.
- Copy EVIDENCE into the "evidence" key exactly as given. Never put words from EVIDENCE into "line".
- Banned words in "line": header, html, tag, script, pixel, scraped, crawled, detected, database, tool, stack.
- When a second tool is listed alongside the target, name both. Two named tools read as real research, one reads as a guess.

Examples:
Input: TARGET_TECH: Shopify | COMPANY: Bombas | VERIFIED_TECH: Shopify, Klaviyo, Gorgias | EVIDENCE: x-shopid
Output: {"line":"you run Klaviyo and Gorgias on top of your Shopify store","evidence":"x-shopid","confidence":"high"}
Input: TARGET_TECH: Shopify | COMPANY: Leverify | VERIFIED_TECH: Shopify | EVIDENCE: x-shopid
Output: {"line":"your storefront runs on Shopify","evidence":"x-shopid","confidence":"high"}
Input: TARGET_TECH: Shopify | COMPANY: Lulu and Georgia | VERIFIED_TECH: Shopify, Klaviyo, Attentive, Zendesk | EVIDENCE: x-shopid
Output: {"line":"you send with Klaviyo and Attentive on your Shopify store","evidence":"x-shopid","confidence":"high"}
Input: TARGET_TECH: HubSpot | COMPANY: Northwind Logistics | VERIFIED_TECH: HubSpot, WordPress | EVIDENCE: js.hs-scripts.com
Output: {"line":"you run your site on WordPress with HubSpot behind the forms","evidence":"js.hs-scripts.com","confidence":"high"}
Input: TARGET_TECH: Shopify | COMPANY: Acme Consulting | VERIFIED_TECH: WordPress, HubSpot | EVIDENCE: none
Output: {"line":"","evidence":"","confidence":"none"}

PER-ROW DATA (appended last)
TARGET_TECH: {{target_tech}} | COMPANY: {{Company Name}} | VERIFIED_TECH: {{Tech Verified}} | EVIDENCE: {{tech_evidence}}

Post-guards run in code after the model, because a prompt rule is a request and a regex is a guarantee. A rejected row ships the abstain value and records why.

No second verifier model call: the claim is already grounded in a live fetch of the company's own site seconds earlier. That is a genuinely stronger guarantee than an LLM judge, and it is why this playbook does not have one.

Truncation guard: finish_reason=length means retry, never abstain.

5. Verification

VERDICT: PASS 10/10 (100%) | tandem discovery, unioned → homepage gate → verdict | $0.00/1k on the default verdict path, ~$0.25/1k when the clause is requested.

The number to carry into planning: the provider's raw precision was 6/10. The path scores 100% because the gate catches the other 4.

⚠️ This verdict covers Shopify only. Every other fingerprint is unvalidated. Re-run if the confirmed rate drops below 50% for two consecutive campaigns, if Shopify stops setting its identifying header, or when adding an unvalidated technology.

6. Clay implementation

  • clay-table.md — the column build, including how to reach a verifier from Clay.
  • clay-workflow.md — the CLI-buildable version.

7. Hard rules

  • A blocked row is never a negative. blocked: true means "we could not look", not "there is nothing there". Whatever you persist must keep blocked and blocked_reason beside the verdict, or a bot-wall 403 reads as a confident "no technology found". Same for unconfirmed and api_error.
  • Exact array-element match, never a substring test. "Shopify Buy Button".includes("Shopify") is true — and that is exactly how a B2B software company gets a storefront clause. Drop embed-kind hits before the model. A platform-kind target additionally needs header or oracle evidence.
  • Only one fingerprint has been validated end to end. Never let a client hear the tested number applied to an untested fingerprint.
  • No output sentence unless the operator asks.
  • Pacing and targeting: the rendering proxy runs only on blocked rows — that is an accuracy rule, not a budget one. Count before you pull where counts are free. Shard past per-filter caps rather than paging into them. Browser User-Agent on every homepage fetch, concurrency 2.
  • Sequencer update-in-place usually requires the email address in the request body, or the write silently no-ops and every row keeps its old clause. Upload responses over-count — true net-new is a lead-count delta (often returned as a string, so cast it).
1---
2name: playbook-tech-on-website
3description: Decides which named technologies a company actually runs on its website, verified against the live site. Triggers on "find companies using Shopify", "they're on HubSpot", "tech stack targeting", "technographics", "who uses Klaviyo", "companies running WordPress". Outputs a per-domain verdict (tech_confirmed plus the verified stack and its evidence); the copy-ready clause is OFF by default and runs only when the operator asks for one.
4---
5 
6# Playbook: Technology On Website
7 
8> All rules here are best practice, not law. Override any of them when the campaign calls for it; note the best practice once and move on.
9 
10## 1. Trigger and scope
11 
12**Use when:** the offer only makes sense to companies running a specific piece of software on their
13site — a Shopify app, a Klaviyo migration, a HubSpot implementation, a WordPress maintenance offer.
14Works in both directions: **discovery** ("build me a list of every company on Shopify") and
15**enrichment** ("here are 4,000 domains, which run Klaviyo").
16 
17**Do not use when:** you want the software a company *sells*, or mail-infrastructure targeting.
18 
19**Default output:** `tech_confirmed`, `tech_stack_verified` (JSON array), `tech_evidence`,
20`tech_checked_at`. Abstain = `false` / `[]` / `""`.
21**On request only:** `tech_on_website_line`, 110 chars.
22 
23> **The sentence is opt-in.** The job is the **verdict**: does this company run the technology, and
24> how do we know. That is what a list gets gated on, and for most campaigns it is the entire
25> deliverable. Write the clause only when the brief explicitly asks. **Never let an unrequested
26> sentence be the reason a row costs a model call.**
27 
28### The thing that makes this playbook necessary
29 
30**A technographic database tells you what a crawler saw at some unstated point in the past, and it
31is wrong often enough to embarrass you.**
32 
33In the graded test, **4 of the 10 companies a provider returned from its own Shopify filter were not
34on Shopify. Two were not ecommerce at all.**
35 
36The homepage verifier is not a quality step. **It is the gate.** Nothing reaches copy without it.
37Plan for roughly **60% of a provider-sourced list to survive verification, so pull 1.7x what you
38need.**
39 
40## 2. Run it: tandem discovery, then the gate
41 
42**The two discovery lanes run IN TANDEM and the result is a union.** Not a waterfall, not a choice.
43 
44Measured: two providers' first pages returned **74 domains and 60 domains with zero overlap.**
45Whichever single provider you pick, you leave most of the market on the table.
46 
47Watch two things when you compare them:
48 
49- A contact-oriented provider returns **contacts**, so page it with a per-company cap of 1. **Never
50 compare its raw count to a company-oriented provider's company count.**
51- Both cap per filter combination (commonly 25,000 and 50,000). **Shard by state or headcount band**
52 and let the union dedupe.
53 
54**No cache. Recompute — it is cheap.** Every source is free except the rendering proxy, which only
55touches rows flagged `blocked`, so re-verifying a domain a previous campaign saw costs one homepage
56GET.
57 
58```bash
59# 1. TANDEM DISCOVERY. Both lanes, together.
60discover-tech --provider a Shopify --all --out a.txt
61discover-tech --provider b Shopify --all --out b.txt
62 
63# 1c. union + dedupe, printing the per-source overlap
64union-domains a.txt b.txt --out domains.txt --provenance sources.csv
65 
66# 2. THE GATE
67verify-tech-html domains.txt > verified.jsonl
68 
69# 3. rescue ONLY the blocked rows (metered, explicit confirm)
70jq -r 'select(.blocked == true) | .domain' verified.jsonl > blocked.txt
71proxy-rescue blocked.txt --confirm --max 500 > rescued.jsonl
72 
73# 4. merge: a rescued row replaces its blocked counterpart, one row per domain
74jq -s -c 'group_by(.domain) | map(sort_by(.blocked) | .[0]) | .[]' \
75 verified.jsonl rescued.jsonl > final.jsonl
76 
77# 5. put target_tech on every row
78attach-row-context final.jsonl --target-tech Shopify > ready.jsonl
79 
80# 6. THE DEFAULT LAST STEP: the verdict. No model calls, no cost.
81tech-verdict ready.jsonl > verdicts.jsonl
82```
83 
84**Stop there unless a sentence was requested.** `verdicts.jsonl` is the deliverable.
85 
86## 3. The gate
87 
88A browser-UA homepage GET at concurrency 2, matching fingerprints over **HTML and response
89headers**. Two behaviors carry the whole playbook.
90 
91### Blocked is a flag, never an inference
92 
93**A bot wall is not an answer.**
94 
95This is subtle and it bites: a common CDN matches on its own `server:` header, so a 403 used to come
96back as a *successful* fetch with a non-empty detection list — which reads downstream as "we looked
97and found nothing." **A silent false abstain.**
98 
99The verifier emits `"blocked": true` with a `blocked_reason` on HTTP 401/403/405/429/503, any other
1004xx/5xx, **a body under 2,000 bytes**, or a transport error — and strips infrastructure and
101analytics hits out of the detection list.
102 
103**Route on the flag, never on an empty list.**
104 
105### Platform hits are graded
106 
107A `Shopify` fingerprint fires on HTML-only strings like `cdn.shopify.com` and
108`<store>.myshopify.com` — **which every Shopify agency portfolio, app-review roundup and Buy-Button
109B2B site also carries.** So platform hits require header or oracle evidence:
110 
111| Evidence | Grade | Result |
112|---|---|---|
113| response header (`x-shopid`, `x-shopify-stage`, `powered-by: shopify`) | **confirmed** | eligible for a clause |
114| a technology oracle answered yes (`/products.json`, `/wp-json/`) | **confirmed** | eligible for a clause |
115| HTML string only, no header, no oracle | **unconfirmed** | excluded from the stack, no clause |
116 
117Only Shopify and WordPress have oracles today, so an HTML-only match on any other platform stays
118`unconfirmed` **by design. That costs recall and buys correctness.**
119 
120### Three outcomes that are NOT negatives
121 
122Getting this wrong is how a campaign quietly mails the wrong people.
123 
124| `confidence` | Meaning | Action |
125|---|---|---|
126| `blocked` | **the page was never read** | proxy rescue. **Never a negative** |
127| `unconfirmed` | matched HTML only — the agency / Buy-Button shape | re-check by hand or with a tech-specific endpoint |
128| `api_error` | a model call failed; **clause path only** | re-run those rows. **A provider error is never a verdict** |
129 
130`target_tech` is read **per row**, with a global default only. If neither is set, the script should
131**error rather than grade everything against the wrong technology.**
132 
133### Adding a technology
134 
135**Never write a fingerprint from memory.** Learn it from a live sample: rank candidate strings by
136specificity (support ≥30%, leakage ≤5%) against a control set.
137 
138⚠️ **The control set must control for the co-occurring platform, not just "any website".** A Klaviyo
139sample surfaced `cdn.shopify.com` at **78% support / 0% leakage** — because most Klaviyo users are
140Shopify stores. Control against Shopify stores, not against the open web.
141 
142Then: never add a bare vendor domain (`/klaviyo\.com/` matches every page that links to them);
143prefer response headers; a platform-kind technology also needs an oracle; and pass a
1443-positives / 3-negatives ritual before marking it validated.
145 
146### Downstream gate
147 
148If `tech_confirmed` is false: drop the clause through spintax and keep the row — **unless the whole
149campaign premise is the technology** (a Shopify app), in which case exclude the row rather than send
150a generic email a company will read as a mistake. **The brief must state which applies before the
151list is built.**
152 
153## 4. Locked prompt (OPT-IN)
154 
155> **Skip this whole section by default.** If the brief did not ask for a personalization line, the
156> run ends at the verdict: no prompt, no model calls, no guards.
157 
158Model: `gpt-4o-mini` inside Clay, a nano-class model elsewhere. Note the inversion: **nano's ~512
159reasoning tokens make it the *more* expensive option for this prompt inside Clay.** Params:
160`max_completion_tokens=2000`, `reasoning_effort="low"`, no `temperature`, JSON response format.
161 
162Everything above `PER-ROW DATA` is the static prefix and must stay byte-identical. **It took five
163correction rounds to get clean, so start from this version rather than from scratch.**
164 
165```text
166You write one short clause for a cold email, based only on technology that was verified on a company website.
167 
168Return JSON only: {"line": "...", "evidence": "...", "confidence": "high|low"}
169 
170Rules:
171- The clause must read grammatically inside this exact sentence: "Noticed LINE."
172- Write it in second person. Always "you" or "your". Never "our", "we", "they", or the company name.
173- The clause must contain a verb.
174- Do not include the word Noticed. The clause is only the part that follows it.
175- Name each product at most once. Never repeat a product name inside one clause.
176- Spell every product name exactly as it appears in VERIFIED_TECH, including its capital letters.
177- Do not capitalize the first word unless that word is a product name.
178- No trailing period. Maximum 110 characters.
179- 5th-grade reading level. No em dashes. No exclamation marks.
180- Only name technology that appears in VERIFIED_TECH. Never infer a tool that is not listed.
181- If VERIFIED_TECH does not contain the technology named in TARGET_TECH, return "" for line and "none" for confidence.
182- Copy EVIDENCE into the "evidence" key exactly as given. Never put words from EVIDENCE into "line".
183- Banned words in "line": header, html, tag, script, pixel, scraped, crawled, detected, database, tool, stack.
184- When a second tool is listed alongside the target, name both. Two named tools read as real research, one reads as a guess.
185 
186Examples:
187Input: TARGET_TECH: Shopify | COMPANY: Bombas | VERIFIED_TECH: Shopify, Klaviyo, Gorgias | EVIDENCE: x-shopid
188Output: {"line":"you run Klaviyo and Gorgias on top of your Shopify store","evidence":"x-shopid","confidence":"high"}
189Input: TARGET_TECH: Shopify | COMPANY: Leverify | VERIFIED_TECH: Shopify | EVIDENCE: x-shopid
190Output: {"line":"your storefront runs on Shopify","evidence":"x-shopid","confidence":"high"}
191Input: TARGET_TECH: Shopify | COMPANY: Lulu and Georgia | VERIFIED_TECH: Shopify, Klaviyo, Attentive, Zendesk | EVIDENCE: x-shopid
192Output: {"line":"you send with Klaviyo and Attentive on your Shopify store","evidence":"x-shopid","confidence":"high"}
193Input: TARGET_TECH: HubSpot | COMPANY: Northwind Logistics | VERIFIED_TECH: HubSpot, WordPress | EVIDENCE: js.hs-scripts.com
194Output: {"line":"you run your site on WordPress with HubSpot behind the forms","evidence":"js.hs-scripts.com","confidence":"high"}
195Input: TARGET_TECH: Shopify | COMPANY: Acme Consulting | VERIFIED_TECH: WordPress, HubSpot | EVIDENCE: none
196Output: {"line":"","evidence":"","confidence":"none"}
197 
198PER-ROW DATA (appended last)
199TARGET_TECH: {{target_tech}} | COMPANY: {{Company Name}} | VERIFIED_TECH: {{Tech Verified}} | EVIDENCE: {{tech_evidence}}
200```
201 
202**Post-guards run in code after the model, because a prompt rule is a request and a regex is a
203guarantee.** A rejected row ships the abstain value and records why.
204 
205**No second verifier model call:** the claim is already grounded in a live fetch of the company's own
206site seconds earlier. That is a genuinely stronger guarantee than an LLM judge, and it is why this
207playbook does not have one.
208 
209**Truncation guard:** `finish_reason=length` means retry, never abstain.
210 
211## 5. Verification
212 
213**VERDICT: PASS 10/10 (100%)** | tandem discovery, unioned → homepage gate → verdict |
214**$0.00/1k on the default verdict path**, ~$0.25/1k when the clause is requested.
215 
216**The number to carry into planning: the provider's raw precision was 6/10. The path scores 100%
217because the gate catches the other 4.**
218 
219⚠️ **This verdict covers Shopify only.** Every other fingerprint is unvalidated. Re-run if the
220confirmed rate drops below 50% for two consecutive campaigns, if Shopify stops setting its
221identifying header, or when adding an unvalidated technology.
222 
223## 6. Clay implementation
224 
225- **`clay-table.md`** — the column build, including how to reach a verifier from Clay.
226- **`clay-workflow.md`** — the CLI-buildable version.
227 
228## 7. Hard rules
229 
230- **A blocked row is never a negative.** `blocked: true` means "we could not look", not "there is
231 nothing there". Whatever you persist must keep `blocked` and `blocked_reason` **beside** the
232 verdict, or a bot-wall 403 reads as a confident "no technology found". Same for `unconfirmed` and
233 `api_error`.
234- **Exact array-element match, never a substring test.** `"Shopify Buy Button".includes("Shopify")`
235 is `true` — **and that is exactly how a B2B software company gets a storefront clause.** Drop
236 embed-kind hits before the model. A platform-kind target additionally needs header or oracle
237 evidence.
238- **Only one fingerprint has been validated end to end.** Never let a client hear the tested number
239 applied to an untested fingerprint.
240- **No output sentence unless the operator asks.**
241- **Pacing and targeting:** the rendering proxy runs only on blocked rows — that is an accuracy rule,
242 not a budget one. Count before you pull where counts are free. Shard past per-filter caps rather
243 than paging into them. Browser User-Agent on every homepage fetch, concurrency 2.
244- **Sequencer update-in-place usually requires the email address in the request body**, or the write
245 silently no-ops and every row keeps its old clause. Upload responses over-count — true net-new is
246 a lead-count delta (often returned as a string, so cast it).
247 

Discussion