Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Patent search, classification, landscape analysis, and prior art mining
.claude/skills/brycewang-stanford-patent-analysis-guide/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 135% | 0% |
| case-20 | ✓→✗ | ▼ Worse | 157% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 86% | 0% |
| case-07 | ✓→✓ | = Same ✓ | 298% | 0% |
| case-08 | ✓→✓ | = Same ✓ | 573% | 0% |
A skill for conducting patent research, landscape analysis, and prior art searches. Covers patent database APIs, classification systems, citation network analysis, claim parsing, and technology trend mapping for intellectual property research.
| Database | Coverage | API | Cost | |----------|----------|-----|------| | USPTO PatentsView | US patents and applications | REST API, bulk download | Free | | EPO Open Patent Services | EP, WO, and 100+ offices | REST API (OPS) | Free (throttled) | | Google Patents | 120M+ documents worldwide | BigQuery (Google Patents Public) | Free (BigQuery costs) | | Lens.org | 130M+ patent records | REST API | Free for researchers | | WIPO PATENTSCOPE | PCT applications + national | REST API | Free |
pythonimport requests import xml.etree.ElementTree as ET class EPOClient: """Client for the EPO Open Patent Services (OPS) API.""" BASE_URL = "https://ops.epo.org/3.2/rest-services" def __init__(self, consumer_key: str, consumer_secret: str): self.token = self._authenticate(consumer_key, consumer_secret) def _authenticate(self, key: str, secret: str) -> str: import base64 credentials = base64.b64encode(f"{key}:{secret}".encode()).decode() resp = requests.post( "https://ops.epo.org/3.2/auth/accesstoken", headers={"Authorization": f"Basic {credentials}"}, data={"grant_type": "client_credentials"}, ) return resp.json()["access_token"] def search(self, cql_query: str, max_results: int = 25) -> list[dict]: """ Search patents using CQL (Common Query Language). Example queries: ta="machine learning" AND cl="neural network" pa="university" AND pd>=2020 """ resp = requests.get( f"{self.BASE_URL}/published-data/search", headers={"Authorization": f"Bearer {self.token}", "Accept": "application/json"}, params={"q": cql_query, "Range": f"1-{max_results}"}, ) return resp.json()
The CPC hierarchy has five levels: Section > Class > Subclass > Group > Subgroup.
Example: H04L 9/3247
H = Electricity (Section)
H04 = Electric communication technique (Class)
H04L = Transmission of digital information (Subclass)
H04L 9/ = Cryptographic mechanisms (Group)
H04L 9/3247 = Digital signatures (Subgroup)pythondef parse_cpc_code(code: str) -> dict: """Parse a CPC classification code into its hierarchical components.""" code = code.strip().replace(" ", "") return { "section": code[0], "class": code[:3], "subclass": code[:4], "group": code.split("/")[0] if "/" in code else code[:4], "subgroup": code if "/" in code else None, "full": code, } # Technology domain mapping (top-level CPC sections) CPC_SECTIONS = { "A": "Human Necessities", "B": "Performing Operations; Transporting", "C": "Chemistry; Metallurgy", "D": "Textiles; Paper", "E": "Fixed Constructions", "F": "Mechanical Engineering; Lighting; Heating", "G": "Physics", "H": "Electricity", "Y": "General Tagging of New Technological Developments", }
A patent landscape maps the technology and competitive environment in a domain:
pythonimport pandas as pd import numpy as np from collections import Counter def patent_landscape_metrics(patents: pd.DataFrame) -> dict: """ Compute patent landscape metrics from a patent dataset. Expected columns: patent_id, filing_date, grant_date, assignee, cpc_codes (list), claims_count, citations_received """ # Filing trend (annual) patents["filing_year"] = pd.to_datetime(patents.filing_date).dt.year annual_filings = patents.groupby("filing_year").size() # Top assignees top_assignees = patents.assignee.value_counts().head(20) # Technology distribution (CPC subclass level) all_cpc = [] for codes in patents.cpc_codes: all_cpc.extend([c[:4] for c in codes]) cpc_distribution = Counter(all_cpc).most_common(20) # Citation impact citation_stats = patents.citations_received.describe() # Geographic distribution (from assignee country) geo_dist = patents.assignee_country.value_counts() return { "total_patents": len(patents), "annual_filings": annual_filings.to_dict(), "top_assignees": top_assignees.to_dict(), "technology_areas": cpc_distribution, "citation_stats": citation_stats.to_dict(), "geographic_distribution": geo_dist.head(10).to_dict(), }
pythonimport networkx as nx def build_citation_network(patents: pd.DataFrame, citations: pd.DataFrame) -> nx.DiGraph: """ Build a patent citation network. citations: DataFrame with columns [citing_patent, cited_patent] """ G = nx.DiGraph() # Add patent nodes with attributes for _, row in patents.iterrows(): G.add_node(row.patent_id, assignee=row.assignee, year=row.filing_year, cpc=row.cpc_codes[0][:4]) # Add citation edges for _, row in citations.iterrows(): if row.citing_patent in G and row.cited_patent in G: G.add_edge(row.citing_patent, row.cited_patent) return G def identify_seminal_patents(G: nx.DiGraph, top_n: int = 20) -> list: """Find the most influential patents by various centrality measures.""" in_degree = dict(G.in_degree()) pagerank = nx.pagerank(G) # Combine metrics scores = {} for node in G.nodes(): scores[node] = { "citations_received": in_degree[node], "pagerank": pagerank[node], } ranked = sorted(scores.items(), key=lambda x: x[1]["pagerank"], reverse=True) return ranked[:top_n]
Patent claims define the legal scope of protection. Independent claims are the broadest; dependent claims narrow them:
pythondef parse_claims(claims_text: str) -> list[dict]: """ Parse patent claims text into structured claim objects. Identifies independent vs dependent claims and extracts dependencies. """ # Split on claim numbers claim_pattern = re.compile(r"\n\s*(\d+)\.\s+", re.MULTILINE) parts = claim_pattern.split(claims_text) claims = [] for i in range(1, len(parts), 2): claim_num = int(parts[i]) claim_text = parts[i + 1].strip() # Detect dependency dep_match = re.match( r"(?:The|A)\s+\w+\s+(?:of|according to)\s+claim\s+(\d+)", claim_text, re.IGNORECASE ) is_independent = dep_match is None depends_on = int(dep_match.group(1)) if dep_match else None claims.append({ "number": claim_num, "text": claim_text, "independent": is_independent, "depends_on": depends_on, "word_count": len(claim_text.split()), }) return claims
Systematic prior art search methodology:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 19,113 | 37,045 | +94% | 1 | 1 | 0% | 4,012 | 6,332 | +58% | 0 | 0 | — |
case-02 | fail→fail | 20,511 | 18,311 | -11% | 1 | 1 | 0% | 3,612 | 5,566 | +54% | 0 | 0 | — |
case-03 | fail→fail | 15,007 | 18,849 | +26% | 1 | 1 | 0% | 2,958 | 6,143 | +108% | 0 | 0 | — |
case-04 | fail→pass | 18,856 | 3,687 | -80% | 1 | 1 | 0% | 1,244 | 2,929 | +135% | 0 | 0 | — |
case-05 | pass→pass | 12,937 | 9,928 | -23% | 1 | 1 | 0% | 2,151 | 4,005 | +86% | 0 | 0 | — |
case-06 | fail→fail | 15,102 | 16,170 | +7% | 1 | 1 | 0% | 2,591 | 5,174 | +100% | 0 | 0 | — |
case-07 | pass→pass | 8,850 | 3,830 | -57% | 1 | 1 | 0% | 695 | 2,769 | +298% | 0 | 0 | — |
case-08 | pass→pass | 2,559 | 2,413 | -6% | 1 | 1 | 0% | 388 | 2,611 | +573% | 0 | 0 | — |
case-09 | pass→pass | 6,087 | 3,342 | -45% | 1 | 1 | 0% | 953 | 2,736 | +187% | 0 | 0 | — |
case-10 | fail→fail | 19,886 | 25,982 | +31% | 1 | 1 | 0% | 2,927 | 5,980 | +104% | 0 | 0 | — |
case-11 | pass→pass | 17,487 | 14,066 | -20% | 1 | 1 | 0% | 2,707 | 4,456 | +65% | 0 | 0 | — |
case-12 | pass→pass | 19,044 | 21,995 | +15% | 1 | 1 | 0% | 2,956 | 5,679 | +92% | 0 | 0 | — |
case-13 | pass→pass | 10,629 | 7,185 | -32% | 1 | 1 | 0% | 1,590 | 3,300 | +108% | 0 | 0 | — |
case-14 | pass→pass | 8,350 | 5,939 | -29% | 1 | 1 | 0% | 1,189 | 3,229 | +172% | 0 | 0 | — |
case-15 | pass→pass | 14,342 | 13,977 | -3% | 1 | 1 | 0% | 2,042 | 4,269 | +109% | 0 | 0 | — |
case-16 | pass→pass | 12,837 | 13,724 | +7% | 1 | 1 | 0% | 1,620 | 4,313 | +166% | 0 | 0 | — |
case-17 | pass→pass | 12,042 | 17,423 | +45% | 1 | 1 | 0% | 1,794 | 4,717 | +163% | 0 | 0 | — |
case-18 | fail→fail | 14,218 | 10,551 | -26% | 1 | 1 | 0% | 2,155 | 3,891 | +81% | 0 | 0 | — |
case-19 | pass→pass | 13,882 | 12,847 | -7% | 1 | 1 | 0% | 2,074 | 4,349 | +110% | 0 | 0 | — |
case-20 | pass→fail | 12,108 | 15,138 | +25% | 1 | 1 | 0% | 1,932 | 4,960 | +157% | 0 | 0 | — |
case-21 | pass→pass | 23,369 | 21,280 | -9% | 1 | 1 | 0% | 4,079 | 5,531 | +36% | 0 | 0 | — |
case-22 | pass→pass | 21,654 | 31,103 | +44% | 1 | 1 | 0% | 3,625 | 7,787 | +115% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of 0 percentage points is the difference between those two pass rates over the 21 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Other measured skills in the registry, with their headline benchmark lift.