Detecting Pv Signals logo

Detecting Pv Signals

CommunityPopular
maziyarpanahi
detecting-pv-signals

Computes disproportionality signals — PRR, ROR, EBGM, and IC (BCPNN) — over FAERS / OpenFDA drug-event data to flag potential safety signals. Use when the user wants to mine spontaneous-report data for drug-reaction associations, build a 2x2 contingency table, compute a Proportional Reporting Ratio or Reporting Odds Ratio, run Empirical Bayes (EBGM/EB05) or Information Component shrinkage, or screen a drug for over-reported reactions. Trigger keywords: disproportionality, signal detection, PRR, ROR, EBGM, EB05, IC, BCPNN, MGPS, 2x2 table, signal of disproportionate reporting, SDR, OpenFDA, FAERS. Pairs adjacent to OpenMed: aggregate de-identified, coded cases (from reporting-adverse-events) then query the public OpenFDA /drug/event count API to build the contingency table. Reaction terms are MedDRA PTs (licensed, user-supplied).

Overview

Publishermaziyarpanahi
Repositoryopenmed
Skill namedetecting-pv-signals
Stars
5.3K
Forks
677
Bundled files
Instructions only
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by maziyarpanahi on GitHub. Read the source before you install it.

Installation

Install the Detecting Pv Signals AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/maziyarpanahi/openmed.git /tmp/openmed
mkdir -p .claude/skills
cp -r /tmp/openmed/skills/detecting-pv-signals .claude/skills/detecting-pv-signals
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Detecting Pv Signals in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Detecting Pv Signals on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Detecting Pv Signals is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Detecting pharmacovigilance signals (disproportionality)

Spontaneous-report databases like the FDA's FAERS are mined for signals of disproportionate reporting (SDR): drug-reaction pairs that occur together more than expected given the background of all reports. The core device is a 2x2 contingency table and a disproportionality metric computed from it — PRR, ROR, EBGM, or IC (BCPNN).

You can build the 2x2 table directly from the public, free OpenFDA /drug/event endpoint (no PHI, no MedDRA license to query; the reaction terms returned are already MedDRA PTs). This skill is statistical screening: a high PRR is a hypothesis, not a confirmed adverse drug reaction.

When to use

  • You have a drug of interest and want to see which reactions are over-reported.
  • You need a PRR / ROR with confidence interval, or an Empirical Bayes EBGM/EB05 / IC025 to control for the small-count noise PRR/ROR suffer from.
  • You are building a routine signal-screening run over OpenFDA or your own aggregated case counts.

The 2x2 table

For one drug D and one reaction R, classify every report:

Reaction RNot R
Drug Dab
Not Dcd
  • PRR = [a/(a+b)] / [c/(c+d)]
  • ROR = (a·d)/(b·c)
  • IC (BCPNN, log2 information component) ≈ log2( a·(a+b+c+d) / ((a+b)·(a+c)) )
  • EBGM = Empirical Bayes Geometric Mean — a gamma-Poisson shrinkage of the observed/expected ratio (the MGPS method) that pulls small-count estimates toward 1; report EB05 (the 5th percentile) as the conservative signal.

Common signal thresholds (screening only): PRR ≥ 2 with χ² ≥ 4 and a ≥ 3; ROR lower 95% CI > 1; IC025 > 0; EB05 ≥ 2.

Quick start (real OpenFDA count queries)

Base endpoint: https://api.fda.gov/drug/event.json. No key needed to try it (240 req/min, 1,000/day per IP; with a free api_key= key: 240/min, 120,000/day). The count=<field>.exact parameter returns a terms histogram, and search= with +AND+ filters the population — that is all you need for a 2x2.

python
import requests

BASE = "https://api.fda.gov/drug/event.json"

def fda_count(search: str | None, count_field: str) -> int:
    """Total reports matching `search` (sum of the .exact histogram)."""
    params = {"count": count_field}
    if search:
        params["search"] = search
    r = requests.get(BASE, params=params, timeout=30)
    if r.status_code == 404:        # OpenFDA returns 404 for an empty result set
        return 0
    r.raise_for_status()
    return sum(row["count"] for row in r.json()["results"])

def cell_count(search: str | None) -> int:
    """Number of reports matching `search` (use meta.results.total via limit=1)."""
    params = {"limit": 1}
    if search:
        params["search"] = search
    r = requests.get(BASE, params=params, timeout=30)
    if r.status_code == 404:
        return 0
    r.raise_for_status()
    return r.json()["meta"]["results"]["total"]

# Build the 2x2 for warfarin x "gastrointestinal haemorrhage".
DRUG = 'patient.drug.openfda.generic_name:"warfarin"'
RXN  = 'patient.reaction.reactionmeddrapt.exact:"gastrointestinal haemorrhage"'

a = cell_count(f"{DRUG}+AND+{RXN}")          # drug & reaction
b = cell_count(DRUG) - a                      # drug, not reaction
c = cell_count(RXN) - a                       # reaction, not drug
N = cell_count(None)                          # total reports in FAERS
d = N - a - b - c

Compute the metrics from (a, b, c, d):

python
import math

def prr(a, b, c, d):
    return (a / (a + b)) / (c / (c + d))

def ror(a, b, c, d):
    return (a * d) / (b * c)

def ror_ci(a, b, c, d):
    lnror = math.log((a * d) / (b * c))
    se = math.sqrt(1/a + 1/b + 1/c + 1/d)     # Woolf's method
    lo, hi = math.exp(lnror - 1.96 * se), math.exp(lnror + 1.96 * se)
    return lo, hi

def ic(a, b, c, d):
    n = a + b + c + d
    expected = (a + b) * (a + c) / n
    return math.log2(a / expected) if a and expected else float("nan")

print("PRR", round(prr(a, b, c, d), 2))
print("ROR", round(ror(a, b, c, d), 2), "95% CI", ror_ci(a, b, c, d))
print("IC",  round(ic(a, b, c, d), 2))

For EBGM / EB05 use a maintained Empirical Bayes implementation (e.g. the openEBGM R package or PhViD in R) on the same (a, b, c, d) rather than hand-rolling the gamma-Poisson MGPS shrinkage — the shrinkage prior is the whole point and easy to get wrong.

Workflow

  1. Pick the population. Decide your denominator: all of FAERS, or a restricted background (e.g. one drug class, one year via receivedate:[20230101+TO+20231231]). The choice of c/d defines the "expected".
  2. Resolve the drug field. Prefer patient.drug.openfda.generic_name (RxNorm ingredient-normalized) over the free-text medicinalproduct to avoid brand fragmentation. Restrict to suspect drugs with patient.drug.drugcharacterization:1 if you want suspect-only signals.
  3. Use .exact for the reaction field so "injection site reaction" counts as one phrase, not three words: patient.reaction.reactionmeddrapt.exact.
  4. Build the 2x2 with the cell counts above. Verify a + b + c + d == N.
  5. Compute PRR and ROR with CIs; add IC025 / EB05 for small counts.
  6. Apply thresholds (e.g. PRR ≥ 2, χ² ≥ 4, a ≥ 3) — but treat them as a triage filter, not a verdict.
  7. Hand flagged pairs to a safety scientist for medical review, confounder assessment, and labeling/expectedness checks.

Hand-off to / from OpenMed

  • From reporting-adverse-events: your own coded, de-identified ICSRs give internal counts you can use instead of or alongside OpenFDA — the same 2x2 math applies. Aggregate only counts; never put narrative PHI in the table.
  • From normalizing-rxnorm: normalize the drug name to an RxNorm ingredient before querying so brand/generic synonyms collapse to one cell.
  • To querying-openfda-labels: for every signal, check whether the reaction is already on the label (expected) via /drug/label. To reporting-adverse-events: a confirmed signal may require expedited reporting.
  • OpenMed runs NER/de-id on-device; only de-identified drug/reaction codes (no PHI) are sent to OpenFDA.

Edge cases & gotchas

  • Disproportionality ≠ causality. A high PRR reflects reporting patterns, notoriety bias, and indication confounding — not a proven causal link.
  • Small counts break PRR/ROR. With a < 3 the ratios are unstable and CIs explode. This is exactly why EBGM/EB05 and IC025 (shrinkage) exist — prefer them for rare events.
  • OpenFDA is a sample, not all of FAERS, and is not deduplicated the way the curated FAERS quarterly files are. Use it for screening; reproduce confirmed signals against the official FAERS extracts.
  • .exact is mandatory for counting phrases. Without it, OpenFDA tokenizes the reaction and your counts are wrong.
  • OpenFDA returns HTTP 404 for an empty result set (not an empty list) — the helpers above treat 404 as zero. Respect the rate limits; register a free key for routine runs.
  • MedDRA versioning. OpenFDA reaction terms are MedDRA PTs at FDA's coding version; if you join to your own MedDRA-coded cases, align the version. MedDRA itself is licensed — you query OpenFDA's already-coded terms, you do not need a MedDRA license to read them, but you do to code your own cases.

Standards & references

Frequently asked questions

What does the Detecting Pv Signals AI skill do?

Computes disproportionality signals — PRR, ROR, EBGM, and IC (BCPNN) — over FAERS / OpenFDA drug-event data to flag potential safety signals. Use when the user wants to mine spontaneous-report data for drug-reaction associations, build a 2x2 contingency table, compute a Proportional Reporting Ratio or Reporting Odds Ratio, run Empirical Bayes (EBGM/EB05) or Information Component shrinkage, or screen a drug for over-reported reactions. Trigger keywords: disproportionality, signal detection, PRR, ROR, EBGM, EB05, IC, BCPNN, MGPS, 2x2 table, signal of disproportionate reporting, SDR, OpenFDA,...

Why use Detecting Pv Signals on TypingMind?

Because you install it once and use it with any model. Detecting Pv Signals is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Detecting Pv Signals in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/maziyarpanahi/openmed/tree/master/skills/detecting-pv-signals. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Detecting Pv Signals?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Detecting Pv Signals?

As many as you like. As long as a model supports skills, you can use Detecting Pv Signals with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Detecting Pv Signals AI skill free?

Yes. It is published on GitHub by maziyarpanahi under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇