feat: complete published registry contracts and gated SRO ingestion
All checks were successful
Mostovik Backend CI/CD / Tests and lint (push) Successful in 3m55s
Mostovik Backend CI/CD / Build linux/amd64 release images (push) Successful in 3m43s
Mostovik Backend CI/CD / Deploy and verify internal main (push) Has been skipped
Mostovik Backend CI/CD / Deploy customer main (push) Has been skipped
Mostovik Backend CI/CD / Deploy dev (push) Successful in 1m45s
All checks were successful
Mostovik Backend CI/CD / Tests and lint (push) Successful in 3m55s
Mostovik Backend CI/CD / Build linux/amd64 release images (push) Successful in 3m43s
Mostovik Backend CI/CD / Deploy and verify internal main (push) Has been skipped
Mostovik Backend CI/CD / Deploy customer main (push) Has been skipped
Mostovik Backend CI/CD / Deploy dev (push) Successful in 1m45s
This commit is contained in:
@@ -5,8 +5,31 @@
|
||||
- BackgroundJob - статус фоновых задач
|
||||
"""
|
||||
|
||||
from drf_yasg.utils import swagger_serializer_method
|
||||
from rest_framework import serializers
|
||||
|
||||
SNAPSHOT_SOURCES = (
|
||||
"budget_ubpandnubp",
|
||||
"fns_sme_support_recipients",
|
||||
"sro_membership_check",
|
||||
)
|
||||
JOB_API_STATUSES = (
|
||||
"queued",
|
||||
"running",
|
||||
"retrying",
|
||||
"success",
|
||||
"failed",
|
||||
"cancelled",
|
||||
"skipped",
|
||||
"error",
|
||||
# Deprecated transport aliases retained for existing generated clients.
|
||||
"pending",
|
||||
"started",
|
||||
"retry",
|
||||
"failure",
|
||||
"revoked",
|
||||
)
|
||||
|
||||
|
||||
def _get_frontend_job_status(raw_status: str) -> str:
|
||||
mapping = {
|
||||
@@ -20,6 +43,83 @@ def _get_frontend_job_status(raw_status: str) -> str:
|
||||
return mapping.get(raw_status, raw_status)
|
||||
|
||||
|
||||
def _get_job_source(job) -> str | None:
|
||||
meta = job.meta if isinstance(job.meta, dict) else {}
|
||||
source = meta.get("source") or meta.get("source_key")
|
||||
if source in SNAPSHOT_SOURCES:
|
||||
return source
|
||||
for candidate in SNAPSHOT_SOURCES:
|
||||
if job.task_name == f"parsers.{candidate}.refresh":
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def _get_job_status(job) -> str:
|
||||
if _get_job_source(job) is None:
|
||||
return _get_frontend_job_status(job.status)
|
||||
if (
|
||||
job.status == "success"
|
||||
and isinstance(job.result, dict)
|
||||
and job.result.get("status") == "skipped"
|
||||
):
|
||||
return "skipped"
|
||||
return {
|
||||
"pending": "queued",
|
||||
"started": "running",
|
||||
"retry": "retrying",
|
||||
"success": "success",
|
||||
"failure": "failed",
|
||||
"revoked": "cancelled",
|
||||
}.get(job.status, job.status)
|
||||
|
||||
|
||||
class SnapshotRunMetadataSerializer(serializers.Serializer):
|
||||
"""Source-controlled counters only; raw responses/configuration stay private."""
|
||||
|
||||
source = serializers.ChoiceField(choices=SNAPSHOT_SOURCES, read_only=True)
|
||||
mode = serializers.ChoiceField(choices=("incremental", "full"), read_only=True)
|
||||
candidate_organizations_count = serializers.IntegerField(read_only=True)
|
||||
queried_organizations_count = serializers.IntegerField(read_only=True)
|
||||
not_found_organizations_count = serializers.IntegerField(read_only=True)
|
||||
raw_memberships_count = serializers.IntegerField(read_only=True)
|
||||
found_memberships_count = serializers.IntegerField(read_only=True)
|
||||
published_records_count = serializers.IntegerField(read_only=True)
|
||||
quarantined_records_count = serializers.IntegerField(read_only=True)
|
||||
missing_admission_dates_count = serializers.IntegerField(read_only=True)
|
||||
http_errors_count = serializers.IntegerField(read_only=True)
|
||||
parse_errors_count = serializers.IntegerField(read_only=True)
|
||||
snapshot_records_count = serializers.IntegerField(read_only=True)
|
||||
snapshot_organizations_count = serializers.IntegerField(read_only=True)
|
||||
batch_published_records_count = serializers.IntegerField(read_only=True)
|
||||
updated_records_count = serializers.IntegerField(read_only=True)
|
||||
upstream_registry_date = serializers.DateField(read_only=True, allow_null=True)
|
||||
|
||||
|
||||
class SnapshotJobResultSerializer(SnapshotRunMetadataSerializer):
|
||||
"""Published registry result; legacy task results retain their existing shape."""
|
||||
|
||||
status = serializers.ChoiceField(choices=("success", "skipped"), read_only=True)
|
||||
source = serializers.ChoiceField(choices=SNAPSHOT_SOURCES, read_only=True)
|
||||
batch_id = serializers.IntegerField(read_only=True)
|
||||
load_id = serializers.IntegerField(read_only=True)
|
||||
artifact_id = serializers.UUIDField(read_only=True)
|
||||
records_count = serializers.IntegerField(read_only=True)
|
||||
organizations_count = serializers.IntegerField(read_only=True)
|
||||
raw_records_count = serializers.IntegerField(read_only=True)
|
||||
published_records_count = serializers.IntegerField(read_only=True)
|
||||
quarantine_records_count = serializers.IntegerField(read_only=True)
|
||||
skipped_records_count = serializers.IntegerField(read_only=True)
|
||||
snapshot_date = serializers.DateField(read_only=True, allow_null=True)
|
||||
source_published_at = serializers.DateField(read_only=True, allow_null=True)
|
||||
version = serializers.CharField(read_only=True, allow_blank=True, allow_null=True)
|
||||
checksum_sha256 = serializers.CharField(
|
||||
read_only=True, allow_blank=True, allow_null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
swagger_schema_fields = {"additional_properties": True}
|
||||
|
||||
|
||||
def _get_task_short_name(task_name: str) -> str:
|
||||
return task_name.rsplit(".", 1)[-1] if task_name else task_name
|
||||
|
||||
@@ -35,17 +135,59 @@ class BackgroundJobSerializer(serializers.Serializer):
|
||||
status = serializers.SerializerMethodField()
|
||||
progress = serializers.IntegerField(read_only=True)
|
||||
message = serializers.SerializerMethodField()
|
||||
result = serializers.JSONField(read_only=True)
|
||||
result = serializers.SerializerMethodField()
|
||||
meta = serializers.SerializerMethodField()
|
||||
source = serializers.SerializerMethodField()
|
||||
error = serializers.SerializerMethodField()
|
||||
|
||||
def to_representation(self, instance):
|
||||
payload = super().to_representation(instance)
|
||||
if _get_job_source(instance) is None:
|
||||
payload.pop("source", None)
|
||||
payload.pop("meta", None)
|
||||
return payload
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=SnapshotRunMetadataSerializer())
|
||||
def get_meta(self, obj):
|
||||
metadata = obj.meta if isinstance(obj.meta, dict) else {}
|
||||
return SnapshotRunMetadataSerializer(
|
||||
{**metadata, "source": _get_job_source(obj)}
|
||||
).data
|
||||
|
||||
@swagger_serializer_method(
|
||||
serializer_or_field=SnapshotJobResultSerializer(allow_null=True)
|
||||
)
|
||||
def get_result(self, obj):
|
||||
return obj.result
|
||||
|
||||
@swagger_serializer_method(
|
||||
serializer_or_field=serializers.ChoiceField(
|
||||
choices=SNAPSHOT_SOURCES, allow_null=True
|
||||
)
|
||||
)
|
||||
def get_source(self, obj):
|
||||
return _get_job_source(obj)
|
||||
|
||||
@swagger_serializer_method(
|
||||
serializer_or_field=serializers.CharField(allow_null=True)
|
||||
)
|
||||
def get_message(self, obj):
|
||||
return obj.progress_message or None
|
||||
|
||||
@swagger_serializer_method(
|
||||
serializer_or_field=serializers.CharField(allow_null=True)
|
||||
)
|
||||
def get_error(self, obj):
|
||||
return obj.error or None
|
||||
|
||||
@swagger_serializer_method(
|
||||
serializer_or_field=serializers.ChoiceField(
|
||||
choices=JOB_API_STATUSES,
|
||||
help_text="Registry jobs use queued/running/retrying/success/failed/cancelled/skipped. Raw pending/started/retry/failure/revoked are deprecated legacy aliases.",
|
||||
)
|
||||
)
|
||||
def get_status(self, obj):
|
||||
return _get_frontend_job_status(obj.status)
|
||||
return _get_job_status(obj)
|
||||
|
||||
|
||||
class BackgroundJobListSerializer(serializers.Serializer):
|
||||
@@ -57,12 +199,33 @@ class BackgroundJobListSerializer(serializers.Serializer):
|
||||
name = serializers.SerializerMethodField()
|
||||
status = serializers.SerializerMethodField()
|
||||
progress = serializers.IntegerField(read_only=True)
|
||||
source = serializers.SerializerMethodField()
|
||||
|
||||
def to_representation(self, instance):
|
||||
payload = super().to_representation(instance)
|
||||
if _get_job_source(instance) is None:
|
||||
payload.pop("source", None)
|
||||
return payload
|
||||
|
||||
@swagger_serializer_method(
|
||||
serializer_or_field=serializers.ChoiceField(
|
||||
choices=SNAPSHOT_SOURCES, allow_null=True
|
||||
)
|
||||
)
|
||||
def get_source(self, obj):
|
||||
return _get_job_source(obj)
|
||||
|
||||
def get_name(self, obj):
|
||||
return _get_task_short_name(obj.task_name)
|
||||
|
||||
@swagger_serializer_method(
|
||||
serializer_or_field=serializers.ChoiceField(
|
||||
choices=JOB_API_STATUSES,
|
||||
help_text="Registry jobs use queued/running/retrying/success/failed/cancelled/skipped. Raw pending/started/retry/failure/revoked are deprecated legacy aliases.",
|
||||
)
|
||||
)
|
||||
def get_status(self, obj):
|
||||
return _get_frontend_job_status(obj.status)
|
||||
return _get_job_status(obj)
|
||||
|
||||
|
||||
class BackgroundJobListResponseSerializer(serializers.Serializer):
|
||||
|
||||
Reference in New Issue
Block a user