Skip to content
Back to skills

Research Comparables

ASecurity

Finds what one model competes with on Etsy and writes the weighted market band and our margin at it, by naming the product, reading a market summary, writing the word rules that score every listing, and reviewing the heaviest sellers once. Use as the research stage once meta.json, analysis.json, quote.json and renders/draft_00.png exist and comparables.json does not. Not for choosing a price (propose-price) or for copy (write-listing).

  • 6 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 16, 2026
ai-agentspythongobashapi

Works with

  • api

Security analysis

A100/100

Pro scans all 20 files and shows the line behind each finding

Scanned October 16, 2026

npx -y skills add 3DAPI-Inc/dapilot --skill research-comparables --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Research Comparables?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Research Comparables
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/3dapi-inc-research-comparables/badge)](https://www.skillsdirectory.com/skills/3dapi-inc-research-comparables)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: research-comparables
description: Finds what one model competes with on Etsy and writes the weighted market band and our margin at it, by naming the product, reading a market summary, writing the word rules that score every listing, and reviewing the heaviest sellers once. Use as the research stage once meta.json, analysis.json, quote.json and renders/draft_00.png exist and comparables.json does not. Not for choosing a price (propose-price) or for copy (write-listing).
allowed-tools: Read, Bash, Write
---

# research-comparables

## When
A brief with `stage: research`. The work dir has `meta.json`, `analysis.json`,
`quote.json` and `renders/draft_00.png`, and no `comparables.json`.

## Inputs
- `meta.json`: the original filename. A witness, not the verdict: it is
  what the uploader called the file, and may be a code, a printer name or
  a lie
- `analysis.json`: `bbox_mm`, `bodies`
- `renders/draft_00.png` and one more draft: what it looks like
- `thumbnails/plate_1.png` and `thumbnails/top_1.png` when they exist: the
  designer's own renders out of the 3MF, in colour and as arranged on the
  bed. The best witness there is; absent for STL and OBJ uploads
- `quote.json`: `unit_price`, `batch`, `metrics.model_g`
- `research/summary.json`, written in step 3 and refreshed in step 4: the
  only research file the judgements read. The compact `research/etsy.json`
  and the full file exist for the scripts; the log's counts come from stdout.

Environment: `ETSY_KEYSTRING`, `ETSY_SHARED_SECRET`, `DAPILOT_STORE_DIR`; a
script fails with a one-line JSON error when one is missing. Pass it on.

## Procedure
1. If `comparables.json` exists, print a `cached:true` result and stop.
2. Judge: decide what the object is. Read the thumbnails when there are
   any, two drafts, `analysis.json`, and last the filename. The pictures
   and the size say what it is; the filename only corroborates or
   contradicts. From that, name the product: the noun (one word, singular,
   the word sellers put in the title), a buyer query of two or three words
   containing it, and the taxonomy id of the aisle the product is sold in,
   from `$PY ${CLAUDE_PLUGIN_ROOT}/scripts/etsy_search.py taxonomy find "<word>"`.
   The id is not optional when one fits: the ladder reads the whole aisle,
   and without an aisle it reads every product that carries the noun.
   Also decide how sure you are, as `identity` in `rules.json` (step 4):
   `object` (what it is, a few words), `confidence` of `high`, `medium` or
   `low`, `witnesses` (which of `filename`, `drafts`, `thumbnails`,
   `analysis` supported the reading) and a one-sentence `reason`. How to
   choose all of it, and what each confidence level means:
   `reference/search-guide.md`.
3. Run, with the defaults for the ladder and its stop rule:
   `$PY ${CLAUDE_PLUGIN_ROOT}/scripts/etsy_search.py search "<query>" --noun <noun> [--taxonomy-id N] --analysis $WORKDIR/analysis.json --currency USD --out $WORKDIR/research/etsy.json`
   The search fetches by price window, each in Etsy's own relevance order: one
   query is one call per window, and the windows are the `pages` it reports.
   Exit 2 means nothing survived: run one broader query as the guide says,
   once. Exit 1: `ok:false`, reason from stderr. Then run
   `$PY ${CLAUDE_PLUGIN_ROOT}/scripts/market_stats.py --workdir $WORKDIR --summary research/summary.json`
   which writes the market summary without scoring anything.
4. Judge: write `research/rules.json` from `research/summary.json` and the
   drafts, schema under Outputs, following the guide: `required_any` names
   the mechanism or kind, `excluded` the neighbouring products,
   `materials_allowed` and `materials_excluded` come from `materials_top`,
   `notes` is one sentence. Words only: the relevance threshold is the
   script's default and this skill never writes one. Then run, in order:
   `$PY ${CLAUDE_PLUGIN_ROOT}/scripts/etsy_search.py score --full $WORKDIR/research/etsy.full.json --rules $WORKDIR/research/rules.json --out $WORKDIR/research/etsy.json`
   `$PY ${CLAUDE_PLUGIN_ROOT}/scripts/market_stats.py --workdir $WORKDIR --rules research/rules.json --summary research/summary.json`
   The second writes `comparables.json` and `viability.json` and rewrites
   the summary with `relevance` and `excluded_by` on every title. Exit 2
   means no comparable had a price: see Failure. Then judge, once: in
   `top_by_market_weight` of `research/summary.json`: once rules exist it
   lists the selected comparables only (each row carries `selected: true`;
   the heaviest rejects sit apart under `top_rejected_by_market_weight`),
   heaviest first. Against the drafts, count how many of the first 20 are
   not our product. A set of our product is ours: each row shows `units`
   (how many one purchase brings, read from the title, the variants or the
   description) and `unit_price`, the band is per unit, and the market's
   usual pack becomes our offer (`viability.json` `offer`). Only a bundle
   of different products counts as not ours, and it is removed by naming
   the other product, never by "set" or "pack" words. A row with `units`
   null said set without a count; it stays out of the band and is listed
   under `market.unknown_units`, which the log mentions when it is not empty.
   Tighten when more than 4 of 20 are not ours, or when fewer than 10 are
   listed and more than 1 is not: add words to `excluded`, `required_any`
   or `materials_excluded` as the guide says, nothing else, and rerun the
   two scripts once. Never a third pass.
5. Append the log paragraph: the object and the confidence with its reason,
   noun, query, taxonomy id, windows and stop reason,
   fetched, kept, scored, selected, whether the rules were tightened and why,
   weighted median and p75 per unit, basis, the offer (`offer.units` and
   `offer.modal_share`, and `offer.market.median` when units is more than
   one), `market.thin` and `market.weight_basis`
   as printed, the heaviest shop and its `top_shop_share` when that is 0.5
   or more, unit cost and the offer's cost,
   break-even at the offer, margin at the median, verdict, quota left. Remove `.lock`.
   Print the result line with the verdict in `summary`; when confidence is
   `low`, the summary starts with "identity low:" and the object, so the
   report and the console say it before they say the verdict.

## Outputs
- `research/etsy.json` and `research/etsy.full.json`: the search, with `noun`,
  `pages` (one per price window), `stop_reason`, and after scoring `relevance`,
  `market_weight` and `excluded_by` per record.
- `research/summary.json`: counts by material class, `materials_top`, price
  quantiles and histogram, `top_by_market_weight`, `sample_every_kth`.
- `research/rules.json`, the one file this skill writes by hand:
  ```json
  {"noun": "duck", "required_any": ["charm", "mini", "miniature"],
   "excluded": ["set of", "bundle", "keychain", "articulated"],
   "materials_allowed": ["pla", "resin", "3d printed"], "materials_excluded": ["clay", "metal", "wood"],
   "taxonomy_id": 1799, "notes": "one sentence",
   "identity": {"object": "mini duck charm", "confidence": "high",
                "witnesses": ["filename", "thumbnails", "drafts", "analysis"],
                "reason": "one sentence: what settled it, or what is still open"}}
  ```
  `market_stats.py` refuses a `confidence` that is not `high`, `medium` or
  `low`, or a witness outside the four names.
- `comparables.json`, by `market_stats.py`: `mode: "rules"`, the rules used,
  `identity` copied from them, `n_scored`, `n_selected`, `comparables`
  ordered by `market_weight` descending with `relevance`, `page` and a
  `shop` block on each, `rejected_count`, `rejected_sample`.
- `viability.json`, schema as printed by `market_stats.py`: `market` with
  `basis`, `weighted` and `unweighted` bands per unit of sale
  (`price_basis: per_unit`; `per_listing` keeps what buyers pay at
  checkout, `unknown_units` the listings left out), `spread_ratio` and `thin`,
  `weight_basis` (`units_per_day`, or `legacy` for a file scored without
  the review pass) and `weight_concentration` (the heaviest listing's and
  shop's share of the weight), per-gram; `offer`: the unit of sale we list
  (`units`, the market's modal pack by weight when it holds `adopt_share`
  of it, else 1; `distribution`, `extras_common`, and `market`, the band at
  that pack); unit cost, fees, margins and break-even per unit and, in each
  scenario's `offer` block, per pack; `identity` as in `comparables.json`,
  and a `verdict` of `viable`, `thin`, `not_viable` or `unreliable`, judged
  at the offer. Paths in it are relative to the work dir.

## Gate
Auto. Etsy reads only; no 3DAPI call. The one spend is Etsy quota: one search call
per price window, one batch call per 100 survivors, one review call per
distinct shop among the survivors, one inventory call per survivor with
variants. At most two searches per item.

## Failure
- Missing Etsy credentials or `DAPILOT_STORE_DIR`: a script exits 1 with a one-line
  JSON error; `ok:false`, that line as `reason`.
- HTTP 429 after the script's own retry: `ok:false`, "etsy rate limited"; the ledger retries once.
- Nothing survives two searches: still write `rules.json` with the noun and
  the guide's default lists, run the two scripts of step 4 so that an empty
  `comparables.json` and a `viability.json` with `ok:false` exist, and
  return `ok:true` with `needs_human` "no priced comparables for <query>;
  skip the model or supply a query".
- `market_stats.py` exits 2 with listings kept: the rules selected nothing. That
  is the one rerun of step 4, removing words instead of adding them, guided by
  `rejected_sample`. If it exits 2 again, finish as the case above.
- `quote.json` without `metrics.model_g`: per-gram fields are null, not a failure.

## Do not
- Do not judge listings one by one. Rules score them; you review twenty titles, once.
- Do not write a threshold, a ratio or any number into `rules.json` other
  than the taxonomy id. Words are the only lever.
- Do not edit `comparables.json` by hand or put a number anywhere a script
  did not print. A thin band is reported from `market.thin`, never worked
  out in prose and never fixed by changing the rules.
- Do not read `research/etsy.json` or the full file; the summary is the view.
- Do not let the filename decide. "test2.stl" is no evidence and
  "dragon_v3.3mf" is one witness; the pictures are the evidence, and a
  reading the pictures do not support is `low` however the file is named.
- Do not propose a price, search more than twice, or run the rules a third time.
- Do not put "set of", "pack", "pcs" or "bundle" in `excluded`. Sets of our
  product are the market and are read per unit; a bundle of other products
  is excluded by the other product's name.

`$PY` is `${DAPILOT_PYTHON:-python3}`.

Files in this skill

  • SKILL.md10.6 KB
  • eval/cases.md12.8 KB
  • eval/expected.duck-charm.deep.comparables.json11.9 KB
  • eval/expected.duck-charm.deep.price.rejected.json2.9 KB
  • eval/expected.duck-charm.deep.research.rules.json690 B
  • eval/expected.duck-charm.deep.research.summary.json14.6 KB
  • eval/expected.duck-charm.deep.viability.json3.5 KB
  • eval/expected.no-noun-match.deep.comparables.json775 B
  • eval/expected.no-noun-match.deep.research.etsy.json963 B
  • eval/expected.no-noun-match.deep.viability.json142 B
  • eval/expected.octagon-fidget.comparables.json1.1 KB
  • eval/expected.octagon-fidget.deep.comparables.json8.1 KB
  • eval/expected.octagon-fidget.deep.price.candidates.json2.7 KB
  • eval/expected.octagon-fidget.deep.research.rules.json782 B
  • eval/expected.octagon-fidget.deep.research.rules.pass1.json805 B
  • eval/expected.octagon-fidget.deep.research.summary.json10.5 KB
  • eval/expected.octagon-fidget.deep.viability.json3.6 KB
  • eval/expected.octagon-fidget.price.candidates.json1.5 KB
  • eval/expected.octagon-fidget.viability.json2.3 KB
  • reference/platform-fees.json2.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.