rda-python-icoads 3.0.2__tar.gz → 3.0.4__tar.gz

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.
Files changed (38) hide show
  1. {rda_python_icoads-3.0.2/src/rda_python_icoads.egg-info → rda_python_icoads-3.0.4}/PKG-INFO +1 -1
  2. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/pyproject.toml +1 -1
  3. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/fillicoads.py +98 -7
  4. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/fillmonth.py +55 -5
  5. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/pg_imma.py +21 -1
  6. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4/src/rda_python_icoads.egg-info}/PKG-INFO +1 -1
  7. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/LICENSE +0 -0
  8. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/MANIFEST.in +0 -0
  9. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/README.md +0 -0
  10. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/setup.cfg +0 -0
  11. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/PgIMMA.py +0 -0
  12. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/R3.0-stat_doc.pdf +0 -0
  13. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/README_R3.0_Subset.html +0 -0
  14. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/__init__.py +0 -0
  15. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/checkicoads.py +0 -0
  16. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/cleanicoads.py +0 -0
  17. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/countattm.py +0 -0
  18. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/countattmvar.py +0 -0
  19. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/counticoads.py +0 -0
  20. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/countsst.py +0 -0
  21. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/fillinventory.py +0 -0
  22. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/fillitable.py +0 -0
  23. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/fixiidx.py +0 -0
  24. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/imma1_subset.py +0 -0
  25. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/maxsst.py +0 -0
  26. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/msg +0 -0
  27. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/msg3.0_subset_readme.txt +0 -0
  28. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/msg3_subset.py +0 -0
  29. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/msg_download.py +0 -0
  30. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/msgsubset.f +0 -0
  31. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/rdimma1_csv.f +0 -0
  32. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads/writeicoads.py +0 -0
  33. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads.egg-info/SOURCES.txt +0 -0
  34. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads.egg-info/dependency_links.txt +0 -0
  35. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads.egg-info/entry_points.txt +0 -0
  36. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads.egg-info/requires.txt +0 -0
  37. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/src/rda_python_icoads.egg-info/top_level.txt +0 -0
  38. {rda_python_icoads-3.0.2 → rda_python_icoads-3.0.4}/tests/test_icoads.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_icoads
3
- Version: 3.0.2
3
+ Version: 3.0.4
4
4
  Summary: RDA python package to manage RDA ICOADS datasets
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-icoads
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "rda_python_icoads"
9
- version = "3.0.2"
9
+ version = "3.0.4"
10
10
  authors = [
11
11
  { name="Zaihua Ji", email="zji@ucar.edu" },
12
12
  ]
@@ -117,6 +117,85 @@ class FillIcoads(PgIMMA):
117
117
 
118
118
  if fcnt > 1: self.pglog("{} ({}) filled for {} files".format('/'.join(map(str, tcounts)), self.PVALS['names'], fcnt), self.LOGWRN)
119
119
 
120
+ #
121
+ # get the first data date in a given imma file
122
+ #
123
+ def get_imma_file_date(self, fname):
124
+
125
+ fdate = None
126
+ IMMA = open(fname, 'r', encoding = 'latin_1')
127
+ line = IMMA.readline()
128
+ self.identify_attm_name(line) # the date offset depends on the attm name
129
+ while line:
130
+ if not (self.PVALS['uatti'] and line[0:2] == self.PVALS['uatti']):
131
+ fdate = self.get_imma_date(line)
132
+ if fdate: break
133
+ line = IMMA.readline()
134
+ IMMA.close()
135
+
136
+ return fdate
137
+
138
+ #
139
+ # get the latest date filled into the inventory
140
+ #
141
+ def get_max_filled_date(self):
142
+
143
+ pgrec = self.pgget(f"{self.CNTLSC}.inventory", "max(date) mdate", "", self.LGEREX)
144
+
145
+ return self.format_date(pgrec['mdate']) if pgrec and pgrec['mdate'] else None
146
+
147
+ #
148
+ # the iidx values are allocated sequentially, so no date can be filled while an earlier one is
149
+ # still missing; a month must be filled up before the next month is filled, and a month cannot
150
+ # be skipped; months are sequenced by fillmonth, this only guards a direct call
151
+ #
152
+ def check_fill_order(self, fdate):
153
+
154
+ mdate = self.get_max_filled_date()
155
+ if not mdate: return # nothing filled yet
156
+ if self.diffdate(mdate, fdate) >= 0: return # the file holds filled dates already
157
+
158
+ rdate = self.adddate(mdate, 0, 0, 1) # the first missing date
159
+ if self.diffdate(fdate[0:7] + "-01", rdate) <= 0: return # the file covers it
160
+
161
+ self.pglog("{}: filled through {} only, fill from {} before filling {}".format(
162
+ mdate[0:7], mdate, rdate, fdate[0:7]), self.LGEREX)
163
+
164
+ #
165
+ # skip the dates that are filled already if the month of the given file is resumed
166
+ #
167
+ def get_resume_date(self, fname, fdate, bdate):
168
+
169
+ mdate = self.get_max_filled_date()
170
+ if not mdate: return bdate # nothing filled yet
171
+ if self.diffdate(mdate, fdate) < 0: return bdate # none of this file is filled yet
172
+ if self.diffdate(mdate, self.enddate(fdate, 0, 'M')) > 0: return bdate # refilling an earlier month
173
+
174
+ rdate = self.adddate(mdate, 0, 0, 1)
175
+ if bdate and self.diffdate(bdate, rdate) >= 0: return bdate
176
+
177
+ self.pglog("> {}: filled through {} already, continue filling from {}".format(
178
+ op.basename(fname), mdate, rdate), self.LOGWRN)
179
+
180
+ return rdate
181
+
182
+ #
183
+ # log an error, without stopping, if the month of the given file is not filled up, so that
184
+ # a partial fill is reported by the calling process and the month is resumed on a rerun
185
+ #
186
+ def check_month_filled(self, fname, fdate, bdate):
187
+
188
+ edate = self.enddate(fdate, 0, 'M')
189
+ pgrec = self.pgget(f"{self.CNTLSC}.inventory", "max(date) mdate",
190
+ "date BETWEEN '{}-01' AND '{}'".format(fdate[0:7], edate), self.LGEREX)
191
+ mdate = self.format_date(pgrec['mdate']) if pgrec and pgrec['mdate'] else None
192
+ if mdate == edate:
193
+ if bdate: self.pglog("> {}: filled up through {}".format(fdate[0:7], edate), self.LOGWRN)
194
+ return
195
+
196
+ self.pglog("{}: PARTIAL fill of {}, filled through {} of {}; rerun to fill the remaining dates".format(
197
+ op.basename(fname), fdate[0:7], (mdate if mdate else 'nothing'), edate), self.LOGERR)
198
+
120
199
  #
121
200
  # read icoads record from given file name and save them into IVADDB
122
201
  #
@@ -125,19 +204,29 @@ class FillIcoads(PgIMMA):
125
204
  iname = fname if addinventory else None
126
205
  self.pglog("Record IMMA records in File '{}' into IVADDB".format(fname), self.WARNLG)
127
206
 
128
- IMMA = open(fname, 'r', encoding = 'latin_1')
129
207
  acounts = [0]*self.TABLECOUNT
130
208
  records = {}
209
+ bdate = self.PVALS['dates'][0] if self.PVALS['dtlen'] > 0 else None
210
+ edate = self.PVALS['dates'][1] if self.PVALS['dtlen'] > 1 else None
211
+
212
+ fdate = self.get_imma_file_date(fname) if iname else None
213
+ if fdate:
214
+ if self.PVALS['dtlen'] == 0: self.check_fill_order(fdate)
215
+ bdate = self.get_resume_date(fname, fdate, bdate)
216
+
217
+ IMMA = open(fname, 'r', encoding = 'latin_1')
131
218
 
132
219
  # get the first valid date and do initialization
220
+ cdate = None
133
221
  line = IMMA.readline()
134
222
  self.identify_attm_name(line) # check and record standalone attm name
135
223
  while line:
136
- idate = cdate = self.get_imma_date(line)
137
- if cdate and (self.PVALS['dtlen'] == 0 or self.diffdate(cdate, self.PVALS['dates'][0]) >= 0):
138
- self.init_indices_for_date(cdate, iname)
139
- records = self.get_imma_records(cdate, line, records)
140
- break
224
+ if not (self.PVALS['uatti'] and line[0:2] == self.PVALS['uatti']): # a standalone attm line holds no date
225
+ cdate = self.get_imma_date(line)
226
+ if cdate and (not bdate or self.diffdate(cdate, bdate) >= 0):
227
+ self.init_indices_for_date(cdate, iname)
228
+ records = self.get_imma_records(cdate, line, records)
229
+ break
141
230
  line = IMMA.readline()
142
231
 
143
232
  line = IMMA.readline()
@@ -152,7 +241,7 @@ class FillIcoads(PgIMMA):
152
241
  for i in range(self.TABLECOUNT): acounts[i] += acnts[i]
153
242
  records = {}
154
243
  cdate = idate
155
- if self.PVALS['dtlen'] == 2 and self.diffdate(cdate, self.PVALS['dates'][1]) > 0: break
244
+ if edate and self.diffdate(cdate, edate) > 0: break
156
245
  self.init_indices_for_date(cdate, iname)
157
246
  records = self.get_imma_records(idate, line, records)
158
247
  line = IMMA.readline()
@@ -165,6 +254,8 @@ class FillIcoads(PgIMMA):
165
254
 
166
255
  self.pglog("{} ({}) filled from {}".format(' '.join(map(str, acounts)), self.PVALS['names'], op.basename(fname)), self.LOGWRN)
167
256
 
257
+ if fdate and self.PVALS['dtlen'] == 0: self.check_month_filled(fname, fdate, bdate)
258
+
168
259
  return acounts
169
260
 
170
261
  # main function to execute this script
@@ -8,6 +8,7 @@
8
8
  # 2025-03-03 transferred to package rda_python_icoads from
9
9
  # https://github.com/NCAR/rda-icoads.git
10
10
  # 2026-09-04 convert to class FillMonth
11
+ # 2026-09-09 fill each missing month, in order, up to the given month
11
12
  # Purpose : process ICOADS monthly data file in IMMA1 format and fill into IVADDB
12
13
  #
13
14
  # Github : https://github.com/NCAR/rda-python-icoads.git
@@ -18,8 +19,9 @@ import sys
18
19
  import re
19
20
  from os import path as op
20
21
  from rda_python_common.pg_util import PgUtil
22
+ from rda_python_common.pg_dbi import PgDBI
21
23
 
22
- class FillMonth(PgUtil):
24
+ class FillMonth(PgUtil, PgDBI):
23
25
 
24
26
  def __init__(self):
25
27
  super().__init__()
@@ -27,7 +29,8 @@ class FillMonth(PgUtil):
27
29
  'filenames' : ["IMMA1_R3.0.2_", "IMMA1_R3.0.3_"], # IMMA1_R3.0.3_ since 2025-08
28
30
  'fillicoads' : "fillicoads -i ",
29
31
  'fillitable' : "fillitable -t -v dck pt sid -r ",
30
- 'cdmsmonth' : "cdmsmonth "
32
+ 'cdmsmonth' : "cdmsmonth ",
33
+ 'inventory' : "cntldb.inventory"
31
34
  }
32
35
 
33
36
  #
@@ -36,7 +39,7 @@ class FillMonth(PgUtil):
36
39
  def main(self):
37
40
 
38
41
  argv = sys.argv[1:]
39
- smonth = srange = None
42
+ smonth = None
40
43
 
41
44
  for arg in argv:
42
45
  if arg == "-b":
@@ -47,7 +50,6 @@ class FillMonth(PgUtil):
47
50
  ms = re.match(r'^(\d+)-(\d+)', arg)
48
51
  if ms:
49
52
  smonth = "{:04}-{:02}".format(int(ms.group(1)), int(ms.group(2)))
50
- srange = "{}-01 {}".format(smonth, self.enddate(smonth, 0, 'M'))
51
53
  else:
52
54
  self.pglog(arg +": Invalid month format", self.LGWNEX)
53
55
  else:
@@ -56,14 +58,62 @@ class FillMonth(PgUtil):
56
58
  if not smonth:
57
59
  print("Usage: fillmonth ProcessMonth")
58
60
  print(" Provide a month (YYYY-MM), to fill monthly IMMA1 into IVADDB")
61
+ print(" Any earlier month not filled up yet is filled first, in order")
59
62
  sys.exit(0)
60
63
 
61
64
  self.PGLOG['LOGFILE'] = "icoads.log"
65
+ self.ivaddb_dbname()
62
66
  self.cmdlog("fillmonth {}".format(' '.join(argv)))
63
- self.fill_monthly_data(smonth, srange)
67
+ months = self.get_fill_months(smonth)
68
+ if len(months) > 1:
69
+ self.pglog("> {}: fill {} months, {} through {}, in order".format(
70
+ smonth, len(months), months[0][0], months[-1][0]), self.LOGWRN)
71
+ mcnt = len(months)
72
+ for i in range(mcnt):
73
+ (cmonth, bdate, edate) = months[i]
74
+ self.fill_monthly_data(cmonth, "{} {}".format(bdate, edate))
75
+ if self.month_filled(cmonth, edate) or (i + 1) == mcnt: continue
76
+ # a later month cannot be filled while this one is short of dates
77
+ self.pglog("> {}: filled partially, stop before {}".format(cmonth, months[i+1][0]), self.LOGWRN)
78
+ break
64
79
  self.cmdlog()
65
80
  sys.exit(0)
66
81
 
82
+ #
83
+ # the iidx values are allocated sequentially, so every month between the last filled date
84
+ # and the given month must be filled, in order; return a [month, bdate, edate] per month,
85
+ # with bdate on the first month set to resume a partially filled month
86
+ #
87
+ def get_fill_months(self, smonth):
88
+
89
+ edate = self.enddate(smonth, 0, 'M')
90
+ pgrec = self.pgget(self.CMDS['inventory'], "max(date) mdate", "", self.LGEREX)
91
+ mdate = self.format_date(pgrec['mdate']) if pgrec and pgrec['mdate'] else None
92
+ if not mdate: return [[smonth, smonth + "-01", edate]] # nothing filled yet
93
+
94
+ bdate = self.adddate(mdate, 0, 0, 1) # resume the partial month, or start the next one
95
+ if self.diffdate(bdate, edate) > 0: return [[smonth, smonth + "-01", edate]] # refill an earlier month
96
+
97
+ months = []
98
+ while self.diffdate(bdate, edate) <= 0:
99
+ cmonth = bdate[0:7]
100
+ cedate = self.enddate(cmonth, 0, 'M')
101
+ if self.diffdate(cedate, edate) > 0: cedate = edate
102
+ months.append([cmonth, bdate, cedate])
103
+ bdate = self.adddate(cedate, 0, 0, 1)
104
+
105
+ return months
106
+
107
+ #
108
+ # check if the given month is filled through its last date
109
+ #
110
+ def month_filled(self, cmonth, edate):
111
+
112
+ pgrec = self.pgget(self.CMDS['inventory'], "max(date) mdate",
113
+ "date BETWEEN '{}-01' AND '{}'".format(cmonth, edate), self.LGEREX)
114
+
115
+ return (pgrec and pgrec['mdate'] and self.format_date(pgrec['mdate']) == edate)
116
+
67
117
  #
68
118
  # locate the monthly IMMA1 file, gunzip it if only the gzipped one is on file
69
119
  #
@@ -375,7 +375,7 @@ class PgIMMA(PgOPT):
375
375
  self.UIDOFFSET = 4
376
376
  self.UIDLENGTH = 15
377
377
  atti = line[15:17]
378
- CURRN3 = int(line[12])
378
+ self.CURRN3 = int(line[12])
379
379
  else:
380
380
  atti = None
381
381
 
@@ -622,8 +622,28 @@ class PgIMMA(PgOPT):
622
622
  #
623
623
  # add multiple imma records into different tables in RDADB
624
624
  #
625
+ # all writes for one date are wrapped in a single transaction, so an interrupted
626
+ # fill can never leave an inventory record behind without its data; the date is
627
+ # then simply refilled when the file is processed again
628
+ #
625
629
  def add_imma_records(self, cdate, records):
626
630
 
631
+ self.starttran()
632
+ try:
633
+ acnts = self.add_date_records(cdate, records)
634
+ except BaseException:
635
+ self.aborttran()
636
+ self.pglog("{}: records rolled back for the date".format(cdate), self.LOGWRN)
637
+ raise
638
+ self.endtran()
639
+
640
+ return acnts
641
+
642
+ #
643
+ # add all records of one date into the attm and the control tables
644
+ #
645
+ def add_date_records(self, cdate, records):
646
+
627
647
  if self.INVENTORY and self.IMMA_NAMES[0] in records: # add counting record into inventory table
628
648
  rcnt = len(records[self.IMMA_NAMES[0]]['iidx'])
629
649
  if rcnt > 0: self.INVENTORY = self.add_inventory_record(self.INVENTORY['fname'], cdate, rcnt, self.INVENTORY)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_icoads
3
- Version: 3.0.2
3
+ Version: 3.0.4
4
4
  Summary: RDA python package to manage RDA ICOADS datasets
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-icoads