Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 181 additions & 2 deletions action/dist/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3536,7 +3536,151 @@
"tailwind",
"turbopack",
"vite",
"vitest"
"vitest",
"atp",
"adp",
"pep",
"rbc",
"wbc",
"dna",
"rna",
"g6pd",
"pklr",
"opsi",
"cbc",
"ldh",
"mchc",
"ema",
"dat",
"aiha",
"hs",
"b12",
"igg",
"igm",
"c3b",
"nadph",
"pk",
"spleen",
"splenectomy",
"macrophage",
"macrophages",
"capsule",
"encapsulated",
"capsulated",
"phagocytosis",
"opsonin",
"opsonized",
"opsonization",
"bacteremia",
"sepsis",
"meningitis",
"spherocyte",
"spherocytes",
"reticulocyte",
"reticulocytes",
"glycolysis",
"mitochondria",
"mitochondrial",
"hemolysis",
"thalassemia",
"echinocyte",
"echinocytes",
"deficiency",
"cell",
"cells",
"autosomal",
"recessive",
"mutation",
"hereditary",
"chronic",
"nonspherocytic",
"hemolytic",
"anemia",
"pyruvate",
"kinase",
"glucose",
"extravascular",
"dissociation",
"affinity",
"asymptomatic",
"symptomatic",
"severity",
"neonatal",
"compensated",
"jaundice",
"splenomegaly",
"gallstones",
"retic",
"destruction",
"marrow",
"bilirubin",
"haptoglobin",
"coombs",
"normocytic",
"macrocytosis",
"morphology",
"pathognomonic",
"oxidative",
"oxidant",
"phenotype",
"parvovirus",
"erythropoiesis",
"bpg",
"enzyme",
"enzymes",
"tissue",
"assay",
"ferritin",
"aplastic",
"crisis",
"prevalence",
"underdiagnosis",
"prototype",
"pneumonia",
"pneumoniae",
"influenzae",
"meningitidis",
"streptococcus",
"strep",
"pneumo",
"hib",
"neisseria",
"polysaccharide",
"antigen",
"antigens",
"antibody",
"antibodies",
"complement",
"bacterium",
"bacteria",
"clearance",
"infection",
"organism",
"organisms",
"immune",
"nonimmune",
"abnormal",
"transfusion",
"infancy",
"adulthood",
"membrane",
"integrity",
"deformability",
"slit",
"slits",
"ion",
"pumps",
"retrieval",
"mnemonic",
"decoder",
"fingerprint",
"defect",
"negative",
"positive",
"hb",
"indirect",
"direct",
"obvious"
]);
var KNOWN_TECHNICAL_TOKENS = new Set(DEFAULT_TECHNICAL_IDENTIFIERS);
var NUMERIC_VALUE = "[0-9\\u0660-\\u0669\\u06F0-\\u06F9]+(?:[.,\\u066B\\u066C][0-9\\u0660-\\u0669\\u06F0-\\u06F9]+)*";
Expand Down Expand Up @@ -3799,6 +3943,23 @@
}
return /[0-9_.]/u.test(token) || /[a-z][A-Z]/u.test(token) || !uppercaseProse && token.length <= ACRONYM_MAXIMUM_LENGTH && /^[A-Z]{2,}$/u.test(token);
}
function addReactionRanges(text, ranges) {
if (!/[→←↔⇒⇐⇌⇄]|->|<-/u.test(text)) return;
const lineRegex = /^[^\r\n]*(?:[→←↔⇒⇐⇌⇄]|->|<-)[^\r\n]*$/gmu;
const chainRegex = /(?:[A-Za-z0-9\u2070-\u209F_./+−–,-]{1,50}(?:\s+[A-Za-z0-9\u2070-\u209F_./+−–,-]{1,50}){0,6}(?:\s*[↑↓])?\s*(?:[→←↔⇒⇐⇌⇄]|->|<-)\s*)+(?:[A-Za-z0-9\u2070-\u209F_./+−–,-]{1,50}(?:\s+[A-Za-z0-9\u2070-\u209F_./+−–,-]{1,50}){0,6}(?:\s*[↑↓])?)/gu;
let lineMatch;
while ((lineMatch = lineRegex.exec(text)) !== null) {
const line2 = lineMatch[0];
const lineOffset = lineMatch.index;
chainRegex.lastIndex = 0;
let match;
while ((match = chainRegex.exec(line2)) !== null) {
const start = lineOffset + match.index;
const end = start + match[0].length;
addRange(ranges, text, start, end, "identifier");
}
}
}
function findTechnicalTokenRanges(text, technicalIdentifiers = []) {
const ranges = [];
addCodeRanges(text, ranges);
Expand Down Expand Up @@ -3857,13 +4018,31 @@
addMatches(text, ranges, /\b[A-Z]{1,4}\s+(?:[IVXLCDM]{1,8}|\d{1,3})\b/gu, "identifier");
addMatches(text, ranges, /\b[A-Z]{1,4}\/[A-Z]{1,4}\b/gu, "identifier");
addMatches(text, ranges, /\b[A-Z]\b(?=\s*(?:=|:|→|->))/gu, "identifier");
addMatches(text, ranges, /\b[A-Z]\.\s+[a-z]{3,}(?:\s+type\s+[a-z0-9]+)?\b/gu, "identifier");
addMatches(text, ranges, /\b\d+,\d+-[A-Z0-9]+(?:\s*[↑↓])?\b/gu, "identifier");
addMatches(text, ranges, /\b(?:Worked|Thought)\s+for\s+\d+s?\b/giu, "identifier");
addReactionRanges(text, ranges);
addMatches(
text,
ranges,
/\b[A-Za-z0-9\u2070-\u209F_.-]+\s*[↑↓]|\b(?:[A-Z0-9]{2,}|Coombs|Rh)\s*[+−-](?!\w)|\b[A-Za-z0-9\u2070-\u209F_.-]+[+−](?!\S)/gu,
"identifier"
);
addMatches(
text,
ranges,
/(?<=(?:ضد|پیش|پس|زیر|ابر|نیمه|بی|نا|غیر)[\u200C-])[A-Za-z][A-Za-z0-9_.-]*/gu,
"identifier"
);
const words = /\b[A-Za-z][A-Za-z0-9_.-]*\b/gu;
const customIdentifiers = customTechnicalIdentifiers(technicalIdentifiers);
const uppercaseProse = usesUppercaseProse(text);
let match;
while ((match = words.exec(text)) !== null) {
const token = match[0];
if (isTechnicalIdentifier(token, customIdentifiers, uppercaseProse)) {
const tail = text.slice(match.index + token.length, match.index + token.length + 8);
const hasAffix = /^[\u200C]?(?:ها|هایی|های|ای|اش|مان|تان|شان|تر|ترین|ات|ت|م|ی|یی|یها)(?![\p{L}\p{N}])/u.test(tail);
if (hasAffix || isTechnicalIdentifier(token, customIdentifiers, uppercaseProse)) {
addRange(ranges, text, match.index, match.index + token.length, "identifier");
}
}
Expand Down
4 changes: 2 additions & 2 deletions action/dist/index.cjs.map

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions corpus/fixtures/fa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Real-world ChatGPT Persian bidirectional test fixtures

This directory contains 28 schema-validated conformance fixtures derived directly
from real-world OpenAI ChatGPT conversations in Persian and mixed English/Persian.

## Background and problem statement

In real-world LLM chat interfaces (such as ChatGPT, Claude, and Gemini), mixed-direction
Persian text breaks down catastrophically when medical, scientific, or technical terminology
is introduced.

Standard web implementations (such as `<p dir="auto">` or default LTR containers) apply
the Unicode Bidirectional Algorithm's P2/P3 rule ("first strong character"). When an AI
generates a paragraph or sentence that begins with an English technical term, `dir="auto"`
selects LTR. As a result:
- The entire Persian paragraph is laid out left-to-right.
- The leading Latin term appears visually isolated on the far left.
- Persian words flow backwards relative to the paragraph axis.
- Terminal punctuation (periods, question marks, colons) jumps to the opposite margin.
- Neutral arrows (`→`, `←`, `↑`, `↓`) reorder chaotically between RTL and LTR runs.
- Table columns in Markdown invert or misalign.

## Fixture categories

### 1. Leading English terms in Persian paragraphs
- `fa-chatgpt-capsule-001`: `Capsule مثل یک روکش لیز و ضدچسب دور باکتری است.`
- `fa-chatgpt-macrophage-002`: `Macrophage برای بلعیدن باکتری خیلی بهتر عمل میکند...`
- `fa-chatgpt-pyruvate-kinase-003`: `Pyruvate kinase یکی از آن enzymeهایی است...`
- `fa-chatgpt-splenic-macrophages-004`: `1) Splenic macrophages خون از spleen عبور میکند...`
- `fa-chatgpt-spherocyte-005`: `spherocyte واضح ندارد`
- `fa-chatgpt-target-cell-006`: `target-cell pattern تیپیک thalassemia ندارد`
- `fa-chatgpt-reticulocyte-007`: `Reticulocyteها enzyme activity بیشتری...`
- `fa-chatgpt-marginal-zone-028`: `Marginal-zone B cells اینها به polysaccharide capsule...`

### 2. Direction indicators and reaction arrows
- `fa-chatgpt-equation-atp-009`: `PEP + ADP → Pyruvate + ATP`
- `fa-chatgpt-immunology-arrow-010`: `IgG / IgM + C3b → phagocytosis ↑`
- `fa-chatgpt-species-arrow-008`: `S. pneumoniae ← مهمترین`
- `fa-chatgpt-bacteremia-chain-011`: `bacteremia میتواند خیلی سریع بالا برود → sepsis / meningitis / OPSI`
- `fa-chatgpt-pathway-process-027`: `glucose → glycolysis → mitochondria → lots of ATP`
- `fa-chatgpt-bpg-pathway-018`: `PK deficiency = anemia، ولی tissue oxygen delivery...`

### 3. Mixed-script diagnostic tables
- `fa-chatgpt-table-row-pk-019`: `chronic DAT− hemolysis، معمولاً no spherocytes، گاهی echinocytes`
- `fa-chatgpt-table-row-aiha-020`: `spherocytes ولی DAT+`
- `fa-chatgpt-table-row-thalassemia-021`: `microcytosis شدید + target cells`
- `fa-chatgpt-decoder-cell-022`: `2,3-BPG پرتکننده O₂`

### 4. Leading emoji and bullet callouts
- `fa-chatgpt-bottom-line-emoji-012`: `🔴 Bottom line:`
- `fa-chatgpt-memory-hook-emoji-013`: `🧠 تصویر ذهنی`
- `fa-chatgpt-take-home-emoji-014`: `🔑 Take-home:`
- `fa-chatgpt-reality-emoji-015`: `✅ واقعیت: ATP deficiency باعث کاهش بقای RBC...`

### 5. Persian quotations and dialogue
- `fa-chatgpt-quote-persian-016`: `«پس قبلش چرا مشکل ایجاد نمیکنند؟»`
- `fa-chatgpt-quote-slang-017`: `«این آشغالِ بیبرق دیگه رد نمیشه.»`

### 6. Persian affixes on Latin loanwords
- Suffixes like `ها` (plural), `هایی` (indefinite plural), `های` (ezāfe), `اش` (pronominal):
`macrophageها`, `enzymeهایی`, `slitهای`, `RBCهای`, `Reticulocyteها`, `complicationهایی`.

## Validation

All fixtures in this directory validate against `corpus/fixture.schema.json` via
`scripts/generate-chatgpt-fixtures.ts` and are exercised by `packages/core/src/conformance-chatgpt-fa.test.ts`.
69 changes: 69 additions & 0 deletions corpus/fixtures/fa/fa-chatgpt-active-recall-024.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"id": "fa-chatgpt-active-recall-024",
"description": "Active recall test question in Persian with clinical lab values and English test names.",
"text": "یک کودک دارد: Hb 8.5، retic 12%، indirect bilirubin↑، haptoglobin↓، DAT−، splenomegaly، no spherocytes.",
"words": [
"یک",
"کودک",
"دارد",
":",
"Hb",
"8.5",
"،",
"retic",
"12",
"%",
"،",
"indirect",
"bilirubin↑",
"،",
"haptoglobin↓",
"،",
"DAT",
"−",
"،",
"splenomegaly",
"،",
"no",
"spherocytes",
"."
],
"expected": "rtl",
"expectedVisualOrderRightToLeft": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24
],
"tags": [
"fa",
"chatgpt",
"medical",
"lab-values",
"arrows",
"question",
"real-world"
],
"curation": "user-provided",
"nativeSpeakerReviewed": false
}
43 changes: 43 additions & 0 deletions corpus/fixtures/fa/fa-chatgpt-bacteremia-chain-011.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"id": "fa-chatgpt-bacteremia-chain-011",
"description": "Medical pathophysiology chain with Persian prose and English clinical outcome.",
"text": "bacteremia میتواند خیلی سریع بالا برود → sepsis / meningitis / OPSI",
"words": [
"bacteremia",
"میتواند",
"خیلی",
"سریع",
"بالا",
"برود",
"→",
"sepsis",
"/",
"meningitis",
"/",
"OPSI"
],
"expected": "rtl",
"expectedVisualOrderRightToLeft": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12
],
"tags": [
"fa",
"chatgpt",
"medical",
"pathway-arrow",
"real-world"
],
"curation": "user-provided",
"nativeSpeakerReviewed": false
}
Loading
Loading