rda-python-icoads 1.0.1__py3-none-any.whl

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 rda-python-icoads might be problematic. Click here for more details.

@@ -0,0 +1,1785 @@
1
+ #
2
+ ###############################################################################
3
+ #
4
+ # Title : PgIMMA.py
5
+ # Author : Zaihua Ji, zji@car.edu
6
+ # Date : 09/13/2020
7
+ # 2025-02-18 transferred to package rda_python_icoads from
8
+ # https://github.com/NCAR/rda-shared-libraries.git
9
+ # Purpose : python library module defining IMMA data
10
+ #
11
+ # Github : https://github.com/NCAR/rda-python-icoads.git
12
+ #
13
+ ###############################################################################
14
+ #
15
+ import os
16
+ import re
17
+ import math
18
+ import numpy
19
+ from rda_python_common import PgLOG
20
+ from rda_python_common import PgUtil
21
+ from rda_python_common import PgDBI
22
+
23
+ # variable definition for each section
24
+ # PREC: 0 - string, 1 - integer, otherwise - float
25
+ # TYPE: 0 - data element
26
+ # 1 - time/location/identity element
27
+ # 2 - IMMA format element
28
+ # 3 - indicator element
29
+ # 4 - QC element(s)
30
+ ICORELOC = { #total length = 45
31
+ # IDX SIZE PREC POS TYPE UNIT Description
32
+ 'yr' : [0, 4, 1, 0, 1], #Year
33
+ 'mo' : [1, 2, 1, 4, 1], #Month
34
+ 'dy' : [2, 2, 1, 6, 1], #Day
35
+ 'hr' : [3, 4, 0.01, 8, 1], #Hour
36
+ 'lat' : [4, 5, 0.01, 12, 1, "degN"], #Latitude
37
+ 'lon' : [5, 6, 0.01, 17, 1, "degE"], #Longitude
38
+ 'im' : [6, 2, 1, 23, 2], #IMMA Version
39
+ 'attc' : [7, 1, 0, 25, 2], #Attm Count
40
+ 'ti' : [8, 1, 1, 26, 3], #Time Indicator
41
+ 'li' : [9, 1, 1, 27, 3], #Latitude/Longitude Indicator
42
+ 'ds' : [10, 1, 1, 28, 0], #Ship Course
43
+ 'vs' : [11, 1, 1, 29, 0], #Ship Speed
44
+ 'nid' : [12, 2, 1, 30, 1], #National Source Indicator
45
+ 'ii' : [13, 2, 1, 32, 3], #ID Indicator
46
+ 'id' : [14, 9, 0, 34, 1], #Identification/Call sign
47
+ 'c1' : [15, 2, 0, 43, 1] #Country Code
48
+ }
49
+
50
+ ICOREREG = { #total length = 63
51
+ # IDX SIZE PREC POS TYPE UNIT MAX Description
52
+ 'di' : [0, 1, 1, 0, 3], #Wind Direction Indicator
53
+ 'd' : [1, 3, 1, 1, 0, "deg"], #Wind Direction
54
+ 'wi' : [2, 1, 1, 4, 3], #Wind Speed Indicator
55
+ 'w' : [3, 3, 0.1, 5, 0, "m/s"], #Wind Speed
56
+ 'vi' : [4, 1, 1, 8, 3], #VV Indicator
57
+ 'vv' : [5, 2, 1, 9, 0], #Visibility
58
+ 'ww' : [6, 2, 1, 11, 0], #Present Weather
59
+ 'w1' : [7, 1, 1, 13, 0], #Past Weather
60
+ 'slp' : [8, 5, 0.1, 14, 0, "hPa"], #Sea Level Pressure
61
+ 'a' : [9, 1, 1, 19, 3], #Characteristic of PPP
62
+ 'ppp' : [10, 3, 0.1, 20, 0, "hPa"], #Amount of Pressure Tendency
63
+ 'it' : [11, 1, 1, 23, 3], #Temperature Indicator
64
+ 'at' : [12, 4, 0.1, 24, 0, "degC"], #Air Temperature
65
+ 'wbti' : [13, 1, 1, 28, 3], #WBT Indicator
66
+ 'wbt' : [14, 4, 0.1, 29, 0, "degC"], #Wet-Bulb Temperature
67
+ 'dpti' : [15, 1, 1, 33, 3], #DPT Indicator
68
+ 'dpt' : [16, 4, 0.1, 34, 0, "degC"], #Dew-Point Tmperature
69
+ 'si' : [17, 2, 1, 38, 3], #SST Measure Method
70
+ 'sst' : [18, 4, 0.1, 40, 0, "degC"], #Sea Surface Temperature
71
+ 'n' : [19, 1, 1, 44, 0], #Total Cloud Amount
72
+ 'nh' : [20, 1, 1, 45, 0], #Lower Cloud Amount
73
+ 'cl' : [21, 1, 0, 46, 3], #Low Cloud Type
74
+ 'hi' : [22, 1, 1, 47, 3], #H Indicator
75
+ 'h' : [23, 1, 0, 48, 0], #Cloud Height
76
+ 'cm' : [24, 1, 0, 49, 3], #Middle Cloud Type
77
+ 'ch' : [25, 1, 0, 50, 3], #High Cloud type
78
+ 'wd' : [26, 2, 10, 51, 0], #Wave Direction
79
+ 'wp' : [27, 2, 1, 53, 0, "s"], #Wave Period
80
+ 'wh' : [28, 2, 0.5, 55, 0, "m", 99], #Wave Height
81
+ 'sd' : [29, 2, 10, 57, 0], #Swell Direction
82
+ 'sp' : [30, 2, 1, 59, 0, "s"], #Swell Period
83
+ 'sh' : [31, 2, 0.5, 61, 0, "m", 99] #Swell Height
84
+ }
85
+
86
+ IICOADS = { # atti = ' 1', attl = 65
87
+ # IDX SIZE PREC POS TYPE Description
88
+ 'bsi' : [0, 1, 1, 4, 3], #Box System Indicator
89
+ 'b10' : [1, 3, 1, 5, 1], #10 Deg Box Number
90
+ 'b1' : [2, 2, 1, 8, 1], #1 Deg Box Number
91
+ 'dck' : [3, 3, 1, 10, 1], #Deck
92
+ 'sid' : [4, 3, 1, 13, 1], #Source ID
93
+ 'pt' : [5, 2, 1, 16, 1], #Platform Type
94
+ 'dups' : [6, 2, 1, 18, 3], #Dup Status
95
+ 'dupc' : [7, 1, 1, 20, 3], #Dup Check
96
+ 'tc' : [8, 1, 1, 21, 3], #Track Check
97
+ 'pb' : [9, 1, 1, 22, 3], #Pressure Bias
98
+ 'wx' : [10, 1, 1, 23, 3], #Wave Period Indicator
99
+ 'sx' : [11, 1, 1, 24, 3], #Swell Period Indicator
100
+ 'c2' : [12, 2, 1, 25, 1], #2nd Country Code
101
+ 'aqcs' : [13, 12, 0, 27, 4], #Adaptive QC Flags
102
+ 'nd' : [14, 1, 1, 39, 1], #Night/Day Flag
103
+ 'trms' : [15, 6, 0, 40, 4], #Trimming Flags (sf,af,uf,vf,pf,rf)
104
+ 'nqcs' : [16, 14, 0, 46, 4], #NCDC-QC Flags (znc,wnc,bnc,xnc,ync,pnc,anc,gnc,dnc,snc,cnc,enc,fnc,tnc)
105
+ 'qce' : [17, 2, 1, 60, 4], #External Flag
106
+ 'lz' : [18, 1, 1, 62, 4], #Landlocked Flag
107
+ 'qcz' : [19, 2, 1, 63, 4] #Source Exclusion Flags
108
+ }
109
+
110
+ IIMMT5 = { # atti = ' 5', attl = 94
111
+ # IDX SIZE PREC POS TYPE UNIT MAX Description
112
+ 'os' : [0, 1, 1, 4, 1], #Obervation Source
113
+ 'op' : [1, 1, 1, 5, 1], #Observation Platform
114
+ 'fm' : [2, 1, 0, 6, 1], #FM Code Version
115
+ 'immv' : [3, 1, 0, 7, 1], #IMMT Version
116
+ 'ix' : [4, 1, 1, 8, 3], #Station/Weather Indicator
117
+ 'w2' : [5, 1, 1, 9, 0], #2nd Past Weather
118
+ 'wmi' : [6, 1, 1, 10, 3], #Wave Meassure Indicator
119
+ 'sd2' : [7, 2, 10, 11, 0, "deg"], #2nd Swell direction
120
+ 'sp2' : [8, 2, 1, 13, 0, "s"], #2nd Swell Period
121
+ 'sh2' : [9, 2, 0.5, 15, 0, "m", 99], #2nd Swell Height
122
+ 'ics' : [10, 1, 1, 17, 0], #Ice Accretion on Ship
123
+ 'es' : [11, 2, 1, 18, 0, "cm"], #Thickness of IS
124
+ 'rs' : [12, 1, 1, 20, 0], #Rate of IS
125
+ 'ic1' : [13, 1, 0, 21, 0], #Concentration of Sea Ice
126
+ 'ic2' : [14, 1, 0, 22, 3], #Stage of Development
127
+ 'ic3' : [15, 1, 0, 23, 1], #Ice of Land Origin
128
+ 'ic4' : [16, 1, 0, 24, 0], #True Bearing Ice Edge
129
+ 'ic5' : [17, 1, 0, 25, 0], #Ice situation/Trend
130
+ 'ir' : [18, 1, 1, 26, 3], #Precipitation Data Indicator
131
+ 'rrr' : [19, 3, 1, 27, 0, "mm"], #Amount of Precipitation
132
+ 'tr' : [20, 1, 1, 30, 3], #Duration of RRR
133
+ 'nu' : [21, 1, 0, 31, 1], #National Use
134
+ 'qci' : [22, 1, 1, 32, 3], #QC Indicator
135
+ 'qis' : [23, 20, 0, 33, 4], #QC Indicator for Fields (qi1-qi20)
136
+ 'qi21' : [24, 1, 1, 53, 2], #MQCS Version
137
+ 'hdg' : [25, 3, 1, 54, 0, "deg"], #Ship Heading
138
+ 'cog' : [26, 3, 1, 57, 0, "deg"], #Cource Over Ground
139
+ 'sog' : [27, 2, 1, 60, 0, "kt"], #Speed Over Ground
140
+ 'sll' : [28, 2, 1, 62, 0, "m"], #max.ht>Sum Land Ln
141
+ 'slhh' : [29, 3, 1, 64, 0, "m"], #Dep. Load Ln.: Sea Lev.
142
+ 'rwd' : [30, 3, 1, 67, 0, "deg"], #Relative Wind Direction
143
+ 'rws' : [31, 3, 0.1, 70, 0, "m/s"], #Relative wind speed
144
+ 'qi22' : [32, 8, 0, 73, 3], #QC Indicator for Fields (qi22-q129)
145
+ 'rh' : [33, 4, 0.1, 81, 0, "%"], #Relative Humidity
146
+ 'rhi' : [34, 1, 1, 85, 3], #Relative Humidity Indicator
147
+ 'awsi' : [35, 1, 1, 86, 3], #AWS Indicator
148
+ 'imono' : [36, 7, 1, 87, 1], #IMO Number
149
+ }
150
+
151
+ IMODQC = { # atti = ' 6', attl 68
152
+ # IDX SIZE PREC POS TYPE UNIT Description
153
+ 'cccc' : [0, 4, 0, 4, 1], #Collecting Center
154
+ 'buid' : [1, 6, 0, 8, 1], #Bulletin ID
155
+ 'fbsrc' : [2, 1, 1, 14, 1], #feedback source
156
+ 'bmp' : [3, 5, 0.1, 15, 0, "hPa"], #Background SLP
157
+ 'bswu' : [4, 4, 0.1, 20, 0, "m/s"], #Background Wind U Comp.
158
+ 'swu' : [5, 4, 0.1, 24, 0, "m/s"], #Derived Wind U Comp.
159
+ 'bswv' : [6, 4, 0.1, 28, 0, "m/s"], #Background Wind V Comp.
160
+ 'swv' : [7, 4, 0.1, 32, 0, "m/s"], #Derived Wind V Comp.
161
+ 'bsat' : [8, 4, 0.1, 36, 0, "degC"], #Background Air Temperature
162
+ 'bsrh' : [9, 3, 1, 40, 0, "%"], #Background Relative Humidity
163
+ 'srh' : [10, 3, 1, 43, 0, "%"], #Derived Relative Humidity
164
+ 'bsst' : [11, 5, .01, 46, 0, "degC"], #Background SST
165
+ 'mst' : [12, 1, 1, 51, 1], #Model Surface Type
166
+ 'msh' : [13, 4, 1, 52, 0, "m"], #Model Height of Land Surface
167
+ 'byr' : [14, 4, 1, 56, 1], #Background Year
168
+ 'bmo' : [15, 2, 1, 60, 1], #Background Month
169
+ 'bdy' : [16, 2, 1, 62, 1], #Background Day
170
+ 'bhr' : [17, 2, 1, 64, 1], #Background Hour
171
+ 'bfl' : [18, 2, 1, 66, 1, "min"] #Background Forecast Length
172
+ }
173
+
174
+ IMETAVOS = { # atti = ' 7', attl = 58
175
+ # IDX SIZE PREC POS TYPE UNIT Description
176
+ 'mds' : [0, 1, 0, 4, 1], #metadata source
177
+ 'c1m' : [1, 2, 0, 5, 1], #Recruiting Country
178
+ 'opm' : [2, 2, 1, 7, 1], #Type of Ship
179
+ 'kov' : [3, 2, 0, 9, 1], #Kind of Vessel
180
+ 'cor' : [4, 2, 0, 11, 1], #Country of Registry
181
+ 'tob' : [5, 3, 0, 13, 1], #Type of Barometer
182
+ 'tot' : [6, 3, 0, 16, 1], #Type of Thermometer
183
+ 'eot' : [7, 2, 0, 19, 1], #Exposure of Thermometer
184
+ 'lot' : [8, 2, 0, 21, 1], #Screen Location
185
+ 'toh' : [9, 1, 0, 23, 1], #Type of Hygrometer
186
+ 'eoh' : [10, 2, 0, 24, 1], #Exposure of Hygrometer
187
+ 'sim' : [11, 3, 0, 26, 1], #SST Measurement Method
188
+ 'lov' : [12, 3, 1, 29, 0, "m"], #Length of Vessel
189
+ 'dos' : [13, 2, 1, 32, 0, "m"], #Depth of SST Measusrement
190
+ 'hop' : [14, 3, 1, 34, 0, "m"], #Height of Visual Obs. Platform
191
+ 'hot' : [15, 3, 1, 37, 0, "m"], #Height of AT Sensor
192
+ 'hob' : [16, 3, 1, 40, 0, "m"], #Height of Barometer
193
+ 'hoa' : [17, 3, 1, 43, 0, "m"], #Height of Anemometer
194
+ 'smf' : [18, 5, 1, 46, 1], #Source Metadata File
195
+ 'sme' : [19, 5, 1, 51, 1], #Source Meta. Element
196
+ 'smv' : [20, 2, 1, 56, 1] #Source Format Version
197
+ }
198
+
199
+ INOCN = { # atti = ' 8', attl = 102
200
+ # IDX SIZE PREC POS TYPE UNIT Description
201
+ 'otv' : [0, 5, 0.001, 4, 0, "degC"], #temperature value
202
+ 'otz' : [1, 4, 0.01, 9, 0, "m"], #temperature depth
203
+ 'osv' : [2, 5, 0.001, 13, 0], #salinity value
204
+ 'osz' : [3, 4, 0.01, 18, 0, "m"], #salinity depth
205
+ 'oov' : [4, 4, 0.01, 22, 0, "ml/l"], #dissolved oxygen
206
+ 'ooz' : [5, 4, 0.01, 26, 0, "m"], #dissolved oxygen depth
207
+ 'opv' : [6, 4, 0.01, 30, 0, "mm/l"], #phosphate value
208
+ 'opz' : [7, 4, 0.01, 34, 0, "m"], #phosphate depth
209
+ 'osiv' : [8, 5, 0.01, 38, 0, "mm/l"], #silicate value
210
+ 'osiz' : [9, 4, 0.01, 43, 0, "m"], #silicate depth
211
+ 'onv' : [10, 5, 0.01, 47, 0, "mm/l"], #nitrate value
212
+ 'onz' : [11, 4, 0.01, 52, 0, "m"], #nitrate depth
213
+ 'ophv' : [12, 3, 0.01, 56, 0], #pH value
214
+ 'ophz' : [13, 4, 0.01, 59, 0, "m"], #pH depth
215
+ 'ocv' : [14, 4, 0.01, 63, 0, "mg/l"], #total chlorophyll value
216
+ 'ocz' : [15, 4, 0.01, 67, 0, "m"], #total chlorophyll depth
217
+ 'oav' : [16, 3, 0.01, 71, 0, "me/l"], #alkalinity value
218
+ 'oaz' : [17, 4, 0.01, 74, 0, "m"], #alkalinity depth
219
+ 'opcv' : [18, 4, 1, 78, 0, "ma"], #partial pressure of carbon dioxide value
220
+ 'opcz' : [19, 4, 0.01, 82, 0, "m"], #partial pressure of carbon dioxide depth
221
+ 'odv' : [20, 2, 1, 86, 0, "mm/l"], #dissolved inorganic carbon value
222
+ 'odz' : [21, 4, 0.01, 88, 0, "m"], #dissolved inorganic carbon depth
223
+ 'puid' : [22, 10, 0, 92, 0] #provider's unique record identification
224
+ }
225
+
226
+ IECR = { # atti = ' 9', attl = 32
227
+ # IDX SIZE PREC POS TYPE UNIT Description
228
+ 'cce' : [0, 1, 0, 4, 1], #change code
229
+ 'wwe' : [1, 2, 1, 5, 0], #present weather
230
+ 'ne' : [2, 1, 1, 7, 0], #total cloud amount
231
+ 'nhe' : [3, 1, 1, 8, 0], #lower cloud amount
232
+ 'he' : [4, 1, 1, 9, 0], #lower cloud base height
233
+ 'cle' : [5, 2, 1, 10, 0], #low cloud type
234
+ 'cme' : [6, 2, 1, 12, 0], #middle cloud type
235
+ 'che' : [7, 1, 1, 14, 0], #high cloud type
236
+ 'am' : [8, 3, 0.01, 15, 0, 'okta'], #middle cloud amount
237
+ 'ah' : [9, 3, 0.01, 18, 0, 'okta'], #high cloud amount
238
+ 'um' : [10, 1, 1, 21, 0, 'okta'], #NOL middle amount
239
+ 'uh' : [11, 1, 1, 22, 0, 'okta'], #NOL high amount
240
+ 'sbi' : [12, 1, 1, 23, 3], #sky-brightness indicator
241
+ 'sa' : [13, 4, 0.01, 24, 1, 'deg'], #solar altitude
242
+ 'ri' : [14, 4, 0.01, 28, 1] #relative lunar illuminance
243
+ }
244
+
245
+ IREANQC = { # atti = '95', attl = 61
246
+ # IDX SIZE PREC POS Description
247
+ 'icnr' : [0, 2, 1, 4], #input component number
248
+ 'fnr' : [1, 2, 1, 6], #field number
249
+ 'dpro' : [2, 2, 1, 8], #lead reanalysis data provider
250
+ 'dprp' : [3, 2, 1, 10], #reanalysis project short name
251
+ 'ufr' : [4, 1, 1, 12], #reanalysis usage flag
252
+ 'mfgr' : [5, 7, 1, 13], #model-collocated first guess value
253
+ 'mfgsr' : [6, 7, 1, 20], #model-collocated first guess spread
254
+ 'mar' : [7, 7, 1, 27], #model-collocated analysis value
255
+ 'masr' : [8, 7, 1, 34], #model-collocated analysis spread
256
+ 'bcr' : [9, 7, 1, 41], #bias crrected value
257
+ 'arcr' : [10, 4, 0, 48], #author reference code
258
+ 'cdr' : [11, 8, 1, 52], #creation date
259
+ 'asir' : [12, 1, 1, 60], #acess status indcator
260
+ }
261
+
262
+ IIVAD = { # atti = '96', attl = 53
263
+ # IDX SIZE PREC POS Description
264
+ 'icni' : [0, 2, 1, 4], #input component number
265
+ 'fni' : [1, 2, 1, 6], #field number
266
+ 'jvad' : [2, 1, 0, 8], #scaling factor for VAD
267
+ 'vad' : [3, 6, 1, 9], #value-added data
268
+ 'ivau1' : [4, 1, 0, 15], #type indicator for VAU1
269
+ 'jvau1' : [5, 1, 0, 16], #scaleing factor for VAU1
270
+ 'vau1' : [6, 6, 1, 17], #uncertainty of type IVAU1
271
+ 'ivau2' : [7, 1, 0, 23], #type indicator for VAU2
272
+ 'jvau2' : [8, 1, 0, 24], #scaleing factor for VAU2
273
+ 'vau2' : [9, 6, 1, 25], #uncertainty of type IVAU2
274
+ 'ivau3' : [10, 1, 0, 31], #type indicator for VAU3
275
+ 'jvau3' : [11, 1, 0, 32], #scaleing factor for VAU3
276
+ 'vau3' : [12, 6, 1, 33], #uncertainty of type IVAU3
277
+ 'vqc' : [13, 1, 1, 39], #value-added QC flag
278
+ 'arci' : [14, 4, 0, 40], #author reference code-ivad
279
+ #48 'cdi' : [15, 3, 0, 44], #creation day number
280
+ #48 'asii' : [16, 1, 1, 47], #access status indic.
281
+ #53
282
+ 'cdi' : [15, 8, 0, 44], #ISO-8601, YYYYMMDD
283
+ #53
284
+ 'asii' : [16, 1, 1, 52], #access status indic.
285
+ }
286
+
287
+ #IERROR = { # atti = '97', attl = 0
288
+ # IDX SIZE PREC POS Description
289
+ # 'icn' : [0, 2, 1, 4], #input component number
290
+ # 'fn' : [1, 2, 1, 6], #field number
291
+ # 'cef' : [2, 1, 1, 7], #corrected/errorneous field flag
292
+ # 'errd' : [3, 0, 0, 13], #corrected/errorneous field value
293
+ # 'arce' : [4, 4, 0, 0], #author reference code-eror
294
+ #'ajdne' : [5, 3, 0, 0], #archive adjusted Julian day number-error
295
+ #}
296
+
297
+ IUIDA = { # atti = '98', attl = 15
298
+ # IDX SIZE PREC POS Description
299
+ 'uid' : [0, 6, 0, 4], #unique report ID
300
+ 'rn1' : [1, 1, 0, 10], #release no.: primary
301
+ 'rn2' : [2, 1, 0, 11], #release no.: secondary
302
+ 'rn3' : [3, 1, 0, 12], #release no.: tertiary
303
+ 'rsa' : [4, 1, 1, 13], #release status indicator
304
+ 'irf' : [5, 1, 1, 14] #intermediate reject flag
305
+ }
306
+
307
+ ISUPPL = { # stti = '99' attl = 0 (variable lenth)
308
+ # IDX SIZE PREC POS Description
309
+ 'atte' : [0, 1, 1, 4], #Attm Encoding
310
+ 'supd' : [1, 0, 0, 5] #Supplemental Data
311
+ }
312
+
313
+ TABLECOUNT = 12
314
+ IMMA_NAMES = ['icoreloc', 'icorereg', 'iicoads', 'iimmt5', 'imodqc', 'imetavos',
315
+ 'inocn', 'iecr', 'ireanqc', 'iivad', 'iuida', 'isuppl']
316
+
317
+ #
318
+ # define IMMA sections, core + attms
319
+ #
320
+ IMMAS = {
321
+ # TIDX ATTI ATTL ATTM ATTLS CN
322
+ 'icoreloc' : [0, '', 45, ICORELOC, '', 'Core'],
323
+ 'icorereg' : [1, '', 63, ICOREREG, '', 'Core'],
324
+ 'iicoads' : [2, ' 1', 65, IICOADS, '65', 'Icoads'],
325
+ 'iimmt5' : [3, ' 5', 94, IIMMT5, '94', 'Immt'],
326
+ 'imodqc' : [4, ' 6', 68, IMODQC, '68', 'Mod-qc'],
327
+ 'imetavos' : [5, ' 7', 58, IMETAVOS, '58', 'Meta-vos'],
328
+ 'inocn' : [6, ' 8', 102, INOCN, '2U', 'Nocn'],
329
+ 'iecr' : [7, ' 9', 32, IECR, '32', 'Ecr'],
330
+ 'ireanqc' : [8, '95', 61, IREANQC, '61', 'Rean-qc'],
331
+ #48 'iivad' : [9, '96', 48, IIVAD, '48', 'Ivad'],
332
+ #53
333
+ 'iivad' : [9, '96', 53, IIVAD, '53', 'Ivad'],
334
+ 'iuida' : [10, '98', 15, IUIDA, '15', 'Uida'],
335
+ 'isuppl' : [11, '99', 0, ISUPPL, ' 0', 'Suppl']
336
+ }
337
+
338
+ UIDATTI = '98'
339
+
340
+ MUNIQUE = {
341
+ 'ireanqc' : ['arcr', 'cdr'],
342
+ 'iivad' : ['arci', 'cdi']
343
+ }
344
+
345
+ MULTI_NAMES = []
346
+ ATTI2NAME = {}
347
+ ATTCPOS = INVENTORY = CURTIDX = CURIIDX = 0
348
+ CURIUID = ''
349
+ IMMA_COUNTS = []
350
+ UIDIDX = 0
351
+ AUTHREFS = {}
352
+ NUM2NAME = {} # cache field names from component/field numbers
353
+ NAME2NUM = {} # cache component/field numbers from field names
354
+ UMATCH = {} # unique matches, use iidx as key
355
+ TINFO = {}
356
+ FIELDINFO = {} # cache the field metadata info for quick find
357
+ ATTM_VARS = {}
358
+ MISSING = -999999
359
+ ERROR = -999999
360
+ LEADUID = 0
361
+ CHKEXIST = 0
362
+ UIDLENGTH = 0 # uid record len
363
+ UIDOFFSET = 0 # uid value offset
364
+ ATTMNAME = None # standalone attm section name to fill
365
+
366
+ #
367
+ # initialize the table information
368
+ #
369
+ def init_table_info():
370
+
371
+ global IMMA_COUNTS, ATTCPOS, UIDATTI
372
+
373
+ IMMA_COUNTS = [0]*TABLECOUNT
374
+ ATTCPOS = ICORELOC['attc'][3]
375
+ UIDATTI = IMMAS['iuida'][1]
376
+
377
+ for aname in IMMA_NAMES:
378
+ imma = IMMAS[aname]
379
+ if imma[1]: ATTI2NAME[imma[1]] = aname
380
+ if aname in MUNIQUE: MULTI_NAMES.append(aname)
381
+
382
+ return 1
383
+
384
+ #
385
+ # identify and return the ATTM name from a given line of standalone attm input
386
+ #
387
+ def identify_attm_name(line):
388
+
389
+ global UIDOFFSET, UIDLENGTH, ATTMNAME
390
+ if LEADUID:
391
+ UIDOFFSET = 0
392
+ UIDLENGTH = 6
393
+ atti = line[6:8]
394
+ elif re.match(r'^9815', line):
395
+ UIDOFFSET = 4
396
+ UIDLENGTH = 15
397
+ atti = line[15:17]
398
+ else:
399
+ atti = None
400
+
401
+ if atti and atti in ATTI2NAME:
402
+ ATTMNAME = ATTI2NAME[atti]
403
+ else:
404
+ ATTMNAME = None
405
+
406
+ return ATTMNAME
407
+
408
+ #
409
+ # cache field information for given attm and variable name
410
+ #
411
+ def cache_field_info(aname, var, uidopt = 0):
412
+
413
+ global FIELDINFO, LEADUID
414
+ if aname not in IMMAS: PgLOG.pglog("{}: Unkown attm name provided to fill field {}".format(aname, var), PgLOG.LGEREX)
415
+ imma = IMMAS[aname]
416
+ attm = imma[3]
417
+ if var not in attm: PgLOG.pglog("{}: Field name not in attm {}".format(var, aname), PgLOG.LGEREX)
418
+ fld = attm[var]
419
+
420
+ if uidopt: LEADUID = uidopt
421
+ FIELDINFO = {'aname' : aname, 'atti' : imma[1], 'attl' : imma[2], 'var' : var, 'fld' : fld, 'prec' : fld[2]}
422
+
423
+ #
424
+ # append the individual fields and return imma records for one line input
425
+ #
426
+ def get_imma_records(line, cdate, records):
427
+
428
+ global CURIIDX, CURIUID
429
+ llen = len(line)
430
+ if llen == 0: return records
431
+
432
+ if CURIUID: # got core section already
433
+ coreidx = 2
434
+ offset = UIDLENGTH
435
+ uid = CURIUID
436
+ else:
437
+ coreidx = 0
438
+ offset = 0
439
+ CURIIDX += 1
440
+ pgrecs = {}
441
+
442
+ while (llen-offset) > 3:
443
+ if coreidx < 2:
444
+ aname = IMMA_NAMES[coreidx]
445
+ coreidx += 1
446
+ else:
447
+ aname = ATTI2NAME[line[offset:offset+2]]
448
+ imma = IMMAS[aname]
449
+ pgrec = get_one_attm(imma[3], offset, line)
450
+ #48 if aname == 'iivad': pgrec['cdi'] = PgUtil.adddate('2014-01-01', 0, 0, I36(pgrec['cdi']), 'YYYMMDD')
451
+ if aname not in records: records[aname] = initialize_attm_records(imma[3])
452
+ if CURIUID:
453
+ append_one_attm(uid, imma[0], imma[3], pgrec, records[aname])
454
+ else:
455
+ pgrecs[aname] = pgrec
456
+
457
+ if not imma[2]: break # force stop for suppl attm
458
+ offset += imma[2]
459
+
460
+ if CURIUID: return records
461
+
462
+ if 'iuida' not in pgrecs: PgLOG.pglog("Miss UID attm: " + line, PgLOG.LGEREX)
463
+ uid = pgrecs['iuida']['uid']
464
+ records['icoreloc']['date'].append(cdate)
465
+
466
+ for aname in pgrecs:
467
+ imma = IMMAS[aname]
468
+ append_one_attm(uid, imma[0], imma[3], pgrecs[aname], records[aname])
469
+
470
+ return records
471
+
472
+ #
473
+ # append the individual fields and return imma records for one line of multi-attm record
474
+ #
475
+ def get_imma_multiple_records(line, records):
476
+
477
+ llen = len(line)
478
+ if llen == 0: return records
479
+ uid = line[4:10]
480
+ offset = 15
481
+ aname = ATTI2NAME[line[15:17]]
482
+ imma = IMMAS[aname]
483
+ if aname not in records: records[aname] = initialize_attm_records(imma[3])
484
+
485
+ while (llen-offset) > 3:
486
+ pgrec = get_one_attm(imma[3], offset, line)
487
+ append_one_attm(uid, imma[0], imma[3], pgrec, records[aname])
488
+ offset += imma[2]
489
+
490
+ return records
491
+
492
+ #
493
+ # read file line and fill a single field value into db
494
+ #
495
+ def set_imma_field(line):
496
+
497
+ llen = len(line)
498
+ var = FIELDINFO['var']
499
+ pgrec = {}
500
+
501
+ if ATTMNAME: # attm name is provided
502
+ coreidx = 2 # skip core sections
503
+ offset = UIDLENGTH
504
+ uid = line[UIDOFFSET:UIDOFFSET+6]
505
+ cont = 1
506
+ else:
507
+ coreidx = 0
508
+ offset = 0
509
+ uid = None
510
+ cont = 2
511
+
512
+ getval = 0
513
+ while (llen-offset) > 3:
514
+ if coreidx < 2:
515
+ aname = IMMA_NAMES[coreidx]
516
+ coreidx += 1
517
+ if aname == FIELDINFO['aname']: getval = 1
518
+ else:
519
+ atti = line[offset:offset+2]
520
+ if atti == UIDATTI:
521
+ uid = line[offset+4:offset+10]
522
+ cont -= 1
523
+ if atti == FIELDINFO['atti']:
524
+ getval = 1
525
+ else:
526
+ aname = ATTI2NAME[atti]
527
+
528
+ if getval:
529
+ fld = FIELDINFO['fld']
530
+ pos = offset + fld[3]
531
+ if fld[1] > 0:
532
+ val = line[pos:pos+fld[1]]
533
+ else:
534
+ val = line[pos:]
535
+ val = val.rstrip(val) # remove trailing whitespaces
536
+ if len(val) > 0:
537
+ if fld[2] > 0:
538
+ cnd = "{} = {}".format(var, val)
539
+ pgrec[var] = int(val)
540
+ else:
541
+ cnd = "{} = '{}'".format(var, val)
542
+ pgrec[var] = val
543
+ getval = 0
544
+ cont -= 1
545
+ offset += FIELDINFO['attl']
546
+ else:
547
+ offset += IMMAS[aname][2]
548
+
549
+ if cont <= 0: break
550
+
551
+ if not pgrec: return 0
552
+
553
+ if not uid: PgLOG.pglog("Miss UID attm: " + line, PgLOG.LGEREX)
554
+ if not get_itidx_date(uid): return 0
555
+
556
+ tname = "{}_{}".format(FIELDINFO['aname'], CURTIDX)
557
+ if PgDBI.pgget(tname, "", "iidx = {} AND {}".format(CURIIDX, cnd)): return 0
558
+ return PgDBI.pgupdt(tname, pgrec, "iidx = {}".format(CURIIDX), PgLOG.LGEREX)
559
+
560
+ #
561
+ # get all field values for a single attm
562
+ #
563
+ def get_one_attm(attm, offset, line):
564
+
565
+ pgrec = {}
566
+ for var in attm:
567
+ fld = attm[var]
568
+ pos = offset + fld[3]
569
+ if fld[1] > 0:
570
+ val = line[pos:pos+fld[1]]
571
+ else:
572
+ val = line[pos:]
573
+
574
+ val = val.rstrip() # remove trailing whitespaces
575
+ if fld[2] > 0:
576
+ pgrec[var] = (int(val) if val else None)
577
+ else:
578
+ pgrec[var] = val
579
+
580
+ return pgrec
581
+
582
+ #
583
+ # Initialize dict records for specified attm table
584
+ #
585
+ def initialize_attm_records(attm):
586
+
587
+ pgrecs = {'iidx' : [], 'uid' : []}
588
+ for var in attm: pgrecs[var] = []
589
+ if 'yr' in attm: pgrecs['date'] = []
590
+
591
+ return pgrecs
592
+
593
+ #
594
+ # append one attm record to the multiple attm records
595
+ #
596
+ def append_one_attm(uid, aidx, attm, pgrec, pgrecs):
597
+
598
+ global IMMA_COUNTS
599
+ pgrecs['iidx'].append(CURIIDX)
600
+ if 'uid' not in attm: pgrecs['uid'].append(uid)
601
+ for var in attm: pgrecs[var].append(pgrec[var])
602
+ IMMA_COUNTS[aidx] += 1 # row index for individual table
603
+
604
+ #
605
+ # get imma attm counts for a given line of imma record
606
+ #
607
+ def get_imma_counts(line, acnts):
608
+
609
+ global CURIIDX, CURIUID
610
+ llen = len(line)
611
+ if llen == 0: return acnts
612
+
613
+ if CURIUID:
614
+ coreidx = 2
615
+ offset = UIDLENGTH
616
+ else:
617
+ coreidx = 0
618
+ offset = 0
619
+ CURIIDX += 1
620
+
621
+ while (llen-offset) > 3:
622
+ if coreidx < 2:
623
+ aname = IMMA_NAMES[coreidx]
624
+ coreidx += 1
625
+ else:
626
+ aname = ATTI2NAME[line[offset:offset+2]]
627
+ imma = IMMAS[aname]
628
+ acnts[imma[0]] += 1
629
+ if not imma[2]: break
630
+ offset += imma[2]
631
+
632
+ return acnts
633
+
634
+ #
635
+ # get imma multiple attm countsfor a given line of multi-attm record
636
+ #
637
+ def get_imma_multiple_counts(line, acnts):
638
+
639
+ llen = len(line)
640
+ if llen == 0: return acnts
641
+ offset = 15
642
+ aname = ATTI2NAME[line[15:17]]
643
+ imma = IMMAS[aname]
644
+
645
+ while (llen-offset) > 3:
646
+ acnts[imma[0]] += 1
647
+ offset += imma[2]
648
+
649
+ return acnts
650
+
651
+ #
652
+ # add multiple imma records into different tables in RDADB
653
+ #
654
+ def add_imma_records(cdate, records):
655
+
656
+ global INVENTORY, CURTIDX
657
+ if INVENTORY and IMMA_NAMES[0] in records: # add counting record into inventory table
658
+ ulen = len(records[IMMA_NAMES[0]]['uid'])
659
+ if ulen > 0: INVENTORY = add_inventory_record(INVENTORY['fname'], cdate, ulen, INVENTORY)
660
+ if CURTIDX < INVENTORY['tidx']: CURTIDX = INVENTORY['tidx']
661
+ tidx = CURTIDX
662
+ else:
663
+ tidx = date2tidx(cdate)
664
+ acnts = [0]*TABLECOUNT
665
+ for i in range(TABLECOUNT):
666
+ if not IMMA_COUNTS[i]: continue
667
+ aname = IMMA_NAMES[i]
668
+ acnts[i] = add_records_to_table(aname, str(tidx), records[aname], cdate)
669
+ IMMA_COUNTS[i] = 0
670
+
671
+ iuida = records['iuida'] if 'iuida' in records else None
672
+ update_control_tables(cdate, acnts, iuida, tidx)
673
+
674
+ return acnts
675
+
676
+ #
677
+ # read attm records for given date
678
+ #
679
+ def read_attm_for_date(aname, cdate, tidx = None):
680
+
681
+ if tidx is None:
682
+ tidx = date2tidx(cdate)
683
+ if tidx is None: return None
684
+
685
+ if aname == IMMA_NAMES[0]: return read_coreloc_for_date(cdate, tidx)
686
+
687
+ table = "{}_{}".format(aname, tidx)
688
+ if not PgDBI.pgcheck(table): return None
689
+ loctable = "{}_{}".format(IMMA_NAMES[0], tidx)
690
+ jtables = "{} a, {} b".format(table, loctable)
691
+
692
+ return PgDBI.pgmget(jtables, "a.*", "b.date = '{}' AND a.iidx = b.iidx ORDER BY iidx".format(cdate), PgLOG.LGEREX)
693
+
694
+ #
695
+ # read core records for given date
696
+ #
697
+ def read_coreloc_for_date(cdate, tidx = None):
698
+
699
+ global CURTIDX
700
+ if not tidx:
701
+ tidx = date2tidx(cdate)
702
+ if not tidx: return None
703
+ CURTIDX = tidx
704
+
705
+ return PgDBI.pgmget("{}_{}".format(IMMA_NAMES[0], tidx), '*', "date = '{}' ORDER BY iidx".format(cdate))
706
+
707
+ #
708
+ # read attm record for given uid
709
+ #
710
+ def read_attm_for_uid(aname, uid, tidx):
711
+
712
+ if aname == IMMA_NAMES[0]: return read_coreloc_for_uid(uid, tidx)
713
+
714
+ table = "{}_{}".format(aname, tidx)
715
+ if not PgDBI.pgcheck(table): return None
716
+
717
+ return PgDBI.pgget(table, "*", "uid = '{}'".format(uid), PgLOG.LGEREX)
718
+
719
+ #
720
+ # read core records for given uid
721
+ #
722
+ def read_coreloc_for_uid(uid, tidx):
723
+
724
+ return PgDBI.pgget("{}_{}".format(IMMA_NAMES[0], tidx), '*', "uid = '{}'".format(uid))
725
+
726
+ #
727
+ # write IMMA records to file
728
+ #
729
+ def write_imma_file(fh, corelocs):
730
+
731
+ (acount, anames, atables, aindices) = get_attm_names(CURTIDX)
732
+ rcnt = len(corelocs['iidx'])
733
+ acnts = [0]*TABLECOUNT
734
+
735
+ for r in range(rcnt):
736
+ coreloc = PgUtil.onerecord(corelocs, r)
737
+ line = get_attm_line(IMMA_NAMES[0], None, 0, coreloc)
738
+ acnts[0] += 1
739
+ ilines = []
740
+ acnt = -1
741
+ for a in range(acount):
742
+ if anames[a] in MUNIQUE:
743
+ (icnt, iline) = get_multiple_attm_line(anames[a], atables[a], coreloc['iidx'])
744
+ if icnt > 0:
745
+ acnt += icnt # un-comment if multiple attms are counted
746
+ acnts[aindices[a]] += icnt
747
+ ilines.append(iline)
748
+ else:
749
+ aline = get_attm_line(anames[a], atables[a], coreloc['iidx'])
750
+ if not aline: continue
751
+ line += aline
752
+ if anames[a] == 'iuida' and ilines:
753
+ for i in len(ilines):
754
+ ilines[i] = aline + ilines[i]
755
+ acnts[aindices[a]] += 1
756
+ acnt += 1
757
+
758
+ if acnt != coreloc['attc']: line[ATTCPOS] = B36(acnt)
759
+
760
+ fh.write(line + "\n") # main record
761
+ if ilines:
762
+ for il in ilines:
763
+ fh.write(il + "\n") # add standalone multiple attm line
764
+
765
+ return acnts
766
+
767
+ #
768
+ # write IMMA records for given date
769
+ #
770
+ def write_imma_records(fh, cdate, tidx, dumpall):
771
+
772
+ acnts = [0]*TABLECOUNT
773
+ if not tidx:
774
+ tidx = date2tidx(cdate)
775
+ if not tidx: return None
776
+
777
+ dcnd = "date = '{}' ORDER BY iidx".format(cdate)
778
+ mtable = "{}_{}".format(IMMA_NAMES[0], tidx)
779
+ pgrecs = PgDBI.pgmget(mtable, "*", dcnd)
780
+ if not pgrecs: return None
781
+ acnts[0] = count = len(pgrecs['iidx'])
782
+ minidx = pgrecs['iidx'][0]
783
+ jcnd = "m.iidx = n.iidx AND " + dcnd
784
+ tcnd = "tidx = {} AND attm =".format(tidx)
785
+ atable = "cntldb.iattm"
786
+
787
+ lines = ['']*count
788
+ attcs = [-2]*count
789
+ if dumpall: ulines = ['']*count
790
+ build_imma_lines(IMMA_NAMES[0], minidx, count, pgrecs, lines, attcs)
791
+ atsave = pgrecs['attc']
792
+
793
+ # dump main record
794
+ for a in range(1, TABLECOUNT):
795
+ aname = IMMA_NAMES[a]
796
+ if aname in MUNIQUE: continue
797
+ if PgDBI.pgget(atable, "", "{} '{}'".format(tcnd, aname)):
798
+ ntable = "{}_{}".format(aname, tidx)
799
+ pgrecs = PgDBI.pgmget("{} m, {} n".format(mtable, ntable), "n.*", jcnd)
800
+ if not pgrecs: continue
801
+ acnts[a] = len(pgrecs['iidx'])
802
+ if dumpall and aname == "iuida":
803
+ build_imma_lines(aname, minidx, acnts[a], pgrecs, ulines, attcs)
804
+ for i in range(count):
805
+ lines[i] += ulines[i]
806
+ else:
807
+ build_imma_lines(aname, minidx, acnts[a], pgrecs, lines, attcs)
808
+
809
+ if dumpall: # append the multi-line attms
810
+ for a in range(1, TABLECOUNT):
811
+ aname = IMMA_NAMES[a]
812
+ if MUNIQUE[aname] is None: continue
813
+ if PgDBI.pgget(atable, "", "{} '{}'".format(tcnd, aname)):
814
+ ntable = "{}_{}".format(aname, tidx)
815
+ pgrecs = PgDBI.pgmget("{} m, {} n".format(mtable, ntable), "n.*", jcnd)
816
+ if not pgrecs: continue
817
+ acnts[a] = len(pgrecs['iidx'])
818
+ append_imma_lines(aname, minidx, acnts[a], pgrecs, ulines, lines, attcs)
819
+
820
+ for i in range(count):
821
+ if attcs[i] != atsave[i]:
822
+ acnt = attcs[i]
823
+ line = lines[i]
824
+ lines[i] = line[0:ATTCPOS] + B36(acnt) + line[ATTCPOS+1:]
825
+ fh.write(lines[i] + "\n")
826
+
827
+ return acnts
828
+
829
+ #
830
+ # build daily imma lines by appending each attm
831
+ #
832
+ def build_imma_lines(aname, minidx, count, pgrecs, lines, attcs):
833
+
834
+ imma = IMMAS[aname]
835
+ attm = imma[3]
836
+
837
+ if aname in ATTM_VARS:
838
+ vars = ATTM_VARS[aname]
839
+ else:
840
+ ATTM_VARS[aname] = vars = order_attm_variables(attm)
841
+
842
+ for i in range(count):
843
+ pgrec = PgUtil.onerecord(pgrecs, i)
844
+ line = ''
845
+ for var in vars:
846
+ vlen = attm[var][1]
847
+ if vlen > 0:
848
+ if pgrec[var] is None:
849
+ line += "{:{}}".format(' ', vlen)
850
+ elif attm[var][2] > 0:
851
+ line += "{:{}}".format(pgrec[var], vlen)
852
+ else:
853
+ line += "{:{}}".format(pgrec[var], vlen)
854
+ elif pgrec[var] is not None:
855
+ line += pgrec[var] # append note
856
+
857
+ idx = pgrec['iidx'] - minidx
858
+ lines[idx] += imma[1] + imma[4] + line
859
+ attcs[idx] += 1
860
+
861
+ #
862
+ # append daily imma lines for each multi-line attm
863
+ #
864
+ def append_imma_lines(aname, minidx, count, pgrecs, ulines, lines, attcs):
865
+
866
+ imma = IMMAS[aname]
867
+ attm = imma[3]
868
+
869
+ if ATTM_VARS[aname]:
870
+ vars = ATTM_VARS[aname]
871
+ else:
872
+ ATTM_VARS[aname] = vars = order_attm_variables(attm)
873
+
874
+ pidx = -1
875
+ for i in range(count):
876
+ pgrec = PgUtil.onerecord(pgrecs, i)
877
+ cidx = pgrec['iidx'] - minidx
878
+ if cidx > pidx: lines[cidx] += "\n" + ulines[cidx]
879
+ line = imma[1] + imma[4]
880
+ for var in vars:
881
+ vlen = attm[var][1]
882
+ if pgrec[var] is None:
883
+ line += "{:{}}".format(' ', vlen)
884
+ elif attm[var][2] > 0:
885
+ line += "{:{}}".format(pgrec[var], vlen)
886
+ else:
887
+ line += "{:{}}".format(pgrec[var], vlen)
888
+ lines[cidx] += line
889
+ attcs[cidx] += 1
890
+ pidx = cidx
891
+
892
+ #
893
+ # count IMMA records for given date
894
+ #
895
+ def count_imma_records(cdate, tidx, cntall):
896
+
897
+ acnts = [0]*TABLECOUNT
898
+ if not tidx:
899
+ tidx = date2tidx(cdate)
900
+ if not tidx: return None
901
+
902
+ atable = "cntldb.iattm"
903
+ tcnd = "tidx = {}".format(tidx)
904
+ dcnd = "date = '{}'".format(cdate)
905
+ mtable = "{}_{}".format(IMMA_NAMES[0], tidx)
906
+ jcnd = "m.iidx = n.iidx AND " + dcnd
907
+ acnts[0] = PgDBI.pgget(mtable, "", dcnd)
908
+ if not acnts[0]: return None
909
+
910
+ for i in range(1,TABLECOUNT):
911
+ aname = IMMA_NAMES[i]
912
+ if not cntall and aname in MUNIQUE: continue
913
+ if PgDBI.pgget(atable, "", "{} AND attm = '{}'".format(tcnd, aname)):
914
+ ntable = "{}_{}".format(aname, tidx)
915
+ acnts[i] = PgDBI.pgget("{} m, {} n".format(mtable, ntable), "", jcnd)
916
+
917
+ return acnts
918
+
919
+ #
920
+ # add inventory information into control db
921
+ #
922
+ def add_inventory_record(fname, cdate, count, inventory, cntopt = 0):
923
+
924
+ didx = 0
925
+ table = "cntldb.inventory"
926
+
927
+ if cntopt == 2:
928
+ cnd = "date = '{}'".format(cdate)
929
+ pgrec = PgDBI.pgget(table, "didx, count", cnd, PgLOG.LGEREX)
930
+ if not pgrec: PgLOG.pglog("{}: error get record for {}".format(table, cnd), PgLOG.LGEREX)
931
+ count = pgrec['count']
932
+ didx = pgrec['didx']
933
+ record = {}
934
+ else:
935
+ record = {'date' : cdate, 'fname' : fname, 'count' : count}
936
+
937
+ if cntopt != 1:
938
+ record['tidx'] = inventory['tidx']
939
+ record['tcount'] = inventory['tcount'] + count
940
+ record['miniidx'] = inventory['maxiidx'] + 1
941
+ record['maxiidx'] = inventory['maxiidx'] + count
942
+ if record['tcount'] > PgDBI.PGDBI['MAXICNT']:
943
+ record['tidx'] += 1
944
+ record['tcount'] = count
945
+
946
+ if didx:
947
+ cnd = "didx = {}".format(didx)
948
+ if not PgDBI.pgupdt(table, record, cnd, PgLOG.LGEREX):
949
+ PgLOG.pglog("{}: error update table for {}".format(table, cnd), PgLOG.LGEREX)
950
+ else:
951
+ didx = PgDBI.pgadd(table, record, PgLOG.LGEREX|PgLOG.AUTOID)
952
+
953
+ record['didx'] = didx
954
+ if cntopt == 2:
955
+ record['count'] = count
956
+ record['date'] = cdate
957
+
958
+ return record
959
+
960
+ #
961
+ # get the attm names for the current tidx
962
+ #
963
+ def get_attm_names(tidx):
964
+
965
+ anames = []
966
+ atables = []
967
+ aindices = []
968
+ attms = {}
969
+ acnt = 0
970
+ pgrecs = PgDBI.pgmget("cntldb.iattm", "attm", "tidx = {}".format(tidx), PgLOG.LGEREX)
971
+ if not pgrecs: PgLOG.pglog("miss iattm record for tidx = {}".format(tidx), PgLOG.LGEREX)
972
+ for aname in pgrecs['attm']: attms[aname] = 1
973
+
974
+ for i in range(1, TABLECOUNT):
975
+ aname = IMMA_NAMES[i]
976
+ if aname in attms:
977
+ anames.append(aname)
978
+ atables.append("{}_{}".format(aname, tidx))
979
+ aindices.append(i)
980
+ acnt += 1
981
+
982
+ return (acnt, anames, atables, aindices)
983
+
984
+ #
985
+ # get the attm line for the attm name and current iidx
986
+ #
987
+ def get_attm_line(aname, atable, iidx, pgrec):
988
+
989
+ if not pgrec: pgrec = PgDBI.pgget(atable, "*", "iidx = {}".format(iidx), PgLOG.LGEREX)
990
+ return build_one_attm_line(aname, pgrec) if pgrec else None
991
+
992
+ #
993
+ # get the attm line for the multiple attms of current iidx
994
+ #
995
+ def get_multiple_attm_line(aname, atable, iidx):
996
+
997
+ pgrecs = PgDBI.pgmget(atable, "*", "iidx = {} ORDER BY lidx".format(iidx), PgLOG.LGEREX)
998
+ icnt = (len(pgrecs['lidx']) if pgrecs else 0)
999
+ if not icnt: return (0, None)
1000
+
1001
+ iline = ''
1002
+ for i in range(icnt):
1003
+ iline += build_one_attm_line(aname, PgUtil.onerecord(pgrecs, i))
1004
+
1005
+ return (icnt, iline)
1006
+
1007
+ #
1008
+ # build the string line for the attm record and current iidx
1009
+ #
1010
+ def build_one_attm_line(aname, pgrec):
1011
+
1012
+ imma = IMMAS[aname]
1013
+ attm = imma[3]
1014
+ line = imma[1] + imma[4]
1015
+
1016
+ if aname in ATTM_VARS:
1017
+ vars = ATTM_VARS[aname]
1018
+ else:
1019
+ ATTM_VARS[aname] = vars = order_attm_variables(attm)
1020
+
1021
+ for var in vars:
1022
+ vlen = attm[var][1]
1023
+ if vlen > 0:
1024
+ if pgrec[var] is None:
1025
+ line += "{:{}}".format(' ', vlen)
1026
+ elif attm[var][2] > 0:
1027
+ line += "{:{}}".format(pgrec[var], vlen)
1028
+ else:
1029
+ line += "{:{}}".format(pgrec[var], vlen)
1030
+ elif pgrec[var] is not None:
1031
+ line += pgrec[var] # append note
1032
+
1033
+ return line
1034
+
1035
+ #
1036
+ # find an existing imma record in RDADB
1037
+ #
1038
+ def find_imma_record(coreloc):
1039
+
1040
+ cnd = "date = '{}'".format(coreloc['date'])
1041
+ if coreloc['dy'] is None:
1042
+ cnd += " AND dy IS NULL"
1043
+ if coreloc['hr'] is not None:
1044
+ cnd += " AND hr = {}".format(coreloc['hr'])
1045
+ else:
1046
+ cnd += " AND hr IS NULL"
1047
+ if coreloc['lat'] is not None:
1048
+ cnd += " AND lat = {}".format(coreloc['lat'])
1049
+ else:
1050
+ cnd += " AND lat IS NULL"
1051
+ if coreloc['lon'] is not None:
1052
+ cnd += " AND lon = {}".format(coreloc['lon'])
1053
+ else:
1054
+ cnd += " AND lon IS NULL"
1055
+ if coreloc['id'] is not None:
1056
+ cnd += " AND id = '{}'".format(coreloc['id'])
1057
+ else:
1058
+ cnd += " AND id IS NULL"
1059
+
1060
+ pgrec = PgDBI.pgget("coreloc", "iidx", cnd, PgLOG.LGWNEX)
1061
+
1062
+ return (pgrec['iidx'] if pgrec else 0)
1063
+
1064
+ #
1065
+ # get imma date
1066
+ #
1067
+ def get_imma_date(line):
1068
+
1069
+ if ATTMNAME:
1070
+ return get_itidx_date(line[UIDOFFSET:UIDOFFSET+6])
1071
+ else:
1072
+ return get_record_date(line[0:4], line[4:6], line[6:8])
1073
+
1074
+ #
1075
+ # get the itidx record from given uid
1076
+ #
1077
+ def get_itidx_date(uid):
1078
+
1079
+ global CURIUID, CURIIDX, CURTIDX
1080
+ uidx = uid[0:2].lower()
1081
+ suid = uid[2:6]
1082
+ table = "cntldb.itidx_{}".format(uidx)
1083
+
1084
+ pgrec = PgDBI.pgget(table, "*", "suid = '{}'".format(suid), PgLOG.LGEREX)
1085
+ if not pgrec: return PgLOG.pglog("{}: SKIP suid not in {}".format(suid, table), PgLOG.WARNLG)
1086
+
1087
+ if CHKEXIST: # check
1088
+ table = "{}_{}".format(ATTMNAME, pgrec['tidx'])
1089
+ cnd = "iidx = {}".format(pgrec['iidx'])
1090
+ if ATTMNAME in MUNIQUE:
1091
+ for fname in MUNIQUE[ATTMNAME]:
1092
+ cnd += " AND {} = '{}'".format(fname, pgrec[fname])
1093
+
1094
+ if PgDBI.pgget(table, "", cnd): return None
1095
+
1096
+ CURIUID = uid
1097
+ CURIIDX = pgrec['iidx']
1098
+ CURTIDX = pgrec['tidx']
1099
+
1100
+ return pgrec['date']
1101
+
1102
+ #
1103
+ # get record date for given year, month and day
1104
+ #
1105
+ def get_record_date(yr, mo, dy):
1106
+
1107
+ global CURIUID
1108
+ mo = mo.strip()
1109
+ dy = dy.strip()
1110
+ if not mo: PgLOG.pglog("missing month", PgLOG.LGEREX)
1111
+
1112
+ nyr = int(yr)
1113
+ nmo = int(mo)
1114
+ sym = "{}-{}".format(yr, mo)
1115
+ if dy:
1116
+ ndy = int(dy)
1117
+ if ndy < 1:
1118
+ ndy = 1
1119
+ PgLOG.pglog("{}-{}: set dy {} to 1".format(yr, mo, dy), PgLOG.LOGWRN)
1120
+ else:
1121
+ ndy = 1
1122
+ PgLOG.pglog("{}-{}: set missing dy to 1".format(yr, mo), PgLOG.LOGWRN)
1123
+
1124
+ CURIUID = ''
1125
+
1126
+ cdate = PgUtil.fmtdate(nyr, nmo, ndy)
1127
+ if ndy > 30 or nmo == 2 and ndy > 28:
1128
+ edate = PgUtil.enddate(sym+"-01", 0, 'M')
1129
+ if cdate > edate:
1130
+ cdate = edate
1131
+ PgLOG.pglog("{}: set {}-{} to {}".format(cdate, sym, dy, edate), PgLOG.LOGWRN)
1132
+
1133
+ return cdate
1134
+
1135
+ #
1136
+ # get the tidx from table inventory for given date
1137
+ #
1138
+ def date2tidx(cdate, getend = True):
1139
+
1140
+ table = "cntldb.inventory"
1141
+ pgrec = PgDBI.pgget(table, "tidx", "date = '{}'".format(cdate), PgLOG.LGEREX)
1142
+ if pgrec: return pgrec['tidx']
1143
+
1144
+ if getend:
1145
+ cnd = "date < '{}'".format(cdate)
1146
+ pgrec = PgDBI.pgget(table, "max(tidx) tidx", cnd, PgLOG.LGEREX)
1147
+ else:
1148
+ cnd = "date > '{}'".format(cdate)
1149
+ pgrec = PgDBI.pgget(table, "min(tidx) tidx", cnd, PgLOG.LGEREX)
1150
+ if pgrec:
1151
+ return pgrec['tidx']
1152
+ else:
1153
+ return 1
1154
+
1155
+ #
1156
+ # get the date from table inventory for given iidx
1157
+ #
1158
+ def iidx2date(iidx):
1159
+
1160
+ pgrec = PgDBI.pgget("cntldb.inventory", "date", "miniidx <= {} AND maxiidx >= {}".format(iidx, iidx), PgLOG.LGEREX)
1161
+ return (pgrec['date'] if pgrec else None)
1162
+
1163
+ #
1164
+ # get field name from the component number and field number
1165
+ #
1166
+ def number2name(cn, fn):
1167
+
1168
+ key = cn * 100 + fn
1169
+ if key in NUM2NAME: return NUM2NAME[key]
1170
+
1171
+ if cn > 0:
1172
+ offset = 3
1173
+ for i in range(2, TABLECOUNT):
1174
+ aname = IMMA_NAMES[i]
1175
+ if cn == int(IMMAS[aname][1]): break
1176
+ if i >= TABLECOUNT: PgLOG.pglog("{}: Cannot find Component".format(cn), PgLOG.LGEREX)
1177
+ elif fn < 17:
1178
+ offset = 1
1179
+ aname = IMMA_NAMES[0]
1180
+ else:
1181
+ offset = 17
1182
+ aname = IMMA_NAMES[1]
1183
+
1184
+ attm = IMMAS[aname][3]
1185
+ for fname in attm:
1186
+ if fn == (attm[fname][0]+offset):
1187
+ NUM2NAME[key] = [fname, aname]
1188
+ return NUM2NAME[key]
1189
+
1190
+ PgLOG.pglog("{}: Cannot find field name in Component '{}'".format(fn, aname), PgLOG.LGEREX)
1191
+
1192
+ #
1193
+ # get component number and field number from give field name
1194
+ #
1195
+ def name2number(fname):
1196
+
1197
+ if fname in NAME2NUM: return NAME2NUM[fname]
1198
+
1199
+ for i in range(TABLECOUNT):
1200
+ aname = IMMA_NAMES[i]
1201
+ attm = IMMAS[aname][3]
1202
+ if fname in attm:
1203
+ cn = int(IMMAS[aname][1]) if IMMAS[aname][1] else 0
1204
+ fn = attm[fname][0]
1205
+ if i == 0:
1206
+ fn += 1
1207
+ elif i == 1:
1208
+ fn += 17
1209
+ else:
1210
+ fn += 3
1211
+
1212
+ NAME2NUM[fname] = [cn, fn, aname]
1213
+ return NAME2NUM[fname]
1214
+
1215
+ PgLOG.pglog(fname + ": Cannot find Field Name", PgLOG.LGEREX)
1216
+
1217
+ #
1218
+ # convert integers to floating values
1219
+ #
1220
+ def float_imma_record(record):
1221
+
1222
+ for aname in IMMA_NAMES:
1223
+ if aname not in record: continue
1224
+ attm = IMMAS[aname][3]
1225
+ for key in attm:
1226
+ prec = attm[key][2]
1227
+ if prec == 1 or prec == 0: continue
1228
+ val = record[aname][key] if record[aname][key] else 0
1229
+ if not val: continue
1230
+ record[aname][key] = val * prec
1231
+
1232
+ return record
1233
+
1234
+ #
1235
+ # convert the floating values to integers
1236
+ #
1237
+ def integer_imma_record(record):
1238
+
1239
+ for aname in IMMA_NAMES:
1240
+ if aname not in record: continue
1241
+ attm = IMMAS[aname][3]
1242
+ for key in attm:
1243
+ prec = attm[key][2]
1244
+ if prec == 1 or prec == 0: continue
1245
+ val = record[aname][key] if record[aname][key] else 0
1246
+ if not val: continue
1247
+ if val > 0:
1248
+ record[aname][key] = int(val/prec + 0.5)
1249
+ else:
1250
+ record[aname][key] = int(val/prec - 0.5)
1251
+
1252
+ return record
1253
+
1254
+ #
1255
+ # order attm fields according FN and return ordered field array
1256
+ #
1257
+ def order_attm_variables(attm, aname = None):
1258
+
1259
+ if not attm: attm = IMMAS[aname][3]
1260
+
1261
+ return list(attm)
1262
+
1263
+ #
1264
+ # get max inventory index
1265
+ #
1266
+ def get_inventory_record(didx = 0, cntopt = 0):
1267
+
1268
+ table = "cntldb.inventory"
1269
+
1270
+ if not didx:
1271
+ if cntopt == 2:
1272
+ pgrec = PgDBI.pgget(table, "min(date) mdate", "tcount = 0", PgLOG.LGEREX)
1273
+ if not (pgrec and pgrec['mdate']): PgLOG.pglog(table+": no counted-only inventory record exists", PgLOG.LGEREX)
1274
+ didx = get_inventory_didx(pgrec['mdate'], 1)
1275
+ elif cntopt == 0:
1276
+ pgrec = PgDBI.pgget(table, "max(didx) idx", "", PgLOG.LGEREX)
1277
+ didx = (pgrec['idx'] if pgrec else 0)
1278
+ if didx:
1279
+ cnd = "didx = {}".format(didx)
1280
+ pgrec = PgDBI.pgget(table, "*", cnd, PgLOG.LGEREX)
1281
+ if not pgrec: PgLOG.pglog("{}: error get record for {}".format(table, cnd), PgLOG.LGEREX)
1282
+ else:
1283
+ pgrec = {'date' : '', 'fname' : '', 'miniidx' : 0, 'maxiidx' : 0,
1284
+ 'didx' : 0, 'count' : 0, 'tcount' : 0, 'tidx' : 1}
1285
+
1286
+ return pgrec
1287
+
1288
+ #
1289
+ # get previous/later inventory didx for given date
1290
+ #
1291
+ def get_inventory_didx(cdate, prev):
1292
+
1293
+ table = "cntldb.inventory"
1294
+ fld = "didx, date"
1295
+ if prev:
1296
+ cnd = "date < '{}' ORDER BY date DECS".format(cdate)
1297
+ else:
1298
+ cnd = "date > '{}' ORDER BY date ASC".format(cdate)
1299
+
1300
+ pgrec = PgDBI.pgget(table, fld, cnd, PgLOG.LGEREX)
1301
+ if not pgrec: PgLOG.pglog("{}: error get record for {}".format(table, cnd), PgLOG.LGEREX)
1302
+
1303
+ return pgrec['didx']
1304
+
1305
+ #
1306
+ # initialize the global indices
1307
+ #
1308
+ def init_current_indices(leaduid = 0, chkexist = 0):
1309
+
1310
+ global UIDIDX, CURIIDX, CURTIDX, CURIUID, AUTHREFS, LEADUID, CHKEXIST
1311
+ # leading info for iuida
1312
+ UIDIDX = IMMAS['iuida'][0]
1313
+ CURIIDX = 0
1314
+ CURTIDX = 1
1315
+ CURIUID = ''
1316
+ AUTHREFS = {}
1317
+ LEADUID = leaduid
1318
+ CHKEXIST = chkexist
1319
+
1320
+ #
1321
+ # initialize indices for givn date
1322
+ #
1323
+ def init_indices_for_date(cdate, fname):
1324
+
1325
+ global INVENTORY, CURIIDX, CURTIDX
1326
+ if fname:
1327
+ if not INVENTORY: INVENTORY = get_inventory_record()
1328
+ INVENTORY['fname'] = fname
1329
+ CURIIDX = INVENTORY['maxiidx']
1330
+ CURTIDX = INVENTORY['tidx']
1331
+ else:
1332
+ pgrec = PgDBI.pgget("cntldb.inventory", "*", "date = '{}'".format(cdate), PgLOG.LGEREX)
1333
+ if not pgrec: PgLOG.pglog("{}: give date not in inventory yet".format(cdate), PgLOG.LGEREX)
1334
+ if CURIIDX < pgrec['miniidx']:
1335
+ CURIIDX = pgrec['miniidx'] - 1
1336
+ CURTIDX = pgrec['tidx'] - 1
1337
+
1338
+ #
1339
+ # update or add control tables
1340
+ #
1341
+ def update_control_tables(cdate, acnts, iuida, tidx = 0):
1342
+
1343
+ if not tidx: tidx = date2tidx(cdate)
1344
+
1345
+ if iuida and acnts[0]:
1346
+ tname = "cntldb.itidx"
1347
+ records = {}
1348
+ for i in range(acnts[UIDIDX]):
1349
+ auid = iuida['uid'][i][0:2].lower()
1350
+ if auid not in records:
1351
+ records[auid] = {'suid' : [], 'date' : [], 'tidx' : [], 'iidx' : []}
1352
+ records[auid]['suid'].append(iuida['uid'][i][2:6])
1353
+ records[auid]['date'].append(cdate)
1354
+ records[auid]['tidx'].append(tidx)
1355
+ records[auid]['iidx'].append(iuida['iidx'][i])
1356
+
1357
+ for auid in records:
1358
+ add_records_to_table(tname, auid, records[auid], cdate)
1359
+
1360
+ tname = "cntldb.iattm"
1361
+ dname = tname + "_daily"
1362
+ for i in range(TABLECOUNT):
1363
+ if not acnts[i]: continue
1364
+ aname = IMMA_NAMES[i]
1365
+ cnd = "attm = '{}' AND tidx = {}".format(aname, tidx)
1366
+ pgrec = PgDBI.pgget(tname, "aidx, count", cnd, PgLOG.LGWNEX)
1367
+ if pgrec:
1368
+ record = {'count' : (pgrec['count'] + acnts[i])}
1369
+ PgDBI.pgupdt(tname, record, "aidx = {}".format(pgrec['aidx']), PgLOG.LGWNEX)
1370
+ else:
1371
+ record = {'tidx' : tidx, 'attm' : aname, 'count' : acnts[i]}
1372
+ PgDBI.pgadd(tname, record, PgLOG.LGWNEX)
1373
+
1374
+ cnd = "attm = '{}' AND date = '{}'".format(aname, cdate)
1375
+ pgrec = PgDBI.pgget(dname, "aidx, count", cnd, PgLOG.LGWNEX)
1376
+ if pgrec:
1377
+ record = {'count' : (pgrec['count'] + acnts[i])}
1378
+ PgDBI.pgupdt(dname, record, "aidx = {}".format(pgrec['aidx']), PgLOG.LGWNEX)
1379
+ else:
1380
+ record = {'date' : cdate, 'tidx' : tidx, 'attm' : aname, 'count' : acnts[i]}
1381
+ PgDBI.pgadd(dname, record, PgLOG.LGWNEX)
1382
+
1383
+ #
1384
+ # add records to a table
1385
+ #
1386
+ def add_records_to_table(tname, suffix, records, cdate):
1387
+
1388
+ table = "{}_{}".format(tname, suffix)
1389
+ if not PgDBI.pgcheck(table):
1390
+ pgcmd = PgDBI.get_pgddl_command(tname)
1391
+ PgLOG.pgsystem("{} -x {}".format(pgcmd, suffix), PgLOG.LGWNEX)
1392
+
1393
+ cnt = PgDBI.pgmadd(table, records, PgLOG.LGEREX)
1394
+ s = 's' if cnt > 1 else ''
1395
+ PgLOG.pglog("{}: {} records added to {}".format(cdate, cnt, table), PgLOG.LOGWRN)
1396
+
1397
+ return cnt
1398
+
1399
+ #
1400
+ # match imma records for given time/space, and matching variables if provided
1401
+ #
1402
+ # TODO: need more work (avoid of itable)
1403
+ #
1404
+ # return maching count
1405
+ #
1406
+ def match_imma_records(cdate, t1, t2, w, e, s, n, vt):
1407
+
1408
+ if cdate in TINFO:
1409
+ tinfo = TINFO[cdate]
1410
+ if not tinfo: return 0
1411
+ else:
1412
+ tinfo = PgDBI.pgget("cntldb.itable", "*", "bdate <= '{}' AND edate >= '{}'".format(cdate, cdate), PgLOG.LGWNEX)
1413
+ if not tinfo:
1414
+ TINFO[cdate] = 0
1415
+ return 0
1416
+
1417
+ # match time/latitude
1418
+ mrecs = PgDBI.pgmget("icoreloc_{}".format(tinfo['tidx']), "*",
1419
+ "date = '{}' AND hr BETWEEN {} AND {} AND lat BETWEEN {} AND {}".format(cdate, t1, t2, s, n), PgLOG.LGWNEX)
1420
+ if not mrecs: return 0 # no match
1421
+
1422
+ cnt = len(mrecs['iidx'])
1423
+ # match longitude, and/or variables
1424
+ m = 0
1425
+ for i in range(cnt):
1426
+ mrec = PgUtil.onerecord(mrecs, i)
1427
+ if w < e:
1428
+ if mrec['lon'] < w or mrec['lon'] > e: continue
1429
+ else:
1430
+ if mrec['lon'] > e and mrec['lon'] < w: continue
1431
+
1432
+ if not vt or match_imma_vars(tinfo, mrec, vt):
1433
+ iidx = mrec['iidx']
1434
+ if iidx not in UMATCH: UMATCH[iidx] = 1
1435
+ m += 1
1436
+
1437
+ return m
1438
+
1439
+ #
1440
+ # return 1 if a value is found for any given variable 0 otherwise
1441
+ #
1442
+ # TODO: need more work
1443
+ #
1444
+ def match_imma_vars(tinfo, mrec, vt):
1445
+
1446
+ mrecs = {}
1447
+ mrecs['icoreloc'] = mrec
1448
+ iidx = mrec['iidx']
1449
+ tidx = tinfo['tidx']
1450
+ for v in vt:
1451
+ name = vt[v]
1452
+ if name not in mrecs:
1453
+ if name in tinfo:
1454
+ mrecs[name] = PgDBI.pgget("{}_{}".format(name, tidx), "*", "iidx = {}".format(iidx))
1455
+ if not mrecs[name]: mrecs[name] = 0
1456
+ else:
1457
+ mrecs[name] = 0
1458
+
1459
+ if mrecs[name] and mrecs[name][v] is not None: return 1 # found value for variable
1460
+
1461
+ return 0
1462
+
1463
+ #
1464
+ # distant in km to degree in 0.01deg
1465
+ #
1466
+ def distant2degree(la, lo, d, b):
1467
+
1468
+ P = 3.14159265
1469
+ R = 6371
1470
+
1471
+ la *= P/18000
1472
+ lo *= P/18000
1473
+ b *= P/18000
1474
+
1475
+ lat = int(18000 * math.asin(math.sin(la)*math.cos(d/R) + math.cos(la)*math.sin(d/R)*math.cos(b))/P + 0.5)
1476
+ lon = int(18000 * (lo + math.atan2(math.sin(b)*math.sin(d/R)*math.cos(la), math.cos(d/R) - math.sin(la)*math.sin(la)))/P + 0.5)
1477
+
1478
+ return (lat, lon)
1479
+
1480
+ # integer to 36-based string
1481
+ def B36(I36):
1482
+
1483
+ STR36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
1484
+ B36 = STR36[I36%36]
1485
+ while I36 >= 36:
1486
+ I36 = int(I36/36)
1487
+ B36 = STR36[I36%36] + B36
1488
+
1489
+ return B36
1490
+
1491
+ # 36-based string to integer
1492
+ def I36(B36):
1493
+
1494
+ STR36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
1495
+ I36 = 0
1496
+ for ch in B36:
1497
+ I36 = I36*36 + int(STR36.find(ch))
1498
+
1499
+ return I36
1500
+
1501
+ #
1502
+ # convert from trimqc2.f:01C by Steve Worley
1503
+ # modified for trinqc2.f01D to trim on RH
1504
+ #
1505
+ def TRIMQC2(record, options = None):
1506
+
1507
+ values = {}
1508
+ values['sst'] = record['icorereg']['sst']
1509
+ values['at'] = record['icorereg']['at']
1510
+ values['d'] = record['icorereg']['d']
1511
+ values['w'] = record['icorereg']['w']
1512
+ values['slp'] = record['icorereg']['slp']
1513
+ values['wbt'] = record['icorereg']['wbt']
1514
+ values['dpt'] = record['icorereg']['dpt']
1515
+ values['rh'] = record['iimmt5']['rh'] if 'iimmt5' in record and record['iimmt5'] else None
1516
+
1517
+ # default to enhenced trimming
1518
+ if not options: options = {'OPDN' : 0, 'OPPT' : 1, 'OPSE' : 0, 'OPCQ' : 0, 'OPTF' : 2, 'OP11' : 1}
1519
+
1520
+ # GET TRIMMING AND OTHER QUALITY CONTROL FLAGS
1521
+ TRFLG = GETTRF(record)
1522
+ flags = TRIMQC0(record['icoreloc']['yr'], record['iicoads']['dck'], record['iicoads']['sid'],
1523
+ record['iicoads']['pt'], record['iicoads']['dups'], TRFLG, options)
1524
+
1525
+ if flags['ZZQF'] == 1: return None # RECORD REJECTED
1526
+
1527
+ if flags['SZQF'] == 1: values['sst'] = None # SST FLAG AND QC APPLICATION
1528
+ if flags['AZQF'] == 1: values['at'] = None # AT FLAG AND QC APPLICATION
1529
+ if flags['WZQF'] == 1: values['d'] = values['w'] = None # WIND, D AND W FLAG AND QC APPLICATION
1530
+ if flags['PZQF'] == 1: values['slp'] = None # SLP FLAG AND QC APPLICATION
1531
+ if flags['RZQF'] == 1: values['rh'] = values['wbt'] = values['dpt'] = None # WBT AND DPT FLAG AND QC APPLICATION
1532
+
1533
+ return values
1534
+
1535
+ #
1536
+ # converted from Fortran code trimqc0.f:01D by Sandy Lubker
1537
+ #
1538
+ def TRIMQC0(YR, DCK, SID, PT, DS, TRFLG, options):
1539
+
1540
+ flags = {'ZZQF' : 1} # INITIAL REPORT REJECTION
1541
+
1542
+ # CHECK IF TRIMMING FLAGS MISSING
1543
+ if TRFLG[0] == 0:
1544
+ PgLOG.pglog('TRIMMING FLAGS MISSING', PgLOG.LGEREX)
1545
+ return flags
1546
+
1547
+ # CHECK RANGES OF OPTIONS
1548
+ if options['OPDN'] < 0 or options['OPDN'] > 2:
1549
+ PgLOG.pglog("OPDN={}".format(options['OPDN']), PgLOG.LGEREX)
1550
+ if options['OPPT'] < 0 or options['OPPT'] > 1:
1551
+ PgLOG.pglog("OPPT={}".format(options['OPPT']), PgLOG.LGEREX)
1552
+ if options['OPSE'] < 0 or options['OPSE'] > 1:
1553
+ PgLOG.pglog("OPSE={}".format(options['OPSE']), PgLOG.LGEREX)
1554
+ if options['OPCQ'] < 0 or options['OPCQ'] > 1:
1555
+ PgLOG.pglog("OPCQ={}".format(options['OPCQ']), PgLOG.LGEREX)
1556
+ if options['OPTF'] < 0 or options['OPTF'] > 3:
1557
+ PgLOG.pglog("OPTF={}".format(options['OPTF']), PgLOG.LGEREX)
1558
+ if options['OP11'] < 0 or options['OP11'] > 1:
1559
+ PgLOG.pglog("OP11={}".format(options['OP11']), PgLOG.LGEREX)
1560
+
1561
+ B2 = TRFLG[0]
1562
+ ND = TRFLG[1]
1563
+ SF = TRFLG[2]
1564
+ AF = TRFLG[3]
1565
+ UF = TRFLG[4]
1566
+ VF = TRFLG[5]
1567
+ PF = TRFLG[6]
1568
+ RF = TRFLG[7]
1569
+ ZQ = TRFLG[8]
1570
+ SQ = TRFLG[9]
1571
+ AQ = TRFLG[10]
1572
+ WQ = TRFLG[11]
1573
+ PQ = TRFLG[12]
1574
+ RQ = TRFLG[13]
1575
+ XQ = TRFLG[14]
1576
+ CQ = TRFLG[15]
1577
+ EQ = TRFLG[16]
1578
+ LZ = TRFLG[17]
1579
+ SZ = TRFLG[18]
1580
+ AZ = TRFLG[19]
1581
+ WZ = TRFLG[20]
1582
+ PZ = TRFLG[21]
1583
+ RZ = TRFLG[22]
1584
+
1585
+ if PT is None: PT = -1
1586
+ if options['OPDN'] == 1:
1587
+ if ND == 2: return flags
1588
+ elif options['OPDN'] == 2:
1589
+ if ND == 1: return flags
1590
+ if DS > 2 and (YR >= 1950 or DS != 6): return flags
1591
+ if LZ == 1: return flags
1592
+ if (ZQ == 1 or ZQ == 3) and YR >= 1950: return flags
1593
+ if YR >= 1980:
1594
+ if SID == 25 and YR > 1984: return flags
1595
+ if SID == 30 and YR > 1984: return flags
1596
+ if SID == 33 and YR < 1986: return flags
1597
+ if options['OPPT'] == 0:
1598
+ if not (PT == 2 or PT == 5 or PT == -1 and DCK == 888): return flags
1599
+ if SID == 70 or SID == 71: return flags
1600
+ elif options['OPPT'] == 0:
1601
+ if PT > 5: return flags
1602
+ if SID == 70 or SID == 71: return flags
1603
+
1604
+ # REMOVE ELEMENT REJECTION
1605
+ flags['ZZQF'] = flags['SZQF'] = flags['AZQF'] = flags['WZQF'] = flags['PZQF'] = flags['RZQF'] = 0
1606
+
1607
+ # SOURCE EXCLUSION FLAGS
1608
+ if YR < 1980 or (PT != 13 and PT != 14 and PT != 16):
1609
+ if options['OPSE'] == 0:
1610
+ if SZ == 1: flags['SZQF'] = 1
1611
+ if AZ == 1: flags['AZQF'] = 1
1612
+ if WZ == 1: flags['WZQF'] = 1
1613
+ if YR >= 1980:
1614
+ if SID == 70 or SID == 71: flags['WZQF'] = 1
1615
+ if PZ == 1: flags['PZQF'] = 1
1616
+ if RZ == 1: flags['RZQF'] = 1
1617
+
1618
+ # COMPOSITE QC FLAGS
1619
+ if options['OPCQ'] == 0:
1620
+ if SQ > 0: flags['SZQF'] = 1
1621
+ if AQ > 0: flags['AZQF'] = 1
1622
+ if WQ > 0: flags['WZQF'] = 1
1623
+ if PQ > 0: flags['PZQF'] = 1
1624
+ if RQ > 0: flags['RZQF'] = 1
1625
+
1626
+ # TRIMMING FLAGS
1627
+ if options['OPTF'] < 3:
1628
+ if SF > (options['OPTF']*2+1):
1629
+ if options['OP11'] == 0 or SF != 11: flags['SZQF'] = 1
1630
+ if AF > (options['OPTF']*2+1): flags['AZQF'] = 1
1631
+ if UF > (options['OPTF']*2+1) or VF > (options['OPTF']*2+1): flags['WZQF'] = 1
1632
+ if PF > (options['OPTF']*2+1):
1633
+ if options['OP11'] == 0 or PF != 11: flags['PZQF'] = 1
1634
+ if RF > (options['OPTF']*2+1): flags['RZQF'] = 1
1635
+ elif options['OPTF'] == 3:
1636
+ if SF > 12: flags['SZQF'] = 1
1637
+ if AF > 12: flags['AZQF'] = 1
1638
+ if UF > 12 or VF > 12: flags['WZQF'] = 1
1639
+ if PF > 12: flags['PZQF'] = 1
1640
+ if RF > 12: flags['RZQF'] = 1
1641
+
1642
+ return flags
1643
+
1644
+ #
1645
+ # get trim flags
1646
+ #
1647
+ def GETTRF(record):
1648
+
1649
+ (ZNC,WNC,BNC,XNC,YNC,PNC,ANC,GNC,DNC,SNC,CNC,ENC,FNC,TNC) = (0,1,2,3,4,5,6,7,8,9,10,11,12,13)
1650
+ (SF,AF,UF,VF,PF,RF) = (0,1,2,3,4,5)
1651
+
1652
+ QCFLG = [None]*14
1653
+ cstr = record['iicoads']['nqcs']
1654
+ if cstr:
1655
+ i = 0
1656
+ for c in cstr:
1657
+ if c != ' ': QCFLG[i] = I36(c)
1658
+ i += 1
1659
+
1660
+ TRIMS = [None]*6
1661
+ cstr = record['iicoads']['trms']
1662
+ if cstr:
1663
+ i = 0
1664
+ for c in cstr:
1665
+ if c != ' ': TRIMS[i] = I36(c)
1666
+ i += 1
1667
+
1668
+ TRFLG = [0]*23
1669
+ if record['icoreloc']['lat'] is not None and record['icoreloc']['lon'] is not None and record['iicoads']['b10'] is not None:
1670
+ TRFLG[0] = B2QXY(QB10(record['iicoads']['b10']),record['icoreloc']['lon'],record['icoreloc']['lat'])
1671
+ if record['iicoads']['nd'] is not None: TRFLG[1] = record['iicoads']['nd']
1672
+ if TRIMS[SF] is not None:
1673
+ TRFLG[2] = TRIMS[SF]
1674
+ TRFLG[3] = TRIMS[AF]
1675
+ TRFLG[4] = TRIMS[UF]
1676
+ TRFLG[5] = TRIMS[VF]
1677
+ TRFLG[6] = TRIMS[PF]
1678
+ TRFLG[7] = TRIMS[RF]
1679
+
1680
+ if QCFLG[ZNC]:
1681
+ if QCFLG[ZNC] >= 7 and QCFLG[ZNC] != 10: TRFLG[8] = 1
1682
+ if QCFLG[SNC] >= 8 and QCFLG[SNC] != 10: TRFLG[9] = 1
1683
+ if QCFLG[ANC] >= 8 and QCFLG[ANC] != 10: TRFLG[10] = 1
1684
+ d = record['icorereg']['d']
1685
+ w = record['icorereg']['w']
1686
+ if d != None and w != None and d >= 1 and d <= 360 and w == 0 and QCFLG[WNC] == 7: TRFLG[11] = 1
1687
+ if QCFLG[PNC] >= 8 and QCFLG[PNC] != 10: TRFLG[12] = 1
1688
+ if QCFLG[GNC] >= 8 and QCFLG[GNC] != 10 or QCFLG[DNC] >= 8 and QCFLG[DNC] != 10: TRFLG[13] = 1
1689
+ if QCFLG[XNC] != 10:
1690
+ if QCFLG[XNC] >= 7:
1691
+ TRFLG[14] = 3
1692
+ elif QCFLG[XNC] >= 4:
1693
+ TRFLG[14] = 2
1694
+ elif QCFLG[XNC] >= 2:
1695
+ TRFLG[14] = 1
1696
+ if QCFLG[CNC] != 10:
1697
+ if QCFLG[CNC] >= 7:
1698
+ TRFLG[15] = 3
1699
+ elif QCFLG[CNC] >= 4:
1700
+ TRFLG[15] = 2
1701
+ elif QCFLG[CNC] >= 2:
1702
+ TRFLG[15] = 1
1703
+ if QCFLG[ENC] != 10:
1704
+ if QCFLG[ENC] >= 7:
1705
+ TRFLG[16] = 3
1706
+ elif QCFLG[ENC] >= 4:
1707
+ TRFLG[16] = 2
1708
+ elif QCFLG[ENC] >= 2:
1709
+ TRFLG[16] = 1
1710
+
1711
+ QC = record['iicoads']['qce']
1712
+ if QC:
1713
+ for i in range(13, 7, -1):
1714
+ TRFLG[i] += 2*(QC%2)
1715
+ QC >>= 1
1716
+
1717
+ if record['iicoads']['lz'] != None: TRFLG[17] = record['iicoads']['lz']
1718
+
1719
+ QC = record['iicoads']['qcz']
1720
+ if QC:
1721
+ for i in range(22, 17, -1):
1722
+ TRFLG[i] += 2*(QC%2)
1723
+ QC >>= 1
1724
+
1725
+ return TRFLG
1726
+
1727
+ #
1728
+ # B10 to Q value
1729
+ #
1730
+ def QB10(B10):
1731
+
1732
+ if B10 < 1 or B10 > 648:
1733
+ return -1
1734
+ else:
1735
+ return 2 + (int((B10-1)/324))*2 - int(((B10-1+3)%36)/18)
1736
+
1737
+ #
1738
+ # Q, X and Y to B2 values
1739
+ #
1740
+ def B2QXY(Q, X, Y):
1741
+
1742
+ YY = (-Y) if Y < 0 else Y
1743
+
1744
+ if Q < 1 or Q > 4 or X < 0 or X > 35999 or YY > 9000: return 0
1745
+
1746
+ if YY < 9000:
1747
+ if (Q%2) == 0:
1748
+ C = int(X/200)
1749
+ if C > 89: C = 89
1750
+ else:
1751
+ C = int(((36000-X)%36000)/200)
1752
+ if C > 89: C = 89
1753
+ C = 179-C
1754
+
1755
+ if int(Q/3) == 0:
1756
+ R = 89-int((9000+Y)/200)
1757
+ else:
1758
+ R = int((9000-Y)/200)
1759
+
1760
+ B2 = 2+R*180+C
1761
+ elif Y == 9000:
1762
+ B2 = 1
1763
+ else:
1764
+ B2 = 16202
1765
+
1766
+ return B2
1767
+
1768
+ #
1769
+ # wind speed/directory to component U and V
1770
+ #
1771
+ def wind2uv(w, d):
1772
+
1773
+ u = v = None
1774
+
1775
+ if w == 0 or d == 361:
1776
+ u = v = 0
1777
+ elif d > 0 and d < 361:
1778
+ d = numpy.deg2rad(d + 180)
1779
+ u = w * math.cos(d)
1780
+ v = w * math.sin(d)
1781
+
1782
+ return (u, v)
1783
+
1784
+ # call to initialize table info when the module is loaded
1785
+ init_table_info()