|
25 | 25 | HumanLanguageLabel, |
26 | 26 | KeywordsMetaField, |
27 | 27 | LanguageMetaField, |
| 28 | + MentionRelation, |
28 | 29 | MetaFieldName, |
29 | 30 | MetaUtils, |
30 | 31 | NodeItem, |
@@ -508,3 +509,82 @@ def test_data_point_range_end_requires_value() -> None: |
508 | 509 | # a DataPointMention constructed with value explicitly set to None via raw dict |
509 | 510 | with pytest.raises(ValidationError, match="range_end requires value"): |
510 | 511 | DataPointMention.model_validate({"text": "x", "value": None, "range_end": 20.0}) |
| 512 | + |
| 513 | + |
| 514 | +# --------------------------------------------------------------------------- |
| 515 | +# MentionRelation tests |
| 516 | +# --------------------------------------------------------------------------- |
| 517 | + |
| 518 | + |
| 519 | +def test_mention_relation_roundtrip() -> None: |
| 520 | + """Relations are stored and survive a JSON round-trip.""" |
| 521 | + doc = DoclingDocument(name="relation-meta") |
| 522 | + item = doc.add_text( |
| 523 | + label=DocItemLabel.TEXT, |
| 524 | + text="IBM reported revenue of $4B in Q3 2024.", |
| 525 | + ) |
| 526 | + item.meta = BaseMeta( |
| 527 | + entities=EntitiesMetaField( |
| 528 | + mentions=[ |
| 529 | + EntityMention(text="IBM", label="ORG", charspan=(0, 3)), |
| 530 | + DataPointMention( |
| 531 | + text="4 billion USD", |
| 532 | + orig="$4B", |
| 533 | + label="REVENUE", |
| 534 | + charspan=(23, 26), |
| 535 | + value=4.0, |
| 536 | + unit="USD", |
| 537 | + scale="billion", |
| 538 | + normalized_value=4_000_000_000.0, |
| 539 | + display_dp=0, |
| 540 | + ), |
| 541 | + ], |
| 542 | + relations=[ |
| 543 | + MentionRelation(source_idx=0, target_idx=1, label="subject_of", confidence=0.95), |
| 544 | + ], |
| 545 | + ) |
| 546 | + ) |
| 547 | + |
| 548 | + roundtrip = DoclingDocument.model_validate(doc.model_dump(mode="json")) |
| 549 | + assert roundtrip.texts[0].meta is not None |
| 550 | + entities = roundtrip.texts[0].meta.entities |
| 551 | + assert entities is not None |
| 552 | + assert entities.relations is not None |
| 553 | + assert len(entities.relations) == 1 |
| 554 | + |
| 555 | + rel = entities.relations[0] |
| 556 | + assert rel.source_idx == 0 |
| 557 | + assert rel.target_idx == 1 |
| 558 | + assert rel.label == "subject_of" |
| 559 | + assert rel.confidence == 0.95 |
| 560 | + |
| 561 | + |
| 562 | +def test_mention_relation_no_relations_field() -> None: |
| 563 | + """relations defaults to None — existing mentions-only objects still work.""" |
| 564 | + field = EntitiesMetaField(mentions=[EntityMention(text="IBM", label="ORG")]) |
| 565 | + assert field.relations is None |
| 566 | + |
| 567 | + |
| 568 | +def test_mention_relation_out_of_range() -> None: |
| 569 | + """source_idx or target_idx beyond the mentions list raises ValidationError.""" |
| 570 | + mentions = [EntityMention(text="IBM", label="ORG")] |
| 571 | + |
| 572 | + with pytest.raises(ValidationError, match="source_idx=1 is out of range"): |
| 573 | + EntitiesMetaField( |
| 574 | + mentions=mentions, |
| 575 | + relations=[MentionRelation(source_idx=1, target_idx=0, label="subject_of")], |
| 576 | + ) |
| 577 | + |
| 578 | + with pytest.raises(ValidationError, match="target_idx=5 is out of range"): |
| 579 | + EntitiesMetaField( |
| 580 | + mentions=mentions, |
| 581 | + relations=[MentionRelation(source_idx=0, target_idx=5, label="subject_of")], |
| 582 | + ) |
| 583 | + |
| 584 | + |
| 585 | +def test_mention_relation_confidence_bounds() -> None: |
| 586 | + """confidence must be in [0, 1].""" |
| 587 | + with pytest.raises(ValidationError): |
| 588 | + MentionRelation(source_idx=0, target_idx=1, label="x", confidence=1.5) |
| 589 | + with pytest.raises(ValidationError): |
| 590 | + MentionRelation(source_idx=0, target_idx=1, label="x", confidence=-0.1) |
0 commit comments