Improve pairing planning and statistics visibility
This commit is contained in:
@@ -17,12 +17,7 @@ router = APIRouter(prefix="/ops-maintenance", tags=["ops-maintenance"])
|
||||
|
||||
class CleanupRequest(BaseModel):
|
||||
confirm: bool = False
|
||||
delete_task_records: bool = True
|
||||
delete_logs: bool = True
|
||||
delete_production_records: bool = True
|
||||
delete_result_products: bool = True
|
||||
delete_production_dirs: bool = True
|
||||
delete_task_pool_dir: bool = True
|
||||
delete_landsar_work_dir: bool = True
|
||||
|
||||
|
||||
@router.get("/tasks")
|
||||
|
||||
@@ -329,6 +329,7 @@ async def find_pairs_endpoint(
|
||||
network_run_id=pairing_metadata.get("network_run_id"),
|
||||
candidate_count=int(pairing_metadata.get("candidate_count") or 0),
|
||||
selected_edge_count=int(pairing_metadata.get("selected_edge_count") or 0),
|
||||
coverage=None,
|
||||
)
|
||||
except Exception as e:
|
||||
if isinstance(e, HTTPException):
|
||||
@@ -339,6 +340,66 @@ async def find_pairs_endpoint(
|
||||
raise HTTPException(status_code=500, detail="处理 AOI 或查找干涉对时发生错误,请查看后端日志")
|
||||
|
||||
|
||||
@router.post("/pairing/coverage-plan", response_model=PairingResponse)
|
||||
async def build_coverage_pairing_plan_endpoint(
|
||||
params: PairingRequest = Depends(get_pairing_request_from_form),
|
||||
target_date_from: str = Form(...),
|
||||
target_date_to: str = Form(...),
|
||||
extension_days: int = Form(15),
|
||||
max_pairs: int = Form(200),
|
||||
target_coverage_ratio: float = Form(0.98),
|
||||
min_new_coverage_ratio: float = Form(0.0005),
|
||||
files: Optional[List[UploadFile]] = File(None),
|
||||
aoi_geojson: Optional[str] = Form(None),
|
||||
require_orbit_data: bool = Form(True),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
resolved_aoi = await _parse_aoi_from_files(files)
|
||||
if resolved_aoi is None:
|
||||
resolved_aoi = _parse_aoi_geojson_form_value(aoi_geojson)
|
||||
|
||||
if files and resolved_aoi is None:
|
||||
raise HTTPException(status_code=400, detail="Uploaded files must include a valid SHP or GeoJSON AOI.")
|
||||
|
||||
aoi_wkt = resolved_aoi[0] if resolved_aoi else None
|
||||
response_aoi_geojson = resolved_aoi[1] if resolved_aoi else None
|
||||
if not aoi_wkt:
|
||||
raise HTTPException(status_code=400, detail="区域空间覆盖规划必须选择行政区或上传 AOI。")
|
||||
coverage_params = params.model_copy(update={"strategy": "dinsar_province_coverage"})
|
||||
pairs, runtime_warnings, pairing_metadata = await spatial_service.find_dinsar_coverage_pairs(
|
||||
db,
|
||||
coverage_params,
|
||||
target_date_from=target_date_from,
|
||||
target_date_to=target_date_to,
|
||||
extension_days=extension_days,
|
||||
max_pairs=max_pairs,
|
||||
target_coverage_ratio=target_coverage_ratio,
|
||||
min_new_coverage_ratio=min_new_coverage_ratio,
|
||||
aoi_wkt=aoi_wkt,
|
||||
require_orbit_data=require_orbit_data,
|
||||
)
|
||||
return PairingResponse(
|
||||
pairs=pairs,
|
||||
aoi_geojson=response_aoi_geojson,
|
||||
warnings=runtime_warnings,
|
||||
fallback_used=bool(pairing_metadata.get("fallback_used")),
|
||||
degraded=bool(pairing_metadata.get("degraded")),
|
||||
policy_version=pairing_metadata.get("policy_version"),
|
||||
network_run_id=pairing_metadata.get("network_run_id"),
|
||||
candidate_count=int(pairing_metadata.get("candidate_count") or 0),
|
||||
selected_edge_count=int(pairing_metadata.get("selected_edge_count") or 0),
|
||||
coverage=pairing_metadata.get("coverage"),
|
||||
)
|
||||
except Exception as e:
|
||||
if isinstance(e, HTTPException):
|
||||
raise e
|
||||
if isinstance(e, (RuntimeError, ValueError)):
|
||||
raise HTTPException(status_code=409, detail=str(e))
|
||||
logger.exception("Failed to build D-InSAR coverage pairing plan")
|
||||
raise HTTPException(status_code=500, detail="Failed to build D-InSAR coverage pairing plan.")
|
||||
|
||||
|
||||
@router.post("/find-ps-timeseries", response_model=Dict[str, List[RadarData]])
|
||||
async def find_ps_timeseries_endpoint(
|
||||
params: PsRequest = Depends(get_ps_request_from_form),
|
||||
|
||||
+221
-52
@@ -11,6 +11,8 @@ from typing import Any, Dict, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SOURCE_METADATA_MANAGED_FORMATS = ("LT1_ARCHIVE", "S1_ZIP")
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import distinct, func, text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -51,7 +53,6 @@ from ..services.data_service import data_service
|
||||
from ..services.dinsar_read_service import dinsar_read_service
|
||||
from ..services.pairing_state_service import pairing_state_service
|
||||
from ..services.admin_region_lookup_service import _build_region_path, _load_region_records
|
||||
from ..utils import find_xml_file
|
||||
from . import dependencies as _deps
|
||||
from .dependencies import _get_current_user
|
||||
|
||||
@@ -80,6 +81,10 @@ def _ratio(numerator: int, denominator: int) -> float:
|
||||
return round(float(numerator) / float(denominator), 4)
|
||||
|
||||
|
||||
def _is_source_metadata_managed_format(value: Any) -> bool:
|
||||
return str(value or "").strip().upper() in SOURCE_METADATA_MANAGED_FORMATS
|
||||
|
||||
|
||||
def _family_label(value: Any) -> str:
|
||||
text = str(value or "").strip().upper()
|
||||
if text in {"LT1", "LT-1", "LUTAN", "LUTAN1"}:
|
||||
@@ -102,6 +107,14 @@ def _month_from_yyyymmdd(value: Any) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
def _compact_yyyymmdd(value: Any) -> Optional[str]:
|
||||
text = str(value or "").strip()
|
||||
digits = "".join(ch for ch in text if ch.isdigit())
|
||||
if len(digits) >= 8:
|
||||
return digits[:8]
|
||||
return None
|
||||
|
||||
|
||||
def _month_from_datetime(value: Any) -> Optional[str]:
|
||||
if not value:
|
||||
return None
|
||||
@@ -594,16 +607,36 @@ async def get_statistics_dashboard(
|
||||
db,
|
||||
select(func.count(SourceProductAssetORM.id)).where(SourceProductAssetORM.is_active == True),
|
||||
)
|
||||
metadata_source_total = await _scalar_count(
|
||||
db,
|
||||
select(func.count(SourceProductAssetORM.id)).where(
|
||||
SourceProductAssetORM.is_active == True,
|
||||
SourceProductAssetORM.source_format.in_(SOURCE_METADATA_MANAGED_FORMATS),
|
||||
),
|
||||
)
|
||||
radar_total = await _scalar_count(db, select(func.count(RadarDataORM.id)))
|
||||
metadata_asset_total = await _scalar_count(
|
||||
db,
|
||||
select(func.count(distinct(SourceMetadataDocumentORM.source_asset_id))),
|
||||
select(func.count(distinct(SourceMetadataDocumentORM.source_asset_id))).where(
|
||||
SourceMetadataDocumentORM.source_format.in_(SOURCE_METADATA_MANAGED_FORMATS),
|
||||
),
|
||||
)
|
||||
metadata_doc_total = await _scalar_count(
|
||||
db,
|
||||
select(func.count(SourceMetadataDocumentORM.id)).where(
|
||||
SourceMetadataDocumentORM.source_format.in_(SOURCE_METADATA_MANAGED_FORMATS),
|
||||
),
|
||||
)
|
||||
geometry_total = await _scalar_count(
|
||||
db,
|
||||
select(func.count(SARSceneGeometryProfileORM.id)).where(
|
||||
SARSceneGeometryProfileORM.source_format.in_(SOURCE_METADATA_MANAGED_FORMATS),
|
||||
),
|
||||
)
|
||||
metadata_doc_total = await _scalar_count(db, select(func.count(SourceMetadataDocumentORM.id)))
|
||||
geometry_total = await _scalar_count(db, select(func.count(SARSceneGeometryProfileORM.id)))
|
||||
geometry_ready = await _scalar_count(
|
||||
db,
|
||||
select(func.count(SARSceneGeometryProfileORM.id)).where(
|
||||
SARSceneGeometryProfileORM.source_format.in_(SOURCE_METADATA_MANAGED_FORMATS),
|
||||
SARSceneGeometryProfileORM.metadata_quality == "READY",
|
||||
SARSceneGeometryProfileORM.production_readiness == "READY",
|
||||
),
|
||||
@@ -633,6 +666,7 @@ async def get_statistics_dashboard(
|
||||
for family, source_format, parse_status, count in source_group_rows.all():
|
||||
family_label = _family_label(family)
|
||||
status_label = _status_label(parse_status)
|
||||
format_label = str(source_format or "UNKNOWN")
|
||||
count_int = _safe_int(count)
|
||||
family_bucket = source_by_family_map.setdefault(
|
||||
family_label,
|
||||
@@ -649,7 +683,6 @@ async def get_statistics_dashboard(
|
||||
family_bucket["ready_count"] += count_int
|
||||
else:
|
||||
family_bucket["issue_count"] += count_int
|
||||
format_label = str(source_format or "UNKNOWN")
|
||||
family_bucket["formats"][format_label] = family_bucket["formats"].get(format_label, 0) + count_int
|
||||
source_by_format.append(
|
||||
{
|
||||
@@ -673,19 +706,22 @@ async def get_statistics_dashboard(
|
||||
geometry_rows = await db.execute(
|
||||
select(
|
||||
SARSceneGeometryProfileORM.satellite_family,
|
||||
SARSceneGeometryProfileORM.source_format,
|
||||
SARSceneGeometryProfileORM.metadata_quality,
|
||||
SARSceneGeometryProfileORM.production_readiness,
|
||||
func.count(SARSceneGeometryProfileORM.id),
|
||||
)
|
||||
.where(SARSceneGeometryProfileORM.source_format.in_(SOURCE_METADATA_MANAGED_FORMATS))
|
||||
.group_by(
|
||||
SARSceneGeometryProfileORM.satellite_family,
|
||||
SARSceneGeometryProfileORM.source_format,
|
||||
SARSceneGeometryProfileORM.metadata_quality,
|
||||
SARSceneGeometryProfileORM.production_readiness,
|
||||
)
|
||||
.order_by(SARSceneGeometryProfileORM.satellite_family)
|
||||
)
|
||||
geometry_by_family_map: dict[str, dict[str, Any]] = {}
|
||||
for family, metadata_quality, production_readiness, count in geometry_rows.all():
|
||||
for family, source_format, metadata_quality, production_readiness, count in geometry_rows.all():
|
||||
family_label = _family_label(family)
|
||||
count_int = _safe_int(count)
|
||||
bucket = geometry_by_family_map.setdefault(
|
||||
@@ -1075,6 +1111,82 @@ async def get_statistics_dashboard(
|
||||
issue_by_severity[severity_label] = issue_by_severity.get(severity_label, 0) + count_int
|
||||
issue_by_code[code_label] = issue_by_code.get(code_label, 0) + count_int
|
||||
|
||||
missing_orbit_issue_rows = await db.execute(
|
||||
select(AssetInventoryIssueORM.metadata_json)
|
||||
.where(AssetInventoryIssueORM.status == "OPEN")
|
||||
.where(AssetInventoryIssueORM.issue_code == "scene_missing_orbit")
|
||||
)
|
||||
orbit_missing_satellite_map: dict[str, dict[str, Any]] = {}
|
||||
orbit_missing_top_dates: dict[tuple[str, str], int] = {}
|
||||
orbit_missing_unknown_date_scene_count = 0
|
||||
for (metadata_json,) in missing_orbit_issue_rows.all():
|
||||
metadata = metadata_json if isinstance(metadata_json, dict) else {}
|
||||
satellite = str(metadata.get("satellite") or "UNKNOWN").strip().upper() or "UNKNOWN"
|
||||
ymd = _compact_yyyymmdd(metadata.get("imaging_date") or metadata.get("acquisition_start_time_utc"))
|
||||
month = _month_from_yyyymmdd(ymd) if ymd else None
|
||||
satellite_bucket = orbit_missing_satellite_map.setdefault(
|
||||
satellite,
|
||||
{
|
||||
"satellite": satellite,
|
||||
"affected_scene_count": 0,
|
||||
"dates": defaultdict(int),
|
||||
"months": defaultdict(lambda: {"affected_scene_count": 0, "dates": set()}),
|
||||
"unknown_date_scene_count": 0,
|
||||
},
|
||||
)
|
||||
satellite_bucket["affected_scene_count"] += 1
|
||||
if not ymd:
|
||||
satellite_bucket["unknown_date_scene_count"] += 1
|
||||
orbit_missing_unknown_date_scene_count += 1
|
||||
continue
|
||||
satellite_bucket["dates"][ymd] += 1
|
||||
orbit_missing_top_dates[(satellite, ymd)] = orbit_missing_top_dates.get((satellite, ymd), 0) + 1
|
||||
if month:
|
||||
month_bucket = satellite_bucket["months"][month]
|
||||
month_bucket["affected_scene_count"] += 1
|
||||
month_bucket["dates"].add(ymd)
|
||||
|
||||
orbit_missing_by_satellite: list[dict[str, Any]] = []
|
||||
for satellite, bucket in sorted(orbit_missing_satellite_map.items()):
|
||||
date_items = [
|
||||
{"date": date_text, "affected_scene_count": _safe_int(count)}
|
||||
for date_text, count in sorted(bucket["dates"].items())
|
||||
]
|
||||
month_items = [
|
||||
{
|
||||
"month": month,
|
||||
"affected_scene_count": _safe_int(month_bucket["affected_scene_count"]),
|
||||
"missing_orbit_date_count": len(month_bucket["dates"]),
|
||||
}
|
||||
for month, month_bucket in sorted(bucket["months"].items())
|
||||
]
|
||||
orbit_missing_by_satellite.append(
|
||||
{
|
||||
"satellite": satellite,
|
||||
"affected_scene_count": _safe_int(bucket["affected_scene_count"]),
|
||||
"missing_orbit_date_count": len(date_items),
|
||||
"unknown_date_scene_count": _safe_int(bucket["unknown_date_scene_count"]),
|
||||
"first_missing_date": date_items[0]["date"] if date_items else None,
|
||||
"last_missing_date": date_items[-1]["date"] if date_items else None,
|
||||
"months": month_items,
|
||||
"dates": date_items,
|
||||
}
|
||||
)
|
||||
|
||||
orbit_missing_summary = {
|
||||
"affected_scene_count": sum(item["affected_scene_count"] for item in orbit_missing_by_satellite),
|
||||
"missing_orbit_date_count": sum(item["missing_orbit_date_count"] for item in orbit_missing_by_satellite),
|
||||
"unknown_date_scene_count": orbit_missing_unknown_date_scene_count,
|
||||
"by_satellite": orbit_missing_by_satellite,
|
||||
"top_dates": [
|
||||
{"satellite": satellite, "date": date_text, "affected_scene_count": _safe_int(count)}
|
||||
for (satellite, date_text), count in sorted(
|
||||
orbit_missing_top_dates.items(),
|
||||
key=lambda item: (-item[1], item[0][0], item[0][1]),
|
||||
)[:20]
|
||||
],
|
||||
}
|
||||
|
||||
inventory_state_rows = await db.execute(
|
||||
select(
|
||||
AssetInventoryStateORM.inventory_type,
|
||||
@@ -1114,16 +1226,24 @@ async def get_statistics_dashboard(
|
||||
|
||||
avg_duration_seconds = round(sum(duration_seconds) / len(duration_seconds), 1) if duration_seconds else None
|
||||
selected_orbit_rate = _ratio(selected_orbit_bindings, orbit_required_total)
|
||||
geometry_ready_rate = _ratio(geometry_ready, source_total)
|
||||
metadata_ready_rate = _ratio(metadata_asset_total, source_total)
|
||||
geometry_ready_rate = _ratio(geometry_ready, geometry_total)
|
||||
metadata_ready_rate = _ratio(metadata_asset_total, metadata_source_total)
|
||||
result_ready_total = sum(item["ready_count"] for item in results_by_catalog)
|
||||
metadata_missing_count = max(0, metadata_source_total - metadata_asset_total)
|
||||
geometry_not_ready_count = max(0, geometry_total - geometry_ready)
|
||||
orbit_missing_gap_count = max(0, orbit_required_total - selected_orbit_bindings)
|
||||
non_orbit_open_issue_total = sum(
|
||||
count
|
||||
for code, count in issue_by_code.items()
|
||||
if code != "scene_missing_orbit"
|
||||
)
|
||||
|
||||
risk_count = (
|
||||
max(0, source_total - metadata_asset_total)
|
||||
+ max(0, source_total - geometry_ready)
|
||||
+ max(0, orbit_required_total - selected_orbit_bindings)
|
||||
metadata_missing_count
|
||||
+ geometry_not_ready_count
|
||||
+ orbit_missing_gap_count
|
||||
+ result_assets_missing
|
||||
+ open_issue_total
|
||||
+ non_orbit_open_issue_total
|
||||
)
|
||||
|
||||
kpis = [
|
||||
@@ -1140,7 +1260,7 @@ async def get_statistics_dashboard(
|
||||
"label": "元数据入库率",
|
||||
"value": round(metadata_ready_rate * 100, 1),
|
||||
"unit": "%",
|
||||
"note": f"{metadata_asset_total}/{source_total} 景已提取 XML/元数据",
|
||||
"note": f"{metadata_asset_total}/{metadata_source_total} 景已提取 XML/元数据",
|
||||
"tone": "success" if metadata_ready_rate >= 0.98 else "warning",
|
||||
},
|
||||
{
|
||||
@@ -1148,7 +1268,7 @@ async def get_statistics_dashboard(
|
||||
"label": "几何画像可用率",
|
||||
"value": round(geometry_ready_rate * 100, 1),
|
||||
"unit": "%",
|
||||
"note": f"{geometry_ready}/{source_total} 景可用于覆盖统计",
|
||||
"note": f"{geometry_ready}/{geometry_total} 景可用于覆盖统计",
|
||||
"tone": "success" if geometry_ready_rate >= 0.95 else "warning",
|
||||
},
|
||||
{
|
||||
@@ -1172,7 +1292,7 @@ async def get_statistics_dashboard(
|
||||
"label": "待关注项",
|
||||
"value": risk_count,
|
||||
"unit": "项",
|
||||
"note": f"开放问题 {open_issue_total},缺失成果资产 {result_assets_missing}",
|
||||
"note": f"缺精轨 {orbit_missing_gap_count},开放问题 {open_issue_total},缺失成果资产 {result_assets_missing}",
|
||||
"tone": "danger" if risk_count else "success",
|
||||
},
|
||||
]
|
||||
@@ -1186,20 +1306,23 @@ async def get_statistics_dashboard(
|
||||
"source_by_family": source_by_family,
|
||||
"source_by_format": source_by_format,
|
||||
"source_by_month": source_by_month,
|
||||
"metadata_source_total": metadata_source_total,
|
||||
"metadata_asset_total": metadata_asset_total,
|
||||
"metadata_doc_total": metadata_doc_total,
|
||||
"metadata_missing_count": metadata_missing_count,
|
||||
"metadata_ready_rate": metadata_ready_rate,
|
||||
"geometry_total": geometry_total,
|
||||
"geometry_ready": geometry_ready,
|
||||
"geometry_not_ready_count": geometry_not_ready_count,
|
||||
"geometry_ready_rate": geometry_ready_rate,
|
||||
"geometry_by_family": geometry_by_family,
|
||||
"preview_ready": preview_ready,
|
||||
"pipeline": [
|
||||
{"key": "source", "label": "源资产登记", "value": source_total, "rate": 1.0},
|
||||
{"key": "metadata", "label": "元数据入库", "value": metadata_asset_total, "rate": metadata_ready_rate},
|
||||
{"key": "geometry", "label": "几何画像", "value": geometry_total, "rate": _ratio(geometry_total, source_total)},
|
||||
{"key": "ready", "label": "可生产画像", "value": geometry_ready, "rate": geometry_ready_rate},
|
||||
{"key": "preview", "label": "预览缓存", "value": preview_ready, "rate": _ratio(preview_ready, radar_total)},
|
||||
{"key": "source", "label": "源资产登记", "value": source_total, "denominator": source_total, "rate": 1.0},
|
||||
{"key": "metadata", "label": "元数据入库", "value": metadata_asset_total, "denominator": metadata_source_total, "rate": metadata_ready_rate},
|
||||
{"key": "geometry", "label": "几何画像", "value": geometry_total, "denominator": metadata_source_total, "rate": _ratio(geometry_total, metadata_source_total)},
|
||||
{"key": "ready", "label": "可生产画像", "value": geometry_ready, "denominator": geometry_total, "rate": geometry_ready_rate},
|
||||
{"key": "preview", "label": "预览缓存", "value": preview_ready, "denominator": radar_total, "rate": _ratio(preview_ready, radar_total)},
|
||||
],
|
||||
},
|
||||
"orbit": {
|
||||
@@ -1208,6 +1331,8 @@ async def get_statistics_dashboard(
|
||||
"orbit_required_total": orbit_required_total,
|
||||
"selected_bindings": selected_orbit_bindings,
|
||||
"matched_bindings": matched_orbit_bindings,
|
||||
"missing_scene_count": orbit_missing_gap_count,
|
||||
"missing_summary": orbit_missing_summary,
|
||||
"selected_rate": selected_orbit_rate,
|
||||
},
|
||||
"coverage": {
|
||||
@@ -1242,6 +1367,9 @@ async def get_statistics_dashboard(
|
||||
"issues": {
|
||||
"issue_total": issue_total,
|
||||
"open_issue_total": open_issue_total,
|
||||
"risk_count": risk_count,
|
||||
"non_orbit_open_issue_total": non_orbit_open_issue_total,
|
||||
"orbit_missing_gap_count": orbit_missing_gap_count,
|
||||
"by_severity": [
|
||||
{"severity": severity, "count": count}
|
||||
for severity, count in sorted(issue_by_severity.items(), key=lambda kv: (-kv[1], kv[0]))
|
||||
@@ -1361,12 +1489,16 @@ async def get_statistics(
|
||||
"db_ready_and_cache_exists_count": 0,
|
||||
"db_ready_but_cache_missing_count": 0,
|
||||
}
|
||||
source_xml_consistency = {
|
||||
source_metadata_consistency = {
|
||||
"total_records_count": 0,
|
||||
"xml_detected_count": 0,
|
||||
"xml_missing_count": 0,
|
||||
"xml_parsed_ok_count": 0,
|
||||
"xml_detected_but_unparsed_count": 0,
|
||||
"metadata_document_asset_count": 0,
|
||||
"metadata_document_count": 0,
|
||||
"metadata_document_missing_count": 0,
|
||||
"metadata_parsed_ok_count": 0,
|
||||
"metadata_detected_but_unparsed_count": 0,
|
||||
"geometry_ready_count": 0,
|
||||
"geometry_missing_count": 0,
|
||||
"geometry_not_ready_count": 0,
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -1399,7 +1531,6 @@ async def get_statistics(
|
||||
)
|
||||
source_rows = source_rows_res.all()
|
||||
source_preview_consistency["total_records_count"] = len(source_rows)
|
||||
source_xml_consistency["total_records_count"] = len(source_rows)
|
||||
|
||||
for (
|
||||
unique_id,
|
||||
@@ -1415,7 +1546,6 @@ async def get_statistics(
|
||||
) in source_rows:
|
||||
if not file_path:
|
||||
source_preview_consistency["preview_missing_count"] += 1
|
||||
source_xml_consistency["xml_missing_count"] += 1
|
||||
continue
|
||||
|
||||
cache_key = unique_id or file_path
|
||||
@@ -1443,34 +1573,64 @@ async def get_statistics(
|
||||
else:
|
||||
source_preview_consistency["db_ready_but_cache_missing_count"] += 1
|
||||
|
||||
scene_dir = file_path if os.path.isdir(file_path) else os.path.dirname(file_path)
|
||||
xml_path = find_xml_file(scene_dir) if scene_dir else None
|
||||
has_xml = bool(xml_path and os.path.exists(xml_path))
|
||||
if has_xml:
|
||||
source_xml_consistency["xml_detected_count"] += 1
|
||||
parsed_ok = any(
|
||||
value is not None and value != ""
|
||||
for value in [
|
||||
scene_center_lon,
|
||||
scene_center_lat,
|
||||
acquisition_time_utc,
|
||||
satellite_mode,
|
||||
receiving_station,
|
||||
product_level,
|
||||
product_unique_id,
|
||||
]
|
||||
)
|
||||
if parsed_ok:
|
||||
source_xml_consistency["xml_parsed_ok_count"] += 1
|
||||
else:
|
||||
source_xml_consistency["xml_detected_but_unparsed_count"] += 1
|
||||
else:
|
||||
source_xml_consistency["xml_missing_count"] += 1
|
||||
active_source_count = await _scalar_count(
|
||||
db,
|
||||
select(func.count(SourceProductAssetORM.id)).where(
|
||||
SourceProductAssetORM.is_active == True,
|
||||
SourceProductAssetORM.source_format.in_(SOURCE_METADATA_MANAGED_FORMATS),
|
||||
),
|
||||
)
|
||||
metadata_asset_count = await _scalar_count(
|
||||
db,
|
||||
select(func.count(distinct(SourceMetadataDocumentORM.source_asset_id))).where(
|
||||
SourceMetadataDocumentORM.source_format.in_(SOURCE_METADATA_MANAGED_FORMATS),
|
||||
),
|
||||
)
|
||||
metadata_doc_count = await _scalar_count(
|
||||
db,
|
||||
select(func.count(SourceMetadataDocumentORM.id)).where(
|
||||
SourceMetadataDocumentORM.source_format.in_(SOURCE_METADATA_MANAGED_FORMATS),
|
||||
),
|
||||
)
|
||||
metadata_parse_issue_count = await _scalar_count(
|
||||
db,
|
||||
select(func.count(SourceMetadataDocumentORM.id)).where(
|
||||
SourceMetadataDocumentORM.source_format.in_(SOURCE_METADATA_MANAGED_FORMATS),
|
||||
SourceMetadataDocumentORM.parse_status != "OK",
|
||||
),
|
||||
)
|
||||
geometry_ready_count = await _scalar_count(
|
||||
db,
|
||||
select(func.count(SARSceneGeometryProfileORM.id)).where(
|
||||
SARSceneGeometryProfileORM.source_format.in_(SOURCE_METADATA_MANAGED_FORMATS),
|
||||
SARSceneGeometryProfileORM.metadata_quality == "READY",
|
||||
SARSceneGeometryProfileORM.production_readiness == "READY",
|
||||
),
|
||||
)
|
||||
geometry_total_count = await _scalar_count(
|
||||
db,
|
||||
select(func.count(SARSceneGeometryProfileORM.id)).where(
|
||||
SARSceneGeometryProfileORM.source_format.in_(SOURCE_METADATA_MANAGED_FORMATS),
|
||||
),
|
||||
)
|
||||
source_metadata_consistency.update(
|
||||
{
|
||||
"total_records_count": active_source_count,
|
||||
"metadata_document_asset_count": metadata_asset_count,
|
||||
"metadata_document_count": metadata_doc_count,
|
||||
"metadata_document_missing_count": max(0, active_source_count - metadata_asset_count),
|
||||
"metadata_parsed_ok_count": max(0, metadata_doc_count - metadata_parse_issue_count),
|
||||
"metadata_detected_but_unparsed_count": metadata_parse_issue_count,
|
||||
"geometry_ready_count": geometry_ready_count,
|
||||
"geometry_missing_count": max(0, active_source_count - geometry_total_count),
|
||||
"geometry_not_ready_count": max(0, geometry_total_count - geometry_ready_count),
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("统计源数据时发生错误 (可能是表不存在): %s", e)
|
||||
source_preview_consistency["error"] = str(e)
|
||||
source_xml_consistency["error"] = str(e)
|
||||
source_metadata_consistency["error"] = str(e)
|
||||
|
||||
# 4. AI 质量统计
|
||||
labeled_good_count = sum(1 for record in dinsar_records if record.product.user_label == 1)
|
||||
@@ -1756,7 +1916,16 @@ async def get_statistics(
|
||||
"with_orbit_data_count": with_orbit_data_count,
|
||||
},
|
||||
"source_preview_consistency": source_preview_consistency,
|
||||
"source_xml_consistency": source_xml_consistency,
|
||||
"source_metadata_consistency": source_metadata_consistency,
|
||||
"source_xml_consistency": {
|
||||
"deprecated": True,
|
||||
"replacement": "source_metadata_consistency",
|
||||
"total_records_count": source_metadata_consistency.get("total_records_count", 0),
|
||||
"xml_detected_count": source_metadata_consistency.get("metadata_document_asset_count", 0),
|
||||
"xml_missing_count": 0,
|
||||
"xml_parsed_ok_count": source_metadata_consistency.get("metadata_parsed_ok_count", 0),
|
||||
"xml_detected_but_unparsed_count": 0,
|
||||
},
|
||||
"water_geo_consistency": water_geo_consistency,
|
||||
"pairing_consistency": pairing_consistency,
|
||||
"by_satellite": by_satellite,
|
||||
|
||||
Reference in New Issue
Block a user