ICD-10-PCS Inpatient Procedure Codes
A seven-character alphanumeric coding system maintained by CMS for reporting surgical and therapeutic procedures performed during a hospital inpatient stay. Every character encodes a specific clinical axis (section, body system, root operation, body part, approach, device, qualifier), so codes are constructed from a hierarchical table rather than selected from a flat list — making the system multi-axial, fully extensible, and the standard source for inpatient procedure data in US administrative claims.
On this page
Every surgery or procedure performed on a hospital inpatient in the United States is described with a seven-character code where each character answers a specific question about the procedure — what type, which body system, what the surgeon did, which body part, how they got there, what was left in, and any additional detail. These ICD-10-PCS codes appear only on the hospital's inpatient facility bill, not on the surgeon's separate bill or on any outpatient record, so researchers who look only at ICD-10-PCS will miss all procedures done in outpatient surgery centers or clinics. The code system has been in use since October 2015 and replaced an older, less detailed system that used shorter numeric codes.
ICD-10-PCS
(International Classification of Diseases, 10th Revision, Procedure Coding System) is the US standard for reporting inpatient hospital procedures. It replaced ICD-9-CM Volume 3 on October 1, 2015. CMS maintains the system and releases annual updates each October 1. The code set is in the public domain. As of recent fiscal years the system contains approximately 78,000+ valid codes; the exact count grows with each annual update as new procedures, devices, and qualifiers are incorporated.
The seven-axis architecture (Medical and Surgical section)
Every ICD-10-PCS code is exactly seven characters long — alphanumeric, using digits 0-9 and letters A-Z with I and O excluded to prevent confusion with 1 and 0. Each character position encodes one and only one clinical axis:
- Position 1 — Section: The broad type of procedure. Section 0 is Medical and Surgical (the dominant section for RWE), covering open and minimally invasive operative procedures. Other sections include Obstetrics (1), Placement (2), Administration (3), Measurement and Monitoring (4), Imaging (B), Nuclear Medicine (C), Radiation Therapy (D), Mental Health (G), Substance Abuse Treatment (H), New Technology (X), and several others. Section X (New Technology) is clinically significant: new surgical approaches, devices, and biologics that do not fit existing tables are assigned here, and RWE analysts studying novel procedures or implants should always include relevant Section X codes.
- Position 2 — Body System: Within Medical and Surgical, the anatomical system involved. Values include 0 Central Nervous System and Cranial Nerves, 1 Peripheral Nervous System, 2 Heart and Great Vessels, 3 Upper Arteries, 4 Lower Arteries, 5 Upper Veins, 6 Lower Veins, 7 Lymphatic and Hemic, 8 Eye, 9 Ear Nose Sinus, B Respiratory, C Mouth and Throat, D Gastrointestinal, F Hepatobiliary and Pancreas, G Endocrine, H Skin and Breast, J Subcutaneous Tissue and Fascia, K Muscles, L Tendons, M Bursae and Ligaments, N Head and Facial Bones, P Upper Bones, Q Lower Bones, R Lower Joints, S Upper Joints, T Urinary, U Female Reproductive, V Male Reproductive, W Anatomical Regions, X Upper Extremities, Y Lower Extremities.
- Position 3 — Root Operation: The objective of the procedure — this is the most analytically consequential axis. Root operations have precise, non-intuitive definitions that often differ from everyday clinical language. A few critical examples for RWE: - Resection (T): Cutting out or off, without replacement, ALL of a body part. A total knee arthroplasty is coded as a Replacement, not Resection. - Excision (B): Cutting out or off, without replacement, a PORTION of a body part. Used for partial resections, biopsies, debridements. - Replacement (R): Putting in or on a biological or synthetic substitute that physically takes the place of all or a portion of a body part. Total knee replacement = Replacement; the body part value specifies the joint; position 6 (Device) specifies the prosthesis type. - Repair (Q): Restoring, to the extent possible, a body part to its normal anatomic structure and function — the default root operation when no other is more specific. - Bypass (1): Altering the route of passage to include an upstream body part (e.g., coronary artery bypass graft). The critical RWE issue: a "bypass" in ICD-10-PCS means specific anatomical rerouting — not a general colloquial "bypass." - Fusion (G): Joining together portions of an articular body part, rendering the articular body part immobile. Spinal fusion codes rely on approach (position 5) and device (position 6) — an analyst building a "spinal fusion" cohort must specify whether posterior, anterior, interbody cage, and so on. - Other key root operations: Dilation (7), Drainage (9), Extirpation (C — removing solid matter), Fragmentation (F), Inspection (J), Occlusion (L), Reattachment (M), Release (N), Removal (P — for removing devices), Repair (Q), Replacement (R), Reposition (S), Restriction (V), Revision (W — correcting a malfunctioning device), Supplement (U), Transfer (X), Transplant (Y).
- Position 4 — Body Part: The specific anatomical site operated upon, defined within the body system. For Lower Joints (body system R), values include Lumbar Vertebral Joint (0), Lumbosacral Joint (3), Sacrococcygeal Joint (5), Coccygeal Joint (6), Sacroiliac Joints (7/8), Hip Joints (9/A/B/C), Knee Joints (C/D/F/G), Ankle Joints (H/J), Tarsal Joints (K/L), and so forth.
- Position 5 — Approach: The technique used to reach the operative site. Open (0), Percutaneous (3), Percutaneous Endoscopic (4), Via Natural or Artificial Opening (7), Via Natural or Artificial Opening Endoscopic (8), External (X). Approach is an essential RWE dimension: a laparoscopic versus open approach for the same procedure may have different safety profiles, lengths of stay, and costs — and ICD-10-PCS distinguishes them explicitly in position 5.
- Position 6 — Device: Any material or object left in or on the body at the end of the procedure. No Device (Z) means nothing was left. Device values include drains, synthetic substitutes, autologous tissue substitutes, bone grafts, implants, pacemakers, and many others. For joint replacements, position 6 identifies the prosthesis type (cemented, uncemented, ceramic-on-ceramic, etc.).
- Position 7 — Qualifier: An additional attribute that further specifies the procedure. Common values: Diagnostic (X — as in a diagnostic excision, which is a biopsy), No Qualifier (Z), All (N), Atrial (6), Ventricular (7), and many procedure-specific values.
How codes are constructed, not selected
Unlike ICD-10-CM (where codes are looked up in a tabular list) or CPT (where codes are selected from a flat numbered list with hierarchical groupings), ICD-10-PCS codes are built from a set of tables. Each table specifies one combination of section + body system + root operation, and the analyst reads across to pick one value per remaining position. This means the system is genuinely combinatorial: for a given root operation on a given body system, all clinically meaningful combinations of body part, approach, device, and qualifier generate valid codes. There is no principal list of 78,000 codes to memorize; coders and researchers navigate tables. RWE analysts who attempt to define a procedure cohort by building a "list of codes" without reading the applicable table definitions — especially the root operation definitions — risk systematic misclassification.
The root operation is the place where ICD-10-PCS most often surprises clinicians and analysts who assume the code describes what was done rather than what was precisely intended.
Coding coverage and scope: inpatient facility only
This is the single most important scoping constraint for RWE. ICD-10-PCS codes appear only on UB-04/837I claims (hospital inpatient facility bills), specifically in the procedure code fields (FL74: principal procedure, FL75-FL80: additional procedures). They are NEVER used on:
- Physician/professional claims (CMS-1500/837P) — those use CPT and HCPCS
- Hospital outpatient facility claims (also UB-04, but the procedure fields use CPT/HCPCS)
- Ambulatory surgery center (ASC) claims — CPT/HCPCS
- Part B administered drug/biologic claims — HCPCS J-codes
The result is a sharp population-of-care segmentation. Total knee arthroplasty performed as an inpatient admission generates an ICD-10-PCS code on the facility claim. The same procedure performed at an outpatient surgery center generates CPT 27447 on the ASC facility claim and on the surgeon's professional claim — no ICD-10-PCS. As the shift toward outpatient surgical care has accelerated, an algorithm using ICD-10-PCS alone increasingly misses real cases in the sicker-appearing inpatient-only subgroup.
Any procedure definition relying exclusively on ICD-10-PCS will miss all outpatient facility cases and all professional claims, systematically under-counting and biasing the identified cohort toward longer, costlier inpatient stays.
Complete procedure ascertainment in US claims almost always requires a union of ICD-10-PCS (inpatient facility) + CPT/HCPCS (physician and outpatient facility) + revenue center codes (outpatient facility type-of-service confirmation). The relative contribution of ICD-10-PCS versus CPT changes over time as procedure migration from inpatient to outpatient settings continues; analysts should report the fraction captured by each stream as a sensitivity diagnostic.
ICD-10-PCS vs. ICD-9-CM Volume 3
The predecessor, ICD-9-CM Volume 3, used a four-character numeric code with two-character category hierarchies. Its coverage was incomplete, its specificity was lower, and its hierarchical structure was not systematically multi-axial — procedure categorization was often inconsistent across anatomic areas. ICD-10-PCS brought standardized multi-axial logic, explicit approach and device coding, and a larger code space.
The transition on 2015-10-01 created a coding discontinuity: time-series analyses crossing October 2015 must account for the change, and ICD-9-to-ICD-10-PCS crosswalks (GEMs — General Equivalence Mappings — provided by CMS) are imperfect because many ICD-9-CM Vol 3 codes map to multiple ICD-10-PCS codes and vice versa. For any cohort or outcome algorithm that spans the transition date, the crosswalk mapping uncertainty must be quantified and reported.
Relationship to MS-DRGs
Medicare Severity Diagnosis Related Groups (MS-DRGs) are assigned by the Medicare grouper software based on the combination of the principal diagnosis (ICD-10-CM), secondary diagnoses, and procedures (ICD-10-PCS). The presence or absence of a "surgical" ICD-10-PCS code — and specifically which major diagnostic category and surgical hierarchy it triggers — determines whether a discharge is classified into a surgical MS-DRG versus a medical MS-DRG. Surgical DRGs command higher payments.
RWE analysts working with DRG-based cost data or severity-adjustment that uses DRG must understand that the ICD-10-PCS code drives this classification: an error in the PCS code changes the DRG assignment and distorts cost comparisons.
Relationship to OMOP CDM
In the Observational Medical Outcomes Partnership (OMOP) common data model, ICD-10-PCS source codes are mapped to the Procedure domain using standard concepts from the SNOMED-CT procedure hierarchy or the OMOP standard concept set for procedures. The source code (ICD-10-PCS character string) is preserved in the source_concept_id column of the procedure_occurrence table; the standard_concept_id is the mapped SNOMED or OMOP concept.
Analysts building procedure cohorts in OMOP should query the procedure_occurrence table using standard_concept_id (SNOMED) unless they have specific reason to query by source code. OMOP's concept mapping for ICD-10-PCS is generally good for common surgical procedures but may lag for new technology (Section X) codes that have been recently added to the ICD-10-PCS tables.
Pros, cons, and trade-offs
specific and comparative.
- vs CPT procedure codes (professional and outpatient facility claims): CPT is axis-free — each code is a discrete concept with a single textual definition, hierarchically grouped but not combinatorially constructed. CPT covers ALL sites of care for professional billing and outpatient facility billing; ICD-10-PCS covers only inpatient facility billing. ICD-10-PCS provides explicit approach, device, and qualifier axes that CPT must encode via add-on codes or modifiers; for inpatient procedures, ICD-10-PCS is more granular. Prefer the union of both for any comprehensive procedure definition. Never use ICD-10-PCS alone when the procedure population spans inpatient and outpatient settings.
- vs ICD-9-CM Volume 3 (the predecessor for pre-October-2015 data): ICD-9-CM Vol 3 is less specific, less consistently structured, and uses a different character count (4 digits) with a different hierarchy. For longitudinal analyses crossing October 2015, both systems are needed. GEMs crosswalks exist but carry mapping uncertainty — the ICD-9 code for "total knee arthroplasty" does not always map 1:1 to a single ICD-10-PCS code combination. Prefer ICD-10-PCS for post-2015 data; when crossing the transition date, document the crosswalk approach, its ambiguity, and validate against procedure counts before and after the transition.
- vs HCPCS Level II (J-codes and device/supply codes): HCPCS Level II covers drugs administered in clinical settings (J-codes), durable medical equipment, and some procedures not captured in CPT. It appears on institutional outpatient and professional claims. It is not a procedure coding system in the ICD-10-PCS sense — it does not describe the operative act. Use HCPCS when the research question concerns drug administration (e.g., infused biologics) or device supply; use ICD-10-PCS when the research question concerns the inpatient surgical act itself.
- vs revenue center codes (UB-04 revenue codes): Revenue codes describe the type of service and hospital cost center (e.g., 0360 = operating room, 0481 = cardiology), not the specific procedure. They complement ICD-10-PCS for outpatient facility procedure identification and are often used to confirm that a procedure was performed in the operating room as a specificity filter. Use revenue codes alongside ICD-10-PCS and CPT/HCPCS; do not use them as the primary procedure identifier.
When NOT to use — and when it is actively misleading or dangerous
- As the sole procedure identifier when the study population includes outpatient or ambulatory cases. Since at least the late 2010s, procedures historically performed inpatient (e.g., total knee arthroplasty, laparoscopic cholecystectomy, many cardiac catheterizations) are increasingly performed in outpatient settings. An ICD-10-PCS-only algorithm captures a diminishing and non-random fraction of all cases — the fraction that is still inpatient, which skews toward more comorbid, higher-risk patients.
The result is not merely incomplete; it is a biased sample. Always combine with CPT and HCPCS for population-representative procedure ascertainment.
- When relying on the code description rather than the root operation definition. The written description of an ICD-10-PCS code (e.g., "Excision of Left Knee Joint, Open Approach") tells you the character values but not why Excision was chosen over Repair or Replacement. A code-list built by matching text descriptions instead of reading the root operation definitions systematically includes wrong codes and excludes the right ones.
For example, all total knee arthroplasties use root operation Replacement (R), but the word "replacement" does not appear in common clinical notes — clinical coders and researchers who look for "knee replacement" in descriptions without checking the root operation mapping will miss cases coded under adjacent root operations and body-part values.
- For pre-October 2015 inpatient data. ICD-10-PCS did not exist in US data before FY2016. Any dataset covering discharges before October 1, 2015 uses ICD-9-CM Volume 3 for inpatient procedures. Applying an ICD-10-PCS code list to pre-2015 data returns zero matches.
- When crossing the October 2015 ICD-9-CM-to-ICD-10-PCS transition date without documenting the crosswalk. GEMs are directional and imperfect. An analysis of procedure rates over a period spanning October 2015 may show a spurious step-change at the transition that reflects coding system change, not a true trend in procedure volume. Always test for and disclose the coding transition artifact in sensitivity analyses.
- When the procedure of interest is predominantly outpatient and was never routinely performed as an inpatient admission. Colonoscopy, upper endoscopy, most dermatologic procedures, most outpatient ophthalmologic procedures — these rarely appear in inpatient facility claims and will have essentially no ICD-10-PCS representation in a typical claims database. Using ICD-10-PCS for these is not just incomplete; it returns near-zero counts regardless of the true rate.
- When building a positional pattern for prefix matching without also testing specificity. A broad prefix (e.g., all codes beginning with 0SR — replacement of lower joints) captures all joint replacements but also articular replacements of the hip, ankle, toe, and patellofemoral surface. Always validate that the prefix includes the intended body parts (position 4) and root operation (position 3) before finalizing the code set.
Data-source operational depth
- Medicare FFS (Parts A/B/D) inpatient claims: ICD-10-PCS codes appear on the inpatient SAF (Standard Analytical File) and the inpatient LDS (Limited Data Set) in the procedure code fields (ICD_PRCDR_CD_1 through ICD_PRCDR_CD_25 in post-2015 data). The principal procedure (clinically most significant procedure, not necessarily first chronologically) is in field 1; up to 24 additional procedures follow. Date fields (ICD_PRCDR_DT_1 through DT_25) record the service date for each procedure. For patients with Medicare Advantage (Part C) coverage, FFS inpatient claims are absent — the same limitation as for any other procedure coding in MA-covered members. Restrict inpatient-procedure denominators to FFS-observable, Parts A/B-enrolled person-time.
- Commercial claims (employer-sponsored and ACA marketplace): ICD-10-PCS codes appear in the inpatient institutional claim file, equivalent to the Medicare inpatient SAF. The field names vary by data vendor (Optum, Merative/MarketScan, IQVIA, FAIR Health, etc.) but the content is structurally the same — up to ~25 procedure code slots on the UB-04-equivalent claim. Post-2015 commercial data universally use ICD-10-PCS for inpatient facility procedures. Pre-2016 data use ICD-9-CM Vol 3.
- All-payer inpatient discharge databases (HCUP NIS, SID, KID): The Healthcare Cost and Utilization Project databases use ICD-10-PCS for discharges on or after Q4 2015 (exact transition quarter varies by state for the SIDs). The NIS (National Inpatient Sample) is the largest US all-payer inpatient database (~7 million weighted discharges/year) and is the primary substrate for ICD-10-PCS-based population-level inpatient procedure research.
- EHR: ICD-10-PCS is a billing code, not a clinical code — it does not natively appear in EHR clinical documentation. However, after billing is complete, ICD-10-PCS codes from the facility bill may be attached to the encounter record in EHR administrative modules. Structured EHR procedure tables more commonly use CPT (for professional billing integration) or SNOMED-CT (for clinical documentation). If ICD-10-PCS codes are required from EHR data, obtain them from the linked billing extract, not from the clinical procedure table.
- OMOP CDM: ICD-10-PCS source codes are mapped to procedure_occurrence via the standard concept mapping. Use concept_relationship where relationship_id = 'Maps to' to translate ICD-10-PCS source concepts to SNOMED standard concepts. The vocabulary_id for ICD-10-PCS source concepts is 'ICD10PCS'. When building concept sets in ATLAS or via SQL, filter on vocabulary_id = 'ICD10PCS' for source-code queries or join through the standard concept hierarchy for portable queries across sites using different procedure coding systems.
Decision diagram
flowchart TD Claim[Hospital Inpatient Claim UB-04 / 837I] Claim --> PCS[ICD-10-PCS codes\nFL74 principal + up to 24 additional] Claim --> CM[ICD-10-CM diagnosis codes\nFL67 principal + secondary] PCS --> DRG[MS-DRG Grouper\nPCS drives surgical vs medical DRG assignment] CM --> DRG PCS --> Seven[7-character alphanumeric code\nno I or O] Seven --> P1[Pos 1: Section\n0=Med/Surg, X=New Technology] Seven --> P2[Pos 2: Body System\nS=Lower Joints, 2=Heart...] Seven --> P3[Pos 3: Root Operation\nR=Replacement, T=Resection, B=Excision...] Seven --> P4[Pos 4: Body Part\nC=Right Knee, D=Left Knee...] Seven --> P5[Pos 5: Approach\n0=Open, 4=Percutaneous Endoscopic...] Seven --> P6[Pos 6: Device\nJ=Synthetic Substitute, Z=No Device...] Seven --> P7[Pos 7: Qualifier\nZ=No Qualifier, X=Diagnostic...] PCS --> Note["ICD-10-PCS: inpatient facility ONLY\nCPT/HCPCS for professional + outpatient"]
flowchart LR
subgraph Inpatient["Hospital Inpatient Stay"]
PCS_Code["ICD-10-PCS\n0SRC0JZ\nReplacement, Rt Knee,\nOpen, Synthetic"]
end
subgraph Outpatient["Outpatient / ASC"]
CPT_Fac["CPT 27447\nTotal knee arthroplasty\nFacility claim"]
CPT_Pro["CPT 27447\nTotal knee arthroplasty\nProfessional claim"]
end
Inpatient -->|"Only stream with ICD-10-PCS"| PCS_Code
Outpatient --> CPT_Fac
Outpatient --> CPT_Pro
PCS_Code --> Warning["RWE risk: PCS-only algorithm\nmisses ALL outpatient cases\n→ biased toward sicker patients"]
CPT_Fac --> Union["Complete ascertainment:\nICD-10-PCS + CPT + HCPCS\n+ revenue codes (union)"]
CPT_Pro --> Union
PCS_Code --> UnionWorked example
Scenario
A health outcomes researcher wants to identify all patients in a Medicare FFS inpatient claims database who received a total knee replacement (TKR) during 2022. She knows from reading the ICD-10-PCS tables that a TKR maps to root operation Replacement (R), body system Lower Joints (R), and she wants to understand how to decompose the code, build the search pattern, and then confirm she is capturing the right set of body parts. She uses the publicly documented example code 0SRC0JZ (Replacement of Right Knee Joint with Synthetic Substitute, Open Approach) to walk through the logic, then writes the positional regex that finds all knee joint replacements regardless of laterality, device, or qualifier.
Dataset
Seven-axis decomposition of ICD-10-PCS code 0SRC0JZ — total knee replacement example
| Position | Character | Axis name | Value meaning |
|---|---|---|---|
| 1 | 0 | Section | Medical and Surgical |
| 2 | S | Body System | Lower Joints |
| 3 | R | Root Operation | Replacement (putting in a substitute that physically takes the place of all or part of a body part) |
| 4 | C | Body Part | Right Knee Joint |
| 5 | 0 | Approach | Open |
| 6 | J | Device | Synthetic Substitute |
| 7 | Z | Qualifier | No Qualifier |
Steps
Result
The 7-character string 0SRC0JZ decodes as: Open total knee replacement of the Right Knee Joint using a Synthetic Substitute. The positional regex ^0SR[CD][0-9A-HJ-NP-Z]{3}$ captures all knee joint replacements regardless of laterality (C=right, D=left), device type, and qualifier. A researcher using this pattern instead of a manually curated flat list guarantees that newly added table combinations in annual ICD-10-PCS updates are automatically included — a key advantage of the multi-axial architecture over flat code lists.
Trade-offs
Runnable example
ICD-10-PCS structural validation and code-set construction utilities for RWE analysts. Three capabilities: (1) validate a code string against the ICD-10-PCS character alphabet (7 characters, no I or O, regex gate); (2) decompose a code into its seven named axes for the Medical and Surgical section;
import re
# ICD-10-PCS character alphabet: digits 0-9 and uppercase letters except I and O.
_VALID_CHAR = re.compile(r'^[0-9A-HJ-NP-Z]{7}$')
# Medical & Surgical section position labels (section 0 only).
_MS_AXES = ["Section", "Body System", "Root Operation",
"Body Part", "Approach", "Device", "Qualifier"]
# Selected root operation values for the Medical & Surgical section (section 0).
ROOT_OPS = {
"0": "Alteration", "1": "Bypass", "2": "Change",
"3": "Control", "4": "Creation", "5": "Destruction",
"6": "Detachment", "7": "Dilation", "8": "Division",
"9": "Drainage", "B": "Excision", "C": "Extirpation",
"D": "Extraction", "F": "Fragmentation", "G": "Fusion",
"H": "Insertion", "J": "Inspection", "K": "Map",
"L": "Occlusion", "M": "Reattachment", "N": "Release",
"P": "Removal", "Q": "Repair", "R": "Replacement",
"S": "Reposition", "T": "Resection", "U": "Supplement",
"V": "Restriction", "W": "Revision", "X": "Transfer",
"Y": "Transplantation",
}
def validate_pcs_code(code: str) -> bool:
"""Return True if code is a syntactically valid ICD-10-PCS string.
Rules: exactly 7 characters; only digits 0-9 and uppercase A-Z
excluding I and O (which are omitted to avoid confusion with 1 and 0).
This is a format check only — it does not confirm the code appears in
the official CMS PCS tables for any specific fiscal year.
"""
return bool(_VALID_CHAR.match(str(code).upper()))
def decompose_pcs_code(code: str) -> dict:
"""Decompose a Medical & Surgical section (section 0) ICD-10-PCS code.
Returns a dict mapping each axis name to its character value.
Raises ValueError for codes that are not 7 valid characters or not
in section 0 (Medical & Surgical).
"""
code = str(code).upper()
if not validate_pcs_code(code):
raise ValueError(
f"Invalid ICD-10-PCS code: {code!r}. "
"Must be 7 characters using 0-9 and A-Z except I and O."
)
if code[0] != "0":
raise ValueError(
f"Code {code!r} is not Medical & Surgical (section 0). "
"Position 1 must be '0' for this decomposition."
)
result = {axis: char for axis, char in zip(_MS_AXES, code)}
# Add root operation label when recognised.
root_char = code[2]
if root_char in ROOT_OPS:
result["Root Operation Label"] = ROOT_OPS[root_char]
return result
def build_pcs_code_set(
section: str,
body_system: str,
root_operation: str,
body_parts: list[str] | None = None,
) -> re.Pattern:
"""Return a compiled regex that matches all ICD-10-PCS codes for the given
combination of section, body system, and root operation, optionally filtered
to a list of body part character values (position 4).
The pattern fixes positions 1-3 and, if body_parts is supplied, anchors
position 4 to those values; positions 5-7 are unconstrained (any valid
ICD-10-PCS character). Use this to build a table-aware code set that
automatically covers new fiscal-year additions without manual enumeration.
Example — all Open knee joint replacements (right and left):
build_pcs_code_set("0", "S", "R", body_parts=["C", "D"])
matches: 0SR[CD][0-9A-HJ-NP-Z]{3}
"""
for label, val in (("section", section), ("body_system", body_system),
("root_operation", root_operation)):
val = str(val).upper()
if not _VALID_CHAR.match(val + "000000"): # pad to 7 for regex test
raise ValueError(f"Invalid PCS character for {label}: {val!r}")
prefix = (str(section).upper()
+ str(body_system).upper()
+ str(root_operation).upper())
if body_parts:
valid_bp = [c.upper() for c in body_parts if _VALID_CHAR.match(c + "000000")]
if not valid_bp:
raise ValueError("No valid body part characters provided.")
bp_class = "[" + "".join(valid_bp) + "]"
else:
bp_class = "[0-9A-HJ-NP-Z]"
# Positions 5-7: any valid ICD-10-PCS character (excludes I and O).
tail = "[0-9A-HJ-NP-Z]{3}"
return re.compile(f"^{prefix}{bp_class}{tail}$")
# ── Worked example ──────────────────────────────────────────────────────────
if __name__ == "__main__":
# Validate and decompose 0SRC0JZ (total knee replacement, right, open, synthetic).
code = "0SRC0JZ"
print(f"Valid: {validate_pcs_code(code)}") # -> True
axes = decompose_pcs_code(code)
for axis, val in axes.items():
print(f" {axis}: {val}")
# Build a regex for all knee joint replacements (right=C, left=D).
tkr_pattern = build_pcs_code_set("0", "S", "R", body_parts=["C", "D"])
test_codes = ["0SRC0JZ", "0SRD0JZ", "0SRC049", "0LRB0JZ"] # last = not knee
for c in test_codes:
print(f" {c}: {'MATCH' if tkr_pattern.match(c) else 'no match'}")
# Expected: 0SRC0JZ=MATCH, 0SRD0JZ=MATCH, 0SRC049=no match (invalid char 4/position 5?),
# 0LRB0JZ=no match (section 0 body system L = Tendons, not Lower Joints).ICD-10-PCS code validation, axis decomposition, and positional code-set construction in R. Mirrors the Python implementation. Three exported functions: validate_pcs_code(), decompose_pcs_code(), and build_pcs_regex(). Requires base R only; no dependencies.
# ICD-10-PCS character alphabet: 0-9 and A-Z except I and O.
.PCS_VALID <- "^[0-9A-HJ-NP-Z]{7}$"
.MS_AXES <- c("Section", "Body_System", "Root_Operation",
"Body_Part", "Approach", "Device", "Qualifier")
.ROOT_OPS <- c(
"0"="Alteration", "1"="Bypass", "2"="Change",
"3"="Control", "4"="Creation", "5"="Destruction",
"6"="Detachment", "7"="Dilation", "8"="Division",
"9"="Drainage", "B"="Excision", "C"="Extirpation",
"D"="Extraction", "F"="Fragmentation", "G"="Fusion",
"H"="Insertion", "J"="Inspection", "K"="Map",
"L"="Occlusion", "M"="Reattachment", "N"="Release",
"P"="Removal", "Q"="Repair", "R"="Replacement",
"S"="Reposition", "T"="Resection", "U"="Supplement",
"V"="Restriction", "W"="Revision", "X"="Transfer",
"Y"="Transplantation"
)
#' Validate ICD-10-PCS code format.
#' @param code Character scalar or vector.
#' @return Logical. TRUE = syntactically valid; does not confirm FY table membership.
validate_pcs_code <- function(code) {
grepl(.PCS_VALID, toupper(as.character(code)))
}
#' Decompose a Medical & Surgical section (section 0) ICD-10-PCS code into its seven axes.
#' @param code Single 7-character ICD-10-PCS code.
#' @return Named character vector with one element per axis plus Root_Operation_Label.
decompose_pcs_code <- function(code) {
code <- toupper(as.character(code))
if (!validate_pcs_code(code))
stop("Invalid ICD-10-PCS code: ", code,
". Must be 7 characters from [0-9A-HJ-NP-Z].")
if (substr(code, 1, 1) != "0")
stop("Code ", code, " is not Medical & Surgical (section 0).")
chars <- strsplit(code, "")[[1]]
result <- setNames(chars, .MS_AXES)
root_char <- chars[3]
if (root_char %in% names(.ROOT_OPS))
result["Root_Operation_Label"] <- .ROOT_OPS[[root_char]]
result
}
#' Build a positional regex matching all ICD-10-PCS codes for a given
#' section + body_system + root_operation, optionally filtered to specific body parts.
#'
#' @param section 1-character section value (e.g., "0" for Medical & Surgical).
#' @param body_system 1-character body system value (e.g., "S" for Lower Joints).
#' @param root_op 1-character root operation value (e.g., "R" for Replacement).
#' @param body_parts Optional character vector of position-4 values to match.
#' @return A compiled regex pattern string (pass to grepl / regexpr).
build_pcs_regex <- function(section, body_system, root_op, body_parts = NULL) {
any_valid <- "[0-9A-HJ-NP-Z]"
prefix <- paste0(toupper(section), toupper(body_system), toupper(root_op))
if (!is.null(body_parts) && length(body_parts) > 0) {
bp_chars <- paste0(toupper(body_parts), collapse = "")
bp_class <- paste0("[", bp_chars, "]")
} else {
bp_class <- any_valid
}
paste0("^", prefix, bp_class, any_valid, "{3}$")
}
## Worked example ----------------------------------------------------------
code <- "0SRC0JZ"
cat("Valid:", validate_pcs_code(code), "\n")
print(decompose_pcs_code(code))
# Regex for all knee joint replacements (Right Knee = C, Left Knee = D)
tkr_regex <- build_pcs_regex("0", "S", "R", body_parts = c("C", "D"))
cat("TKR regex:", tkr_regex, "\n")
test_codes <- c("0SRC0JZ", "0SRD0JZ", "0SRC049", "0LRB0JZ")
cat("Match results:\n")
print(data.frame(code = test_codes, match = grepl(tkr_regex, test_codes)))Citations
- [1]Utter GH, Cox GL, Owens PL, Romano PS. Challenges and Opportunities with ICD-10-CM/PCS: Implications for Surgical Research Involving Administrative Data. Journal of the American College of Surgeons. 2013;217(3):564-574.
- [2]Utter GH, Cox GL, Atolagbe O, Owens PL. Conversion of the Agency for Healthcare Research and Quality's Quality Indicators from ICD-9-CM to ICD-10-CM/PCS: The Process, Results, and Implications for Users. Health Services Research. 2018;53(Suppl 3):5072-5102.
- [4]Bhatt DL, Thornton JD. Change in opioid-related inpatient discharges after the ICD-9 to ICD-10 transition in Texas. Research in Social and Administrative Pharmacy. 2023;19(3):473-478.
- [5]Centers for Medicare and Medicaid Services. ICD-10 Procedure Coding System (ICD-10-PCS). CMS Medicare Coding and Billing. Updated annually. Accessed 2026.