Enable governed GRB evolution
This commit is contained in:
@@ -27,6 +27,10 @@ from app.services.vector_feature_service import VectorFeatureService
|
||||
|
||||
class TemporalAnalysisService:
|
||||
IDENTITY_COMPARISON_LIMIT = 5_000
|
||||
GOVERNED_GRB_IDENTITY_OPERATORS = {
|
||||
"provision_regional_grb_buildings.py",
|
||||
"provision_regional_grb_context.py",
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def list_series(db: Session, project_id: UUID) -> list[TemporalSeriesRead]:
|
||||
@@ -345,7 +349,9 @@ class TemporalAnalysisService:
|
||||
) -> tuple[TemporalObjectChanges, dict[str, Any], list[str]]:
|
||||
earlier_config = earlier.source_metadata if isinstance(earlier.source_metadata, dict) else {}
|
||||
later_config = later.source_metadata if isinstance(later.source_metadata, dict) else {}
|
||||
if not earlier_config.get("identity_stable") or not later_config.get("identity_stable"):
|
||||
earlier_identity = TemporalAnalysisService._identity_contract(earlier)
|
||||
later_identity = TemporalAnalysisService._identity_contract(later)
|
||||
if earlier_identity is None or later_identity is None or earlier_identity != later_identity:
|
||||
return (
|
||||
TemporalObjectChanges(available=False),
|
||||
{"type": "FeatureCollection", "features": []},
|
||||
@@ -368,8 +374,7 @@ class TemporalAnalysisService:
|
||||
if not full_dataset_area:
|
||||
query = query.filter(ST_Intersects(VectorFeature.geometry, selection_shape))
|
||||
return (
|
||||
query.filter(VectorFeature.source_feature_id.isnot(None))
|
||||
.order_by(VectorFeature.source_feature_id.asc())
|
||||
query.order_by(VectorFeature.source_feature_id.asc())
|
||||
.limit(TemporalAnalysisService.IDENTITY_COMPARISON_LIMIT + 1)
|
||||
.all()
|
||||
)
|
||||
@@ -386,6 +391,16 @@ class TemporalAnalysisService:
|
||||
["Object-level preview was skipped because the selection exceeds the 5,000 feature safety limit."],
|
||||
)
|
||||
|
||||
identity_prefixes = earlier_identity[1]
|
||||
if not TemporalAnalysisService._rows_match_identity_contract(earlier_rows, identity_prefixes) or not (
|
||||
TemporalAnalysisService._rows_match_identity_contract(later_rows, identity_prefixes)
|
||||
):
|
||||
return (
|
||||
TemporalObjectChanges(available=False),
|
||||
{"type": "FeatureCollection", "features": []},
|
||||
["De geselecteerde objecten bevatten geen volledig verifieerbare stabiele bronidentiteit."],
|
||||
)
|
||||
|
||||
earlier_by_id = {str(row.source_feature_id): row for row in earlier_rows if row.source_feature_id}
|
||||
later_by_id = {str(row.source_feature_id): row for row in later_rows if row.source_feature_id}
|
||||
earlier_ids = set(earlier_by_id)
|
||||
@@ -459,3 +474,50 @@ class TemporalAnalysisService:
|
||||
{"type": "FeatureCollection", "features": features},
|
||||
warnings,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _identity_contract(dataset: Dataset) -> tuple[str, tuple[str, ...]] | None:
|
||||
source_metadata = dataset.source_metadata if isinstance(dataset.source_metadata, dict) else {}
|
||||
declared_stable = source_metadata.get("identity_stable")
|
||||
if declared_stable is False:
|
||||
return None
|
||||
|
||||
configured_prefixes = source_metadata.get("identity_prefixes")
|
||||
prefixes = tuple(
|
||||
sorted(
|
||||
{
|
||||
str(value).strip()
|
||||
for value in configured_prefixes
|
||||
if str(value).strip()
|
||||
}
|
||||
)
|
||||
) if isinstance(configured_prefixes, list) else ()
|
||||
if declared_stable is True:
|
||||
return str(source_metadata.get("identity_scheme") or "declared_source_feature_id"), prefixes
|
||||
|
||||
provenance = dataset.provenance_metadata if isinstance(dataset.provenance_metadata, dict) else {}
|
||||
if (
|
||||
dataset.source_name != "grb"
|
||||
or not str(dataset.temporal_series_key or "").startswith("grb:")
|
||||
or source_metadata.get("authority_level") != "authoritative"
|
||||
or source_metadata.get("geometry_clipped_to_area") is not True
|
||||
or provenance.get("operator_tool") not in TemporalAnalysisService.GOVERNED_GRB_IDENTITY_OPERATORS
|
||||
or provenance.get("reference_truncated") is not False
|
||||
):
|
||||
return None
|
||||
|
||||
if dataset.reference_layer_name == "buildings" and source_metadata.get("collection") == "GRB/GBG":
|
||||
prefixes = ("GBG.",)
|
||||
else:
|
||||
collections = source_metadata.get("collections")
|
||||
if not isinstance(collections, list) or not collections:
|
||||
return None
|
||||
prefixes = tuple(sorted(f"{str(collection)}:{str(collection)}." for collection in collections))
|
||||
return "grb_ogc_feature_id", prefixes
|
||||
|
||||
@staticmethod
|
||||
def _rows_match_identity_contract(rows: list[VectorFeature], prefixes: tuple[str, ...]) -> bool:
|
||||
identities = [str(row.source_feature_id or "").strip() for row in rows]
|
||||
if any(not identity for identity in identities) or len(set(identities)) != len(identities):
|
||||
return False
|
||||
return not prefixes or all(identity.startswith(prefixes) for identity in identities)
|
||||
|
||||
Reference in New Issue
Block a user