AI Prompts

Grossing DESCRIPTOR_PROMPT = """You are assisting a pathologist assistant. Given the gross pathology description below, extract up to 40 concise macroscopic descriptor terms that would be useful suggestions for similar future cases. Focus on: texture, color, consistency, shape, architecture, external surface and cut surface features, margin descriptors, invasion patterns, and anatomic findings. Include compound terms like "lymphovascular invasion" as single entries. Return ONLY a JSON array of strings. No explanation, no markdown, no preamble. Specimen: {specimen} History: {history} Gross description: {gross_text} JSON array:"""

TOKENIZE_PROMPT = """Extract the meaningful medical and pathological terms from this gross pathology description for text similarity analysis. Rules: - Remove all measurements (numbers with units) - Remove cassette labels (like A1, B3) - Remove bracket placeholders like [___] - Remove common stopwords (the, a, is, was, etc.) - Keep compound terms together (e.g. "lymphovascular invasion", "resection margin") - Lowercase all terms - Return ONLY a JSON array of strings, one term per entry Gross description: {gross_text} JSON array of terms:"""

STOPWORDS = { # Common English 'the','a','an','and','or','but','in','on','at','to','for','of','with', 'is','are','was','were','be','been','being','have','has','had','do','does', 'did','will','would','could','should','may','might','shall','can', 'it','its','this','that','these','those','there','their','they', 'he','she','we','you','i','me','my','our','your','his','her', 'not','no','nor','so','yet','both','either','neither','each', 'from','by','as','if','than','then','when','where','which','who', 'all','any','most','more','some','such','other','same','also', 'into','through','during','before','after','above','below','between', # Reception sentence boilerplate (belt-and-suspenders after strip_first_line) 'specimen','received','container','labeled','labelled', 'patient','name','initials','site', # Suture/orientation words from specimen site field 'suture','sutures','long','short','lateral','superior','inferior', 'medial','anterior','posterior','double','deep', # Grossing process words (no discriminative value) 'consistent','representative','sections','follows','submitted', 'toto','cassette','section','gross','description','per','well', 'present','identified','noted','seen','observed','appears','appear', # Numbers as words 'one','two','three','four','five','six','seven','eight','nine','ten', }

def normalize_specimen_name(name: str) → str: """ Strip clerical suffixes from specimen names typed at the container. Keeps the medically meaningful part (e.g. 'left breast'). Used when creating new specimen records to avoid spurious new entries. Mirrors the logic in maybeInferSpecimen() in app.js. Examples: 'left breast, sutures long lateral, short superior 675 g' → 'left breast' 'left breast tissue suture short superior long lateral' → 'left breast tissue' 'right axillary sentinel node #1' → 'right axillary sentinel node' 'left cheek lesion? SCC' → 'left cheek lesion' """ n = name.strip() # Truncate at first comma that separates suture/weight info comma = n.find(',') if comma > 2: n = n[:comma].strip() # Remove trailing suture/orientation phrase (no comma version) n = re.sub(r'\s+(sutures?|suture marks?).*$', '', n, flags=re.IGNORECASE) n = re.sub(r'\s+(long lateral|short superior|double deep).*$', '', n, flags=re.IGNORECASE) # Remove weight suffix n = re.sub(r'\s+\d+\.?\d\sg\s*$', '', n) # Remove sentinel node number suffix n = re.sub(r'\s+#\d+\s*$', '', n) # Remove diagnostic query suffix n = re.sub(r'\?.*$', '', n) return n.strip().rstrip(',').strip()

def strip_first_line(text: str) → str: """ Remove the boilerplate reception sentence from gross text before tokenizing. This line always looks like: 'The specimen is received in a container labelled with the patient's name, who has the initials "XX" and the specimen site "left breast...".' Keeping it would add 'specimen', 'patient', 'initials', 'container' as high-frequency tokens that dilute TF-IDF similarity across all cases. Also removes suture/orientation noise from the specimen site field. Must stay in sync with strip_boilerplate() in similarity.php. Multi-line aware: boilerplate may wrap across lines, so we skip all consecutive matching lines from the top of the text. """ lines = text.split('\n') result = [] in_boilerplate = True for line in lines: lower = line.strip().lower() if in_boilerplate and ( 'specimen is received' in lower or 'container labelled' in lower or 'container labeled' in lower or "patient's name" in lower or "patient´s name" in lower or ('initials' in lower and 'specimen site' in lower) ): continue in_boilerplate = False result.append(line) return '\n'.join(result).strip()