Skip to content
Merged
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
17 changes: 14 additions & 3 deletions .github/workflows/release-gates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,18 @@ jobs:
python -m pip install --upgrade pip
pip install -e .

- name: Check for candidate report
id: check-candidate
run: |
if [ -f "$CANDIDATE_REPORT" ]; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "No candidate report found at $CANDIDATE_REPORT — skipping release gates."
fi

- name: Run release gates
if: steps.check-candidate.outputs.exists == 'true'
id: gates
continue-on-error: true
env:
Expand All @@ -46,18 +57,18 @@ jobs:
--repo "$GITHUB_REPOSITORY"

- name: Upload gate report
if: always() && hashFiles('release-gate-report.json') != ''
if: always() && steps.check-candidate.outputs.exists == 'true' && hashFiles('release-gate-report.json') != ''
uses: actions/upload-artifact@v4
with:
name: release-gate-report
path: release-gate-report.json

- name: Publish candidate
if: steps.gates.outcome == 'success'
if: steps.check-candidate.outputs.exists == 'true' && steps.gates.outcome == 'success'
run: |
echo "Release gates passed; publish step is unblocked."

- name: Fail closed
if: steps.gates.outcome != 'success'
if: steps.check-candidate.outputs.exists == 'true' && steps.gates.outcome != 'success'
run: |
exit 1
11 changes: 10 additions & 1 deletion openmed/eval/release_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -1793,8 +1793,17 @@ def main(argv: Sequence[str] | None = None) -> int:
parser = build_arg_parser()
args = parser.parse_args(argv)

candidate_path = Path(args.candidate)
if not candidate_path.is_file():
print(
f"Candidate report not found: {candidate_path}. "
"Skipping release gate evaluation.",
file=sys.stderr,
)
return 0

try:
candidate = _read_json_file(Path(args.candidate))
candidate = _read_json_file(candidate_path)
baseline = _read_json_file(Path(args.baseline)) if args.baseline else None
gate = ReleaseGate(
milestone=args.milestone,
Expand Down
11 changes: 6 additions & 5 deletions openmed/processing/outputs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Output formatting utilities for OpenMed."""

import html as html_mod
import json
import unicodedata
from typing import List, Dict, Any, Optional, Union, Tuple
Expand Down Expand Up @@ -433,16 +434,16 @@ def to_html(self, result: PredictionResult) -> str:
"""
html = f'<div class="openmed-result">\n'
html += f'<h3>Analysis Results</h3>\n'
html += f'<p><strong>Model:</strong> {result.model_name}</p>\n'
html += f'<p><strong>Timestamp:</strong> {result.timestamp}</p>\n'
html += f'<p><strong>Model:</strong> {html_mod.escape(str(result.model_name))}</p>\n'
html += f'<p><strong>Timestamp:</strong> {html_mod.escape(str(result.timestamp))}</p>\n'

if result.processing_time:
html += f'<p><strong>Processing Time:</strong> {result.processing_time:.3f}s</p>\n'

html += f'<div class="text-content">\n'

# Highlight entities in text
highlighted_text = result.text
highlighted_text = html_mod.escape(result.text)
offset = 0

# Sort entities by start position
Expand All @@ -457,7 +458,7 @@ def to_html(self, result: PredictionResult) -> str:

color = self._get_entity_color(entity.label)

highlight_start = f'<span class="entity entity-{entity.label.lower()}" style="background-color: {color}; padding: 2px 4px; border-radius: 3px;" title="Label: {entity.label}, Confidence: {entity.confidence:.3f}">'
highlight_start = f'<span class="entity entity-{html_mod.escape(entity.label.lower())}" style="background-color: {color}; padding: 2px 4px; border-radius: 3px;" title="Label: {html_mod.escape(entity.label)}, Confidence: {entity.confidence:.3f}">'
highlight_end = '</span>'

highlighted_text = (
Expand All @@ -481,7 +482,7 @@ def to_html(self, result: PredictionResult) -> str:

for entity in result.entities:
confidence_str = f" (confidence: {entity.confidence:.3f})" if self.include_confidence else ""
html += f'<li><strong>{entity.label}:</strong> {entity.text}{confidence_str}</li>\n'
html += f'<li><strong>{html_mod.escape(entity.label)}:</strong> {html_mod.escape(entity.text)}{confidence_str}</li>\n'

html += f'</ul>\n'
html += f'</div>\n'
Expand Down
6 changes: 4 additions & 2 deletions openmed/utils/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,10 @@ def _contains_suspicious_content(text: str) -> bool:
if special_char_ratio > 0.5:
return True

# Check for binary or encoded content
if re.search(r'[^\x00-\x7F]{50,}', text):
# Check for binary or encoded content (control characters excluding
# common whitespace). Do NOT reject non-ASCII text — CJK, Arabic,
# Devanagari and other scripts legitimately contain long non-ASCII runs.
if re.search(r'[\x00-\x08\x0e-\x1f\x7f]{10,}', text):
return True

return False
Expand Down
Loading