6767# -----------------------------------------------------------------------------
6868
6969# Header mínimo para o GSI
70- REQ_HDR_GSI : Tuple [str , ...] = ("XOB" , "YOB" , "DHR" , "TYP" )
70+ # GSI REAL exige apenas posição e tempo
71+ REQ_HDR_GSI : Tuple [str , ...] = ("XOB" , "YOB" , "DHR" )
7172
7273# Sequências Table D para eventos (diagnóstico)
7374REQ_SEQS : Tuple [str , ...] = ("TEVN" , "WEVN" , "PEVN" , "ZEVN" , "QEVN" )
7879# Progresso
7980DEFAULT_PROGRESS_EVERY : int = 10_000
8081
81- # Faixas de plausibilidade (diagnóstico)
82+ # Faixas de plausibilidade baseadas estritamente na tabela PREPBUFR
83+
8284RANGE = {
83- "XOB" : (- 180.0 , 180.0 ),
84- "YOB" : (- 90.0 , 90.0 ),
85- "TOB" : (180.0 , 340.0 ),
86- "POB" : (1.0 , 1100.0 ),
87- "ZOB" : (- 500.0 , 40000.0 ),
88- "UOB" : (- 120.0 , 120.0 ),
89- "VOB" : (- 120.0 , 120.0 ),
90- "QOB" : (0.0 , 0.05 ),
91- "PRSS" : (500.0 , 1085.0 ),
92- "PWO" : (0.0 , 100.0 ),
93- "DHR" : (- 6.0 , 6.0 ),
94- "HOVI" : (0.0 , 100000.0 ),
95- "TDO" : (173.0 , 333.0 ),
96- "MSST" : (271.0 , 313.0 ),
85+ # Geolocalização
86+ "XOB" : (- 180.0 , 180.0 ), # DEG E
87+ "YOB" : (- 90.0 , 90.0 ), # DEG N
88+
89+ # Tempo
90+ "DHR" : (- 24.0 , 24.0 ), # HOURS (tabela permite até ±24h)
91+
92+ # Temperatura (DEG C)
93+ "TOB" : (- 90.0 , 60.0 ), # Atmosfera (°C)
94+ "TDO" : (- 100.0 , 40.0 ), # Ponto de orvalho (°C)
95+
96+ # Temperatura de superfície (K)
97+ "TMSK" : (200.0 , 350.0 ), # K (compatível com tabela)
98+
99+ # Pressão
100+ "POB" : (1.0 , 1100.0 ), # mb
101+ "PRSS" : (50000.0 , 108500.0 ), # Pa (ATENÇÃO: Pascals!)
102+
103+ # Altura
104+ "ZOB" : (- 500.0 , 40000.0 ), # m (ref = -1000 m)
105+
106+ # Vento
107+ "UOB" : (- 150.0 , 150.0 ), # m/s
108+ "VOB" : (- 150.0 , 150.0 ), # m/s
109+
110+ # Umidade específica
111+ "QOB" : (0.0 , 30000.0 ), # mg/kg (0–30 g/kg)
112+
113+ # Água precipitável
114+ "PWO" : (0.0 , 120.0 ), # mm (valores extremos tropicais)
97115}
98116
117+
99118# Unidade de pressão (para interpretar POB/PRSS) e autodetecção
100119PRESSURE_UNITS = ("hpa" , "cb" , "pa" , "auto" )
101120DEFAULT_PRESSURE_UNIT = "hpa"
107126)
108127
109128# Limites estruturais para lon/lat (mínimo GSI)
129+ # GSI aceita lon 0–360 e normaliza internamente
110130LON_RANGE_STRUCT = (- 180.0 , 360.0 )
111131LAT_RANGE_STRUCT = (- 90.0 , 90.0 )
112132
133+ # Variáveis assimiláveis por tipo de mensagem (Tabela A / comportamento GSI)
134+ USEFUL_BY_TYPE = {
135+ "ADPUPA" : ("TOB" , "QOB" , "UOB" , "VOB" , "ZOB" ),
136+ "AIRCAR" : ("TOB" , "UOB" , "VOB" ),
137+ "AIRCFT" : ("TOB" , "UOB" , "VOB" ),
138+ "SATWND" : ("UOB" , "VOB" ),
139+ "PROFLR" : ("UOB" , "VOB" ),
140+ "VADWND" : ("UOB" , "VOB" ),
141+ "ADPSFC" : ("PRSS" , "TOB" , "QOB" , "UOB" , "VOB" ),
142+ "SFCSHP" : ("PRSS" , "TOB" , "QOB" , "UOB" , "VOB" ),
143+ # BUOY: GSI aceita só vento, só pressão ou só temperatura
144+ "SFCSHP" : ("PRSS" , "TOB" , "QOB" , "UOB" , "VOB" , "POB" ),
145+ "SATEMP" : ("TMBR" ,),
146+ "GOESND" : ("TMBR" ,),
147+ "SPSSMI" : ("PWO" , "UOB" , "VOB" ),
148+ "QKSWND" : ("UOB" , "VOB" ),
149+ }
150+
151+
113152# Missing típico da BUFRLIB
114153try :
115154 BUFR_MISSING = float (ncepbufr .bufrlib .getbmiss ())
@@ -299,7 +338,7 @@ def _autodetect_pressure_unit(path: str, max_samples: int = PRESSURE_AUTODETECT_
299338 cnt = 0
300339 while b .advance () == 0 and cnt < max_samples :
301340 while b .load_subset () == 0 and cnt < max_samples :
302- for nm in ("POB" , "PRSS" ):
341+ for nm in ("POB" ,):
303342 arr = b .read_subset (nm )
304343 if getattr (arr , "size" , 0 ):
305344 try :
@@ -477,8 +516,8 @@ def inc(d: Dict[object, int], k: object, by: int = 1):
477516 where_gsi_lonlat .append (_fmt_where (mtyp , nsub , xob , yob , dhr ))
478517 fail_records .append (FailRecord (_b2s (mtyp ), nsub , FAIL_GSI_LONLAT , None ))
479518
480- # Pelo menos uma variável "útil" (TOB/QOB/U/V/POB/ZOB/PRSS )
481- useful_mnems = ( "TOB" , "QOB" , "UOB" , "VOB" , "POB" , "ZOB" , "PRSS" )
519+ # Pelo menos uma variável "útil" (dirigido por tipo )
520+ useful_mnems = USEFUL_BY_TYPE . get ( _b2s ( mtyp ), () )
482521 has_useful = False
483522 for nm in useful_mnems :
484523 arr = _read1 (read , nm )
@@ -508,6 +547,7 @@ def inc(d: Dict[object, int], k: object, by: int = 1):
508547 # -------------------------------
509548
510549 # 2.1. Eventos
550+ # NOTA: Eventos EVN são opcionais no GSI real → ignorados
511551 seq_ok = False
512552 subset_no_events = False
513553
@@ -583,6 +623,7 @@ def check_range(name: str) -> bool:
583623 subset_miss_oberrs = False
584624 if check_oberrs :
585625 pairs = [
626+ # *_OE são opcionais no GSI (erro default)
586627 ("TOB" , "TOE" ),
587628 (("UOB" , "VOB" ), "WOE" ),
588629 ("POB" , "POE" ),
@@ -604,12 +645,12 @@ def check_range(name: str) -> bool:
604645
605646 # 2.5. ADPSFC (diagnóstico, se --kind adpsfc)
606647 subset_miss_adpsfc = False
607- if kind == "adpsfc" :
648+ # Diagnóstico ADPSFC só se msg_type == ADPSFC
649+ if kind == "adpsfc" and _b2s (mtyp ) == "ADPSFC" :
608650 if not subset_gsi_hdr_bad : # só olha ADPSFC se header básico ok
609651 prss = _read1 (read , "PRSS" )
610652 pwo = _read1 (read , "PWO" )
611- cat = _read1 (read , "CAT" )
612- if (prss is None ) or (pwo is None ) or (cat is None ):
653+ if (prss is None ) or (pwo is None ):
613654 subset_miss_adpsfc = True
614655 else :
615656 try :
@@ -637,54 +678,174 @@ def check_range(name: str) -> bool:
637678 if pbar is not None :
638679 pbar .close ()
639680
681+ # -------------------------------------------------------------------------
640682 # Status estrutural (mínimo GSI)
683+ #
684+ # Objetivo:
685+ # Determinar se o arquivo PREPBUFR é ESTRUTURALMENTE UTILIZÁVEL pelo GSI,
686+ # isto é, se o GSI consegue LER, INTERPRETAR e TENTAR ASSIMILAR os dados.
687+ #
688+ # Critérios adotados (estritamente alinhados ao GSI real):
689+ #
690+ # - gsi_bad_hdr:
691+ # Subsets sem header essencial (XOB, YOB, DHR, TYP).
692+ # -> Sem essas chaves, o GSI NÃO consegue sequer posicionar a observação.
693+ #
694+ # - gsi_bad_lonlat:
695+ # Longitude ou latitude claramente inválidas.
696+ # -> O GSI rejeita observações fora de limites geográficos básicos.
697+ #
698+ # - gsi_no_useful:
699+ # Subsets sem NENHUMA variável potencialmente assimilável
700+ # (TOB, QOB, UOB, VOB, POB, ZOB ou PRSS).
701+ # -> Observação sem conteúdo útil não entra no sistema.
702+ #
703+ # Regra:
704+ # - status_struct = "OK"
705+ # TODOS os critérios acima são satisfeitos para TODOS os subsets.
706+ #
707+ # - status_struct = "PROBLEMAS_ESTRUTURAIS"
708+ # Pelo menos um subset falhou em algum critério estrutural.
709+ #
710+ # Observação importante:
711+ # Este status define o código de saída do programa:
712+ # - 0 → OK (arquivo utilizável pelo GSI)
713+ # - 1 → PROBLEMAS_ESTRUTURAIS (arquivo inadequado para o GSI)
714+ # -------------------------------------------------------------------------
641715 if gsi_bad_hdr == 0 and gsi_bad_lonlat == 0 and gsi_no_useful == 0 :
642716 status_struct = "OK"
643717 else :
644718 status_struct = "PROBLEMAS_ESTRUTURAIS"
645719
646- # Status diagnóstico: ATENÇÃO se houver qualquer contador > 0
720+ # -------------------------------------------------------------------------
721+ # Status diagnóstico
722+ #
723+ # Objetivo:
724+ # Elevar status_diag para "ATENÇÃO" apenas quando houver condições
725+ # que possam causar REJEIÇÃO da observação ou IMPACTO OPERACIONAL REAL
726+ # no GSI.
727+ #
728+ # Princípios adotados (alinhados ao comportamento do GSI):
729+ #
730+ # - diag_no_events:
731+ # -> EVN ausente (TEVN/WEVN/PEVN/ZEVN/QEVN)
732+ # -> NÃO eleva status.
733+ # -> O GSI não exige eventos para leitura ou assimilação.
734+ #
735+ # - diag_miss_oberrs:
736+ # -> Erros de observação ausentes (*_OE)
737+ # -> NÃO eleva status.
738+ # -> O GSI utiliza erros default definidos em convinfo.
739+ #
740+ # - diag_time_oow:
741+ # -> DHR fora da janela temporal
742+ # -> ELEVA status (ATENÇÃO).
743+ # -> Observações fora da janela podem ser descartadas pelo GSI.
744+ #
745+ # - diag_bad_units:
746+ # -> Valores ou unidades fisicamente implausíveis
747+ # -> ELEVA status (ATENÇÃO).
748+ # -> Podem causar rejeição posterior ou contaminar a análise.
749+ #
750+ # - diag_miss_adpsfc:
751+ # -> Inconsistência em campos críticos de superfície (PRSS/PWO/CAT)
752+ # -> ELEVA status (ATENÇÃO).
753+ # -> PRSS é essencial para observações de superfície no GSI.
754+ #
755+ # Resultado:
756+ # - status_diag = "OK"
757+ # Nenhuma condição com impacto real detectada (diagnóstico informativo).
758+ # - status_diag = "ATENÇÃO"
759+ # Existe pelo menos uma condição com potencial impacto na assimilação.
760+ # -------------------------------------------------------------------------
647761 if any ([
648- diag_no_events ,
649- diag_time_oow ,
650- diag_bad_units ,
651- diag_miss_oberrs ,
652- diag_miss_adpsfc ,
762+ diag_time_oow , # DHR fora da janela temporal
763+ diag_bad_units , # valores/unidades fisicamente implausíveis
764+ diag_miss_adpsfc , # campos críticos ausentes para ADPSFC
653765 ]):
654766 status_diag = "ATENÇÃO"
655767 else :
656768 status_diag = "OK"
657769
770+ # -------------------------------------------------------------------------
771+ # Consolidação do resumo final da validação
772+ #
773+ # O objeto Summary concentra:
774+ # - métricas estruturais mínimas (relevantes ao GSI)
775+ # - métricas diagnósticas (informativas e/ou de ATENÇÃO)
776+ # - status final (estrutural e diagnóstico)
777+ # - amostras "onde" ocorreram os problemas
778+ # - registros detalhados de falhas (para CSV detalhado)
779+ # -------------------------------------------------------------------------
658780 summary = Summary (
659- nmsg = nmsg ,
660- nsub = nsub ,
661- gsi_bad_hdr = gsi_bad_hdr ,
662- gsi_bad_lonlat = gsi_bad_lonlat ,
663- gsi_no_useful = gsi_no_useful ,
664- per_type_total = per_type_total ,
665- per_type_gsi_bad_hdr = per_type_gsi_bad_hdr ,
666- per_type_gsi_bad_lonlat = per_type_gsi_bad_lonlat ,
667- per_type_gsi_no_useful = per_type_gsi_no_useful ,
668- per_type_gsi_approved = per_type_gsi_approved ,
669- diag_no_events = diag_no_events ,
670- diag_time_oow = diag_time_oow ,
671- diag_bad_units = diag_bad_units ,
672- diag_miss_oberrs = diag_miss_oberrs ,
673- diag_miss_adpsfc = diag_miss_adpsfc ,
781+ # ---------------------------------------------------------------------
782+ # Totais globais
783+ # ---------------------------------------------------------------------
784+ nmsg = nmsg , # número total de mensagens BUFR no arquivo
785+ nsub = nsub , # número total de subsets processados
786+
787+ # ---------------------------------------------------------------------
788+ # Métricas estruturais (mínimo GSI)
789+ # Estas métricas indicam se o GSI CONSEGUE ler e usar o PREPBUFR.
790+ # Qualquer uma delas diferente de zero implica status_struct != OK.
791+ # ---------------------------------------------------------------------
792+ gsi_bad_hdr = gsi_bad_hdr , # subsets sem header essencial (XOB/YOB/DHR/TYP)
793+ gsi_bad_lonlat = gsi_bad_lonlat , # lon/lat claramente inválidos (checagem grosseira)
794+ gsi_no_useful = gsi_no_useful , # subsets sem NENHUMA variável assimilável
795+
796+ # ---------------------------------------------------------------------
797+ # Contadores por tipo de mensagem (msg_type)
798+ # Permitem avaliar qualidade e cobertura por tipo (ADPSFC, SFCSHP, etc.)
799+ # ---------------------------------------------------------------------
800+ per_type_total = per_type_total , # total de subsets por msg_type
801+ per_type_gsi_bad_hdr = per_type_gsi_bad_hdr , # header inválido por msg_type
802+ per_type_gsi_bad_lonlat = per_type_gsi_bad_lonlat , # lon/lat inválido por msg_type
803+ per_type_gsi_no_useful = per_type_gsi_no_useful , # sem variáveis úteis por msg_type
804+ per_type_gsi_approved = per_type_gsi_approved , # subsets estruturalmente OK (GSI)
805+
806+ # ---------------------------------------------------------------------
807+ # Métricas diagnósticas (não impedem leitura pelo GSI)
808+ # Servem para QA/QC, debug e avaliação da cadeia de produção.
809+ # ---------------------------------------------------------------------
810+ diag_no_events = diag_no_events , # EVN ausente (TEVN/WEVN/PEVN/ZEVN/QEVN)
811+ diag_time_oow = diag_time_oow , # DHR fora da janela temporal
812+ diag_bad_units = diag_bad_units , # valores/unidades fisicamente implausíveis
813+ diag_miss_oberrs = diag_miss_oberrs , # erros de observação (*_OE) ausentes
814+ diag_miss_adpsfc = diag_miss_adpsfc , # inconsistência em PRSS/PWO/CAT (superfície)
815+
816+ # ---------------------------------------------------------------------
817+ # Métricas diagnósticas por tipo de mensagem
818+ # Úteis para identificar problemas sistemáticos por origem/tipo.
819+ # ---------------------------------------------------------------------
674820 per_type_no_events = per_type_no_events ,
675821 per_type_time_oow = per_type_time_oow ,
676822 per_type_bad_units = per_type_bad_units ,
677823 per_type_miss_oberrs = per_type_miss_oberrs ,
678824 per_type_miss_adpsfc = per_type_miss_adpsfc ,
679- status_struct = status_struct ,
680- status_diag = status_diag ,
681- pressure_unit_used = pressure_unit_used ,
682- where_gsi_hdr = where_gsi_hdr ,
683- where_gsi_lonlat = where_gsi_lonlat ,
684- where_gsi_no_useful = where_gsi_no_useful ,
685- where_events = where_events ,
686- where_time = where_time ,
687- where_units = where_units ,
825+
826+ # ---------------------------------------------------------------------
827+ # Status finais
828+ # ---------------------------------------------------------------------
829+ status_struct = status_struct , # OK / PROBLEMAS_ESTRUTURAIS
830+ status_diag = status_diag , # OK / ATENÇÃO (conforme regras definidas)
831+ pressure_unit_used = pressure_unit_used , # unidade de pressão adotada (hpa/cb/pa)
832+
833+ # ---------------------------------------------------------------------
834+ # Exemplos ("onde") para facilitar inspeção manual
835+ # Limitados por --where
836+ # ---------------------------------------------------------------------
837+ where_gsi_hdr = where_gsi_hdr , # exemplos de header estrutural inválido
838+ where_gsi_lonlat = where_gsi_lonlat , # exemplos de lon/lat inválidos
839+ where_gsi_no_useful = where_gsi_no_useful , # exemplos sem variáveis úteis
840+ where_events = where_events , # exemplos sem EVN
841+ where_time = where_time , # exemplos fora da janela temporal
842+ where_units = where_units , # exemplos com unidades implausíveis
843+
844+ # ---------------------------------------------------------------------
845+ # Registro detalhado de falhas
846+ # Cada entrada representa uma falha específica em um subset.
847+ # Base para --report-csv
848+ # ---------------------------------------------------------------------
688849 fail_records = fail_records ,
689850 )
690851
0 commit comments