dyff-schema 0.38.5__tar.gz → 0.39.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dyff-schema might be problematic. Click here for more details.
- {dyff_schema-0.38.5/dyff_schema.egg-info → dyff_schema-0.39.0}/PKG-INFO +1 -1
- dyff_schema-0.39.0/dyff/schema/_version.py +2 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/platform.py +214 -23
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/requests.py +11 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0/dyff_schema.egg-info}/PKG-INFO +1 -1
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff_schema.egg-info/SOURCES.txt +2 -1
- dyff_schema-0.39.0/tests/test_score_curves.py +139 -0
- dyff_schema-0.38.5/dyff/schema/_version.py +0 -2
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/.gitignore +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/.gitlab-ci.yml +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/.idea/dyff-schema.iml +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/.licenserc.yaml +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/.pre-commit-config.yaml +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/.prettierignore +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/.secrets.baseline +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/CODE_OF_CONDUCT.md +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/LICENSE +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/NOTICE +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/README.md +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/__init__.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/adapters.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/annotations.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/base.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/commands.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/copydoc.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/dataset/__init__.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/dataset/arrow.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/dataset/binary.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/dataset/classification.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/dataset/embedding.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/dataset/text.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/dataset/vision.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/errors.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/ids.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/io/__init__.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/io/vllm.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/platform.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/py.typed +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/quantity.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/requests.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/responses.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/test.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/__init__.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/__init__.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/adapters.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/base.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/commands.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/dataset/__init__.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/dataset/arrow.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/dataset/binary.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/dataset/classification.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/dataset/embedding.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/dataset/text.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/dataset/vision.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/io/__init__.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/io/vllm.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/oci.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/responses.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/test.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/v0/r1/version.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff/schema/version.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff_schema.egg-info/dependency_links.txt +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff_schema.egg-info/requires.txt +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/dyff_schema.egg-info/top_level.txt +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/makefile +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/pyproject.toml +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/setup.cfg +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/tests/test_adapters.py +0 -0
- {dyff_schema-0.38.5 → dyff_schema-0.39.0}/tests/test_import.py +0 -0
|
@@ -216,6 +216,7 @@ class Entities(str, enum.Enum):
|
|
|
216
216
|
AuditProcedure = "AuditProcedure"
|
|
217
217
|
Challenge = "Challenge"
|
|
218
218
|
Concern = "Concern"
|
|
219
|
+
Curve = "Curve"
|
|
219
220
|
DataSource = "DataSource"
|
|
220
221
|
Dataset = "Dataset"
|
|
221
222
|
Documentation = "Documentation"
|
|
@@ -256,6 +257,7 @@ class Resources(str, enum.Enum):
|
|
|
256
257
|
AuditProcedure = "auditprocedures"
|
|
257
258
|
Challenge = "challenges"
|
|
258
259
|
Concern = "concerns"
|
|
260
|
+
Curve = "curves"
|
|
259
261
|
Dataset = "datasets"
|
|
260
262
|
DataSource = "datasources"
|
|
261
263
|
Descriptor = "descriptors"
|
|
@@ -309,6 +311,7 @@ EntityKindLiteral = Literal[
|
|
|
309
311
|
"Audit",
|
|
310
312
|
"AuditProcedure",
|
|
311
313
|
"Challenge",
|
|
314
|
+
"Curve",
|
|
312
315
|
"DataSource",
|
|
313
316
|
"Dataset",
|
|
314
317
|
"Evaluation",
|
|
@@ -435,7 +438,7 @@ class Labeled(DyffSchemaBaseModel):
|
|
|
435
438
|
class Annotation(DyffSchemaBaseModel):
|
|
436
439
|
key: str = pydantic.Field(
|
|
437
440
|
pattern=_k8s_label_key_regex(),
|
|
438
|
-
max_length=
|
|
441
|
+
max_length=_k8s_label_key_maxlen(),
|
|
439
442
|
description="The annotation key. A DNS label with an optional DNS domain prefix."
|
|
440
443
|
" For example: 'my-key', 'your.com/key_0'. Names prefixed with"
|
|
441
444
|
" 'dyff.io/', 'subdomain.dyff.io/', etc. are reserved.\n\n"
|
|
@@ -541,6 +544,7 @@ class DyffEntity(Status, Labeled, SchemaVersion, DyffModelWithID):
|
|
|
541
544
|
"Audit",
|
|
542
545
|
"AuditProcedure",
|
|
543
546
|
"Challenge",
|
|
547
|
+
"Curve",
|
|
544
548
|
"DataSource",
|
|
545
549
|
"Dataset",
|
|
546
550
|
"Evaluation",
|
|
@@ -1442,6 +1446,21 @@ class VolumeMount(DyffSchemaBaseModel):
|
|
|
1442
1446
|
default=None, description="Configuration for Scratch volume mounts."
|
|
1443
1447
|
)
|
|
1444
1448
|
|
|
1449
|
+
@pydantic.model_validator(mode="after")
|
|
1450
|
+
def _validate_kind_matches_payload(self):
|
|
1451
|
+
"""Ensure payload fields match declared kind."""
|
|
1452
|
+
if self.kind == VolumeMountKind.data:
|
|
1453
|
+
if self.data is None or self.scratch is not None:
|
|
1454
|
+
raise ValueError(
|
|
1455
|
+
"VolumeMount(kind='Data') requires .data and forbids .scratch"
|
|
1456
|
+
)
|
|
1457
|
+
elif self.kind == VolumeMountKind.scratch:
|
|
1458
|
+
if self.scratch is None or self.data is not None:
|
|
1459
|
+
raise ValueError(
|
|
1460
|
+
"VolumeMount(kind='Scratch') requires .scratch and forbids .data"
|
|
1461
|
+
)
|
|
1462
|
+
return self
|
|
1463
|
+
|
|
1445
1464
|
|
|
1446
1465
|
class Container(DyffSchemaBaseModel):
|
|
1447
1466
|
"""Configuration of a runnable container backed by either an image hosted in an
|
|
@@ -1489,6 +1508,15 @@ class Container(DyffSchemaBaseModel):
|
|
|
1489
1508
|
" which might be configured in the container image.",
|
|
1490
1509
|
)
|
|
1491
1510
|
|
|
1511
|
+
@pydantic.model_validator(mode="after")
|
|
1512
|
+
def _validate_image_or_ref(self):
|
|
1513
|
+
"""Exactly one of image or imageRef must be set."""
|
|
1514
|
+
has_image = self.image is not None
|
|
1515
|
+
has_ref = self.imageRef is not None
|
|
1516
|
+
if has_image == has_ref:
|
|
1517
|
+
raise ValueError("Exactly one of 'image' or 'imageRef' must be set.")
|
|
1518
|
+
return self
|
|
1519
|
+
|
|
1492
1520
|
|
|
1493
1521
|
class InferenceServiceRunner(Container):
|
|
1494
1522
|
"""Configuration of the runtime environment to use to run an inference service.
|
|
@@ -2101,6 +2129,34 @@ class ScoreSpec(DyffSchemaBaseModel):
|
|
|
2101
2129
|
return format.format(quantity=quantity, unit=unit)
|
|
2102
2130
|
|
|
2103
2131
|
|
|
2132
|
+
class CurveSpec(DyffSchemaBaseModel):
|
|
2133
|
+
"""Metadata describing the curve dimensions and how to read them.
|
|
2134
|
+
|
|
2135
|
+
- `dimensions` supplies per-dimension metadata (name -> ScoreSpec).
|
|
2136
|
+
- Use these keys as columns in `CurveData.points`.
|
|
2137
|
+
"""
|
|
2138
|
+
|
|
2139
|
+
name: str = pydantic.Field(
|
|
2140
|
+
description="Unique key for this curve within the Method context.",
|
|
2141
|
+
pattern=identifier_regex(),
|
|
2142
|
+
max_length=identifier_maxlen(),
|
|
2143
|
+
)
|
|
2144
|
+
title: str = pydantic.Field(
|
|
2145
|
+
description="Human-friendly title for the curve.",
|
|
2146
|
+
max_length=title_maxlen(),
|
|
2147
|
+
)
|
|
2148
|
+
summary: str = pydantic.Field(
|
|
2149
|
+
description="Short description of what the curve shows.",
|
|
2150
|
+
max_length=summary_maxlen(),
|
|
2151
|
+
)
|
|
2152
|
+
dimensions: dict[str, ScoreSpec] = pydantic.Field(
|
|
2153
|
+
description=(
|
|
2154
|
+
"Per-dimension metadata. Keys must match the columns stored in CurveData.points. "
|
|
2155
|
+
"Typical keys might include 'x', 'y', 'threshold', 'tp', 'fp', etc."
|
|
2156
|
+
)
|
|
2157
|
+
)
|
|
2158
|
+
|
|
2159
|
+
|
|
2104
2160
|
class MethodBase(DyffSchemaBaseModel):
|
|
2105
2161
|
name: str = pydantic.Field(description="Descriptive name of the Method.")
|
|
2106
2162
|
|
|
@@ -2131,7 +2187,7 @@ class MethodBase(DyffSchemaBaseModel):
|
|
|
2131
2187
|
description="Specification of the Method output."
|
|
2132
2188
|
)
|
|
2133
2189
|
|
|
2134
|
-
scores: list[ScoreSpec] = pydantic.Field(
|
|
2190
|
+
scores: list[Union[ScoreSpec, CurveSpec]] = pydantic.Field(
|
|
2135
2191
|
default_factory=list,
|
|
2136
2192
|
description="Specifications of the Scores that this Method produces.",
|
|
2137
2193
|
)
|
|
@@ -2149,12 +2205,13 @@ class MethodBase(DyffSchemaBaseModel):
|
|
|
2149
2205
|
)
|
|
2150
2206
|
|
|
2151
2207
|
@pydantic.field_validator("scores")
|
|
2152
|
-
def _scores_validator(cls, scores
|
|
2153
|
-
if
|
|
2154
|
-
|
|
2208
|
+
def _scores_validator(cls, scores):
|
|
2209
|
+
score_specs = [s for s in scores if isinstance(s, ScoreSpec)]
|
|
2210
|
+
if score_specs:
|
|
2211
|
+
primary_count = sum(s.priority == "primary" for s in score_specs)
|
|
2155
2212
|
if primary_count != 1:
|
|
2156
2213
|
raise ValueError(
|
|
2157
|
-
"scores: Must have exactly one
|
|
2214
|
+
"scores: Must have exactly one primary ScoreSpec (curves excluded)"
|
|
2158
2215
|
)
|
|
2159
2216
|
return scores
|
|
2160
2217
|
|
|
@@ -2329,17 +2386,19 @@ class ScoreData(ScoreSpec):
|
|
|
2329
2386
|
description="The Analysis that generated the current score instance."
|
|
2330
2387
|
)
|
|
2331
2388
|
|
|
2332
|
-
quantity: float = pydantic.Field(
|
|
2333
|
-
description="
|
|
2389
|
+
quantity: float | None = pydantic.Field(
|
|
2390
|
+
default=None, description="Numeric quantity for scalar scores."
|
|
2391
|
+
)
|
|
2392
|
+
points: dict[str, list[float]] | None = pydantic.Field(
|
|
2393
|
+
default=None,
|
|
2394
|
+
description="Aligned vectors for curve scores. Keys should match curve.dimensions.",
|
|
2334
2395
|
)
|
|
2335
2396
|
|
|
2336
2397
|
quantityString: str = pydantic.Field(
|
|
2337
|
-
description="
|
|
2338
|
-
" after processing with the .format specification."
|
|
2398
|
+
description="Formatted string representation of .quantity (scalar) or a summary string (curve)."
|
|
2339
2399
|
)
|
|
2340
|
-
|
|
2341
2400
|
text: str = pydantic.Field(
|
|
2342
|
-
description="
|
|
2401
|
+
description="Short text description of what the value/curve means.",
|
|
2343
2402
|
max_length=summary_maxlen(),
|
|
2344
2403
|
)
|
|
2345
2404
|
|
|
@@ -2355,6 +2414,53 @@ class Score(ScoreData):
|
|
|
2355
2414
|
id: str = pydantic.Field(description="Unique identifier of the entity")
|
|
2356
2415
|
|
|
2357
2416
|
|
|
2417
|
+
class CurveData(CurveSpec):
|
|
2418
|
+
"""An instance of a curve produced by an analysis.
|
|
2419
|
+
|
|
2420
|
+
- Inherits the curve spec (name/title/summary/dimensions) directly.
|
|
2421
|
+
- `points` is a dict-of-lists: each key is a dimension name defined in `dimensions`.
|
|
2422
|
+
- All lists must be the same (non-zero) length.
|
|
2423
|
+
|
|
2424
|
+
spec: CurveSpec = pydantic.Field(description="The CurveSpec this data conforms to.")
|
|
2425
|
+
"""
|
|
2426
|
+
|
|
2427
|
+
metadata: ScoreMetadata = pydantic.Field(
|
|
2428
|
+
description="Indexing metadata (method required).",
|
|
2429
|
+
)
|
|
2430
|
+
analysis: str = pydantic.Field(
|
|
2431
|
+
description="ID of the Analysis that produced this curve."
|
|
2432
|
+
)
|
|
2433
|
+
points: dict[str, list[float]] = pydantic.Field(
|
|
2434
|
+
description="Aligned vectors for each dimension; all lists must have equal length >= 1."
|
|
2435
|
+
)
|
|
2436
|
+
|
|
2437
|
+
@pydantic.model_validator(mode="after")
|
|
2438
|
+
def _validate_points(self):
|
|
2439
|
+
if not self.points:
|
|
2440
|
+
raise ValueError("CurveData.points must not be empty")
|
|
2441
|
+
|
|
2442
|
+
lengths = {k: len(v) for k, v in self.points.items()}
|
|
2443
|
+
uniq = set(lengths.values())
|
|
2444
|
+
if len(uniq) != 1:
|
|
2445
|
+
raise ValueError(
|
|
2446
|
+
f"All vectors must have equal length; got lengths {lengths}"
|
|
2447
|
+
)
|
|
2448
|
+
n = next(iter(uniq))
|
|
2449
|
+
if n < 1:
|
|
2450
|
+
raise ValueError("Vectors must contain at least 1 point")
|
|
2451
|
+
|
|
2452
|
+
missing = [k for k in self.dimensions.keys() if k not in self.points]
|
|
2453
|
+
if missing:
|
|
2454
|
+
raise ValueError(f"points is missing required dimensions: {missing}")
|
|
2455
|
+
return self
|
|
2456
|
+
|
|
2457
|
+
|
|
2458
|
+
class Curve(CurveData):
|
|
2459
|
+
kind: Literal["Curve"] = Entities.Curve.value
|
|
2460
|
+
|
|
2461
|
+
id: str = pydantic.Field(description="Unique identifier of the entity")
|
|
2462
|
+
|
|
2463
|
+
|
|
2358
2464
|
# ---------------------------------------------------------------------------
|
|
2359
2465
|
# OCI artifacts
|
|
2360
2466
|
|
|
@@ -2635,6 +2741,19 @@ class ChallengeTaskSchedule(DyffSchemaBaseModel):
|
|
|
2635
2741
|
description="Teams are limited to this many submissions per cycle.",
|
|
2636
2742
|
)
|
|
2637
2743
|
|
|
2744
|
+
@pydantic.model_validator(mode="after")
|
|
2745
|
+
def _validate_times(self):
|
|
2746
|
+
"""Basic temporal sanity checks."""
|
|
2747
|
+
if (
|
|
2748
|
+
self.openingTime
|
|
2749
|
+
and self.closingTime
|
|
2750
|
+
and self.closingTime <= self.openingTime
|
|
2751
|
+
):
|
|
2752
|
+
raise ValueError("closingTime must be after openingTime")
|
|
2753
|
+
if self.submissionCycleDuration <= timedelta(0):
|
|
2754
|
+
raise ValueError("submissionCycleDuration must be positive")
|
|
2755
|
+
return self
|
|
2756
|
+
|
|
2638
2757
|
|
|
2639
2758
|
class ChallengeTaskRules(DyffSchemaBaseModel):
|
|
2640
2759
|
"""The rules of the challenge."""
|
|
@@ -2656,6 +2775,18 @@ class ChallengeTaskContent(DyffSchemaBaseModel):
|
|
|
2656
2775
|
)
|
|
2657
2776
|
|
|
2658
2777
|
|
|
2778
|
+
class SubmissionStructure(DyffSchemaBaseModel):
|
|
2779
|
+
submissionKind: EntityKindLiteral = pydantic.Field(
|
|
2780
|
+
default=Entities.InferenceService.value,
|
|
2781
|
+
description="The kind of entity that you can submit to this task.",
|
|
2782
|
+
)
|
|
2783
|
+
pipelineKeyword: str = pydantic.Field(
|
|
2784
|
+
default="submission",
|
|
2785
|
+
description="The keyword parameter where the submitted entity ID"
|
|
2786
|
+
" should be passed to the assessment Pipeline.",
|
|
2787
|
+
)
|
|
2788
|
+
|
|
2789
|
+
|
|
2659
2790
|
class ChallengeTaskBase(DyffSchemaBaseModel):
|
|
2660
2791
|
"""The part of the ChallengeTask spec that is not system-populated."""
|
|
2661
2792
|
|
|
@@ -2667,9 +2798,14 @@ class ChallengeTaskBase(DyffSchemaBaseModel):
|
|
|
2667
2798
|
" This may appear in URLs and must follow naming restrictions.",
|
|
2668
2799
|
max_length=title_maxlen(),
|
|
2669
2800
|
pattern=r"[a-z0-9-]*",
|
|
2801
|
+
min_length=1,
|
|
2670
2802
|
)
|
|
2671
2803
|
assessment: str = pydantic.Field(
|
|
2672
|
-
description="ID of the
|
|
2804
|
+
description="ID of the Pipeline used to assess the submission."
|
|
2805
|
+
)
|
|
2806
|
+
submissionStructure: SubmissionStructure = pydantic.Field(
|
|
2807
|
+
default_factory=SubmissionStructure,
|
|
2808
|
+
description="How to run the assessment pipeline on a new submission.",
|
|
2673
2809
|
)
|
|
2674
2810
|
rules: ChallengeTaskRules = pydantic.Field(description="The rules for submissions.")
|
|
2675
2811
|
content: ChallengeTaskContent = pydantic.Field(
|
|
@@ -2729,7 +2865,14 @@ class Challenge(DyffEntity):
|
|
|
2729
2865
|
return None
|
|
2730
2866
|
|
|
2731
2867
|
|
|
2732
|
-
class
|
|
2868
|
+
class SubmissionBase(DyffSchemaBaseModel):
|
|
2869
|
+
team: str = pydantic.Field(description="The ID of the team making the submission.")
|
|
2870
|
+
submission: EntityIdentifier = pydantic.Field(
|
|
2871
|
+
description="The resource being submitted for assessment."
|
|
2872
|
+
)
|
|
2873
|
+
|
|
2874
|
+
|
|
2875
|
+
class Submission(DyffEntity, SubmissionBase):
|
|
2733
2876
|
"""A submission of an inference system to a challenge by a team.
|
|
2734
2877
|
|
|
2735
2878
|
All of the constituent resources must already exist. Creating a Submission simply
|
|
@@ -2746,9 +2889,8 @@ class ChallengeSubmission(DyffEntity):
|
|
|
2746
2889
|
task: str = pydantic.Field(
|
|
2747
2890
|
description="The key of the task within the challenge being submitted to."
|
|
2748
2891
|
)
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
description="The ID of the inference service being submitted."
|
|
2892
|
+
pipelineRun: str = pydantic.Field(
|
|
2893
|
+
description="The ID of the PipelineRun for this submission."
|
|
2752
2894
|
)
|
|
2753
2895
|
|
|
2754
2896
|
def dependencies(self) -> list[str]:
|
|
@@ -2765,14 +2907,33 @@ class ChallengeSubmission(DyffEntity):
|
|
|
2765
2907
|
class PipelineEvaluationRequest(EvaluationRequestBase):
|
|
2766
2908
|
kind: Literal["PipelineEvaluationRequest"] = "PipelineEvaluationRequest"
|
|
2767
2909
|
|
|
2910
|
+
def dependencies(self) -> list[str]:
|
|
2911
|
+
if self.inferenceSession is None:
|
|
2912
|
+
raise AssertionError()
|
|
2913
|
+
return [self.dataset, self.inferenceSession.inferenceService]
|
|
2914
|
+
|
|
2915
|
+
@pydantic.model_validator(mode="after")
|
|
2916
|
+
def check_session_not_reference(self):
|
|
2917
|
+
if self.inferenceSession is None or self.inferenceSessionReference is not None:
|
|
2918
|
+
raise ValueError(
|
|
2919
|
+
"evaluations in pipelines must set inferenceSession, not inferenceSessionReference"
|
|
2920
|
+
)
|
|
2921
|
+
return self
|
|
2922
|
+
|
|
2768
2923
|
|
|
2769
2924
|
class PipelineMeasurementRequest(AnalysisRequestBase):
|
|
2770
2925
|
kind: Literal["PipelineMeasurementRequest"] = "PipelineMeasurementRequest"
|
|
2771
2926
|
|
|
2927
|
+
def dependencies(self) -> list[str]:
|
|
2928
|
+
return [self.method] + [x.entity for x in self.inputs]
|
|
2929
|
+
|
|
2772
2930
|
|
|
2773
2931
|
class PipelineSafetyCaseRequest(AnalysisRequestBase):
|
|
2774
2932
|
kind: Literal["PipelineSafetyCaseRequest"] = "PipelineSafetyCaseRequest"
|
|
2775
2933
|
|
|
2934
|
+
def dependencies(self) -> list[str]:
|
|
2935
|
+
return [self.method] + [x.entity for x in self.inputs]
|
|
2936
|
+
|
|
2776
2937
|
|
|
2777
2938
|
PipelineNodeRequest = Union[
|
|
2778
2939
|
PipelineEvaluationRequest,
|
|
@@ -2814,16 +2975,25 @@ class PipelineNode(DyffSchemaBaseModel):
|
|
|
2814
2975
|
|
|
2815
2976
|
class PipelineParameter(DyffSchemaBaseModel):
|
|
2816
2977
|
"""Declares a parameter that can be passed to the pipeline to customize its
|
|
2817
|
-
behavior.
|
|
2978
|
+
behavior.
|
|
2979
|
+
|
|
2980
|
+
By default, the argument will be substituted for the placeholder value
|
|
2981
|
+
``$(keyword)``, where ``keyword`` is the ``.keyword`` property of the
|
|
2982
|
+
parameter. In this case, the value of the argument must be a string.
|
|
2983
|
+
|
|
2984
|
+
If ``.destination`` is specified, the argument will instead overwrite
|
|
2985
|
+
the value at the specified JSON path.
|
|
2986
|
+
"""
|
|
2818
2987
|
|
|
2819
|
-
keyword: str = pydantic.Field(description="The keyword of the
|
|
2820
|
-
destination: str = pydantic.Field(
|
|
2988
|
+
keyword: str = pydantic.Field(description="The keyword of the parameter.")
|
|
2989
|
+
destination: Optional[str] = pydantic.Field(
|
|
2990
|
+
default=None,
|
|
2821
2991
|
description="The field in a pipeline node to substitute with the"
|
|
2822
|
-
" parameter value. Should be a string like 'node_name.field1.field2'."
|
|
2992
|
+
" parameter value. Should be a string like 'node_name.field1.field2'.",
|
|
2823
2993
|
)
|
|
2824
2994
|
description: Optional[str] = pydantic.Field(
|
|
2825
2995
|
default=None,
|
|
2826
|
-
description="A description of the
|
|
2996
|
+
description="A description of the parameter.",
|
|
2827
2997
|
max_length=summary_maxlen(),
|
|
2828
2998
|
)
|
|
2829
2999
|
|
|
@@ -2834,6 +3004,10 @@ class PipelineBase(DyffSchemaBaseModel):
|
|
|
2834
3004
|
description="The nodes in the pipeline graph.",
|
|
2835
3005
|
min_length=1,
|
|
2836
3006
|
)
|
|
3007
|
+
parameters: dict[str, PipelineParameter] = pydantic.Field(
|
|
3008
|
+
default_factory=dict,
|
|
3009
|
+
description="Input parameters used to customize the pipeline.",
|
|
3010
|
+
)
|
|
2837
3011
|
|
|
2838
3012
|
@pydantic.field_validator("nodes", mode="after")
|
|
2839
3013
|
def validate_node_names_match(cls, nodes: dict[str, PipelineNode]):
|
|
@@ -2842,6 +3016,15 @@ class PipelineBase(DyffSchemaBaseModel):
|
|
|
2842
3016
|
raise ValueError(f"nodes[{k}]: dict key must match value.name")
|
|
2843
3017
|
return nodes
|
|
2844
3018
|
|
|
3019
|
+
@pydantic.field_validator("parameters", mode="after")
|
|
3020
|
+
def validate_parameter_keywords_match(
|
|
3021
|
+
cls, parameters: dict[str, PipelineParameter]
|
|
3022
|
+
):
|
|
3023
|
+
for k, v in parameters.items():
|
|
3024
|
+
if k != v.keyword:
|
|
3025
|
+
raise ValueError(f"parameters[{k}]: dict key must match value.keyword")
|
|
3026
|
+
return parameters
|
|
3027
|
+
|
|
2845
3028
|
|
|
2846
3029
|
class Pipeline(DyffEntity, PipelineBase):
|
|
2847
3030
|
"""A set of Dyff workflows that can be executed as a group.
|
|
@@ -3160,6 +3343,7 @@ _DyffEntityTypeRevisable = Union[
|
|
|
3160
3343
|
PipelineRun,
|
|
3161
3344
|
Report,
|
|
3162
3345
|
SafetyCase,
|
|
3346
|
+
Submission,
|
|
3163
3347
|
Team,
|
|
3164
3348
|
UseCase,
|
|
3165
3349
|
]
|
|
@@ -3239,6 +3423,7 @@ _ENTITY_CLASS: dict[Entities, type[DyffEntityType]] = {
|
|
|
3239
3423
|
Entities.Report: Report,
|
|
3240
3424
|
Entities.Revision: Revision,
|
|
3241
3425
|
Entities.SafetyCase: SafetyCase,
|
|
3426
|
+
Entities.Submission: Submission,
|
|
3242
3427
|
Entities.Team: Team,
|
|
3243
3428
|
Entities.UseCase: UseCase,
|
|
3244
3429
|
}
|
|
@@ -3272,6 +3457,7 @@ DyffEntityT = TypeVar(
|
|
|
3272
3457
|
Report,
|
|
3273
3458
|
Revision,
|
|
3274
3459
|
SafetyCase,
|
|
3460
|
+
Submission,
|
|
3275
3461
|
Team,
|
|
3276
3462
|
UseCase,
|
|
3277
3463
|
)
|
|
@@ -3303,7 +3489,6 @@ __all__ = [
|
|
|
3303
3489
|
"Challenge",
|
|
3304
3490
|
"ChallengeContent",
|
|
3305
3491
|
"ChallengeContentPage",
|
|
3306
|
-
"ChallengeSubmission",
|
|
3307
3492
|
"ChallengeTask",
|
|
3308
3493
|
"ChallengeTaskBase",
|
|
3309
3494
|
"ChallengeTaskContent",
|
|
@@ -3315,6 +3500,9 @@ __all__ = [
|
|
|
3315
3500
|
"ConcernBase",
|
|
3316
3501
|
"Container",
|
|
3317
3502
|
"ContainerImageSource",
|
|
3503
|
+
"Curve",
|
|
3504
|
+
"CurveData",
|
|
3505
|
+
"CurveSpec",
|
|
3318
3506
|
"DataSchema",
|
|
3319
3507
|
"Dataset",
|
|
3320
3508
|
"DatasetBase",
|
|
@@ -3440,6 +3628,9 @@ __all__ = [
|
|
|
3440
3628
|
"ScoreMetadataRefs",
|
|
3441
3629
|
"Status",
|
|
3442
3630
|
"StorageSignedURL",
|
|
3631
|
+
"Submission",
|
|
3632
|
+
"SubmissionBase",
|
|
3633
|
+
"SubmissionStructure",
|
|
3443
3634
|
"TagName",
|
|
3444
3635
|
"TagNameType",
|
|
3445
3636
|
"TaskSchema",
|
|
@@ -45,6 +45,7 @@ from .platform import (
|
|
|
45
45
|
PipelineBase,
|
|
46
46
|
PipelineRunBase,
|
|
47
47
|
ReportBase,
|
|
48
|
+
SubmissionBase,
|
|
48
49
|
TagNameType,
|
|
49
50
|
TeamBase,
|
|
50
51
|
summary_maxlen,
|
|
@@ -148,6 +149,10 @@ class ChallengeCreateRequest(DyffEntityCreateRequest):
|
|
|
148
149
|
)
|
|
149
150
|
|
|
150
151
|
|
|
152
|
+
class SubmissionCreateRequest(DyffEntityCreateRequest, SubmissionBase):
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
|
|
151
156
|
class ChallengeTaskCreateRequest(DyffEntityCreateRequest, ChallengeTaskBase):
|
|
152
157
|
pass
|
|
153
158
|
|
|
@@ -398,6 +403,10 @@ class AuditQueryRequest(DyffEntityQueryRequest):
|
|
|
398
403
|
name: Optional[str] = pydantic.Field(default=None)
|
|
399
404
|
|
|
400
405
|
|
|
406
|
+
class ChallengeQueryRequest(DyffEntityQueryRequest):
|
|
407
|
+
pass
|
|
408
|
+
|
|
409
|
+
|
|
401
410
|
class DatasetQueryRequest(DyffEntityQueryRequest):
|
|
402
411
|
name: Optional[str] = pydantic.Field(default=None)
|
|
403
412
|
|
|
@@ -492,6 +501,7 @@ __all__ = [
|
|
|
492
501
|
"AuditQueryRequest",
|
|
493
502
|
"ChallengeContentEditRequest",
|
|
494
503
|
"ChallengeCreateRequest",
|
|
504
|
+
"ChallengeQueryRequest",
|
|
495
505
|
"ChallengeTaskCreateRequest",
|
|
496
506
|
"ChallengeTaskRulesEditRequest",
|
|
497
507
|
"ChallengeTeamCreateRequest",
|
|
@@ -531,6 +541,7 @@ __all__ = [
|
|
|
531
541
|
"ReportQueryRequest",
|
|
532
542
|
"SafetyCaseQueryRequest",
|
|
533
543
|
"ScoreQueryRequest",
|
|
544
|
+
"SubmissionCreateRequest",
|
|
534
545
|
"TeamEditRequest",
|
|
535
546
|
"TeamQueryRequest",
|
|
536
547
|
"UseCaseQueryRequest",
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2024 UL Research Institutes
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import pydantic
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from dyff.schema.platform import (
|
|
8
|
+
CurveData,
|
|
9
|
+
CurveSpec,
|
|
10
|
+
ScoreData,
|
|
11
|
+
ScoreMetadata,
|
|
12
|
+
ScoreMetadataRefs,
|
|
13
|
+
ScoreSpec,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _dim_spec(name: str, title: str) -> ScoreSpec:
|
|
18
|
+
return ScoreSpec(
|
|
19
|
+
name=name,
|
|
20
|
+
title=title,
|
|
21
|
+
summary=f"{title} dimension",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _minimal_curve_spec() -> CurveSpec:
|
|
26
|
+
return CurveSpec(
|
|
27
|
+
name="roc",
|
|
28
|
+
title="ROC Curve",
|
|
29
|
+
summary="Receiver operating characteristic.",
|
|
30
|
+
dimensions={
|
|
31
|
+
"x": _dim_spec("x", "False Positive Rate"),
|
|
32
|
+
"y": _dim_spec("y", "True Positive Rate"),
|
|
33
|
+
"threshold": _dim_spec("threshold", "Threshold"),
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _metadata(method_id: str = "method-123") -> ScoreMetadata:
|
|
39
|
+
return ScoreMetadata(refs=ScoreMetadataRefs(method=method_id))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_curve_data_ok_equal_length_vectors_and_required_dimensions():
|
|
43
|
+
spec = _minimal_curve_spec()
|
|
44
|
+
|
|
45
|
+
cd = CurveData(
|
|
46
|
+
name=spec.name,
|
|
47
|
+
title=spec.title,
|
|
48
|
+
summary=spec.summary,
|
|
49
|
+
dimensions=spec.dimensions,
|
|
50
|
+
metadata=_metadata(),
|
|
51
|
+
analysis="analysis-abc",
|
|
52
|
+
points={
|
|
53
|
+
"x": [0.0, 0.5, 1.0],
|
|
54
|
+
"y": [0.0, 0.7, 1.0],
|
|
55
|
+
"threshold": [1.0, 0.5, 0.0],
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
assert len(cd.points["x"]) == 3
|
|
60
|
+
for k in spec.dimensions.keys():
|
|
61
|
+
assert k in cd.points
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_curve_data_raises_when_points_missing_required_dimension():
|
|
65
|
+
spec = _minimal_curve_spec()
|
|
66
|
+
|
|
67
|
+
with pytest.raises(pydantic.ValidationError):
|
|
68
|
+
CurveData(
|
|
69
|
+
name=spec.name,
|
|
70
|
+
title=spec.title,
|
|
71
|
+
summary=spec.summary,
|
|
72
|
+
dimensions=spec.dimensions,
|
|
73
|
+
metadata=_metadata(),
|
|
74
|
+
analysis="analysis-abc",
|
|
75
|
+
points={"x": [0.0, 1.0], "y": [0.0, 1.0]},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_curve_data_raises_when_vector_lengths_differ():
|
|
80
|
+
spec = _minimal_curve_spec()
|
|
81
|
+
|
|
82
|
+
with pytest.raises(pydantic.ValidationError):
|
|
83
|
+
CurveData(
|
|
84
|
+
name=spec.name,
|
|
85
|
+
title=spec.title,
|
|
86
|
+
summary=spec.summary,
|
|
87
|
+
dimensions=spec.dimensions,
|
|
88
|
+
metadata=_metadata(),
|
|
89
|
+
analysis="analysis-abc",
|
|
90
|
+
points={
|
|
91
|
+
"x": [0.0, 0.5, 1.0],
|
|
92
|
+
"y": [0.0, 1.0],
|
|
93
|
+
"threshold": [1.0, 0.0, -1.0],
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_curve_data_raises_when_points_empty():
|
|
99
|
+
spec = _minimal_curve_spec()
|
|
100
|
+
|
|
101
|
+
with pytest.raises(pydantic.ValidationError):
|
|
102
|
+
CurveData(
|
|
103
|
+
name=spec.name,
|
|
104
|
+
title=spec.title,
|
|
105
|
+
summary=spec.summary,
|
|
106
|
+
dimensions=spec.dimensions,
|
|
107
|
+
metadata=_metadata(),
|
|
108
|
+
analysis="analysis-abc",
|
|
109
|
+
points={},
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_score_data_scalar_accepts_quantity():
|
|
114
|
+
sd_scalar = ScoreData(
|
|
115
|
+
name="auc",
|
|
116
|
+
title="AUC",
|
|
117
|
+
summary="Area under ROC",
|
|
118
|
+
metadata=_metadata(),
|
|
119
|
+
analysis="analysis-abc",
|
|
120
|
+
quantity=0.9123,
|
|
121
|
+
points=None,
|
|
122
|
+
quantityString="0.912",
|
|
123
|
+
text="Higher is better.",
|
|
124
|
+
)
|
|
125
|
+
assert sd_scalar.quantity == 0.9123
|
|
126
|
+
|
|
127
|
+
with pytest.raises(pydantic.ValidationError):
|
|
128
|
+
ScoreData(
|
|
129
|
+
scoreKind="scalar",
|
|
130
|
+
name="auc_bad",
|
|
131
|
+
title="AUC",
|
|
132
|
+
summary="Scalar must not carry points",
|
|
133
|
+
metadata=_metadata(),
|
|
134
|
+
analysis="analysis-abc",
|
|
135
|
+
quantity=0.9,
|
|
136
|
+
points={"x": [0.0, 1.0], "y": [0.0, 1.0]}, # forbidden
|
|
137
|
+
quantityString="0.900",
|
|
138
|
+
text="This should fail.",
|
|
139
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|