afscgap.inference
Tools for inferring missing, negative, or zero catch records.
(c) 2023 Regents of University of California / The Eric and Wendy Schmidt Center for Data Science and the Environment at UC Berkeley.
This file is part of afscgap released under the BSD 3-Clause License. See LICENSE.md.
1""" 2Tools for inferring missing, negative, or zero catch records. 3 4(c) 2023 Regents of University of California / The Eric and Wendy Schmidt Center 5for Data Science and the Environment at UC Berkeley. 6 7This file is part of afscgap released under the BSD 3-Clause License. See 8LICENSE.md. 9""" 10import copy 11import csv 12import io 13import itertools 14import queue 15import typing 16 17import afscgap.convert 18import afscgap.client 19import afscgap.cursor 20import afscgap.http_util 21import afscgap.model 22import afscgap.query_util 23 24from afscgap.typesdef import OPT_FLOAT 25from afscgap.typesdef import OPT_INT 26from afscgap.typesdef import OPT_REQUESTOR 27from afscgap.typesdef import OPT_STR 28 29DEFAULT_HAULS_URL = 'https://pyafscgap.org/community/hauls.csv' 30SPECIES_DICT = typing.Dict[str, afscgap.model.SpeciesRecord] 31 32HAUL_LIST = typing.List[afscgap.model.Haul] 33OPT_HAUL_LIST = typing.Optional[HAUL_LIST] 34HAUL_FILTERABLE_FIELDS = [ 35 'year', 36 'srvy', 37 'survey', 38 'survey_id', 39 'cruise', 40 'haul', 41 'stratum', 42 'station', 43 'vessel_name', 44 'vessel_id', 45 'date_time', 46 'latitude_dd', 47 'longitude_dd', 48 'bottom_temperature_c', 49 'surface_temperature_c', 50 'depth_m', 51 'distance_fished_km', 52 'net_width_m', 53 'net_height_m', 54 'area_swept_ha', 55 'duration_hr' 56] 57 58PARAMS_CHECKER = typing.Callable[[afscgap.model.Haul], bool] 59 60 61def build_inference_cursor(params: dict, inner_cursor: afscgap.cursor.Cursor, 62 requestor: OPT_REQUESTOR = None, hauls_url: afscgap.client.OPT_STR = None, 63 hauls_prefetch: OPT_HAUL_LIST = None): 64 """Build a cursor which infers zero catch records. 65 66 Args: 67 params: Dictionary of filters to apply to the query where a value of 68 None means no filter should be applied on that field. 69 inner_cursor: Cursor which yields records which, when appearing, should 70 not be later inferred as zero catch records. 71 requestor: Strategy to make HTTP GET requests. If None, will default 72 to requests.get. 73 hauls_url: The URL at which the Hauls file can be found or None to use 74 a default. Defaults to None. 75 hauls_prefetch: List of hauls data to use. If None, will request from 76 hauls_url. If not None, will use this instead. 77 78 Returns: 79 Cursor which 1) first iterates over the inner_cursor and then 80 2) provides inferred zero catch records (for any hauls without observed 81 data from inner_cursor for a species). 82 """ 83 params_safe = copy.deepcopy(params) 84 85 if 'date_time' in params_safe: 86 params_safe['date_time'] = afscgap.convert.convert_from_iso8601( 87 params_safe['date_time'] 88 ) 89 90 if hauls_prefetch is not None: 91 hauls_data = hauls_prefetch 92 else: 93 hauls_data = get_hauls_data( 94 params_safe, 95 requestor=requestor, 96 hauls_url=hauls_url 97 ) 98 99 return NegativeInferenceCursorDecorator(inner_cursor, hauls_data) 100 101 102def build_params_checker(params: dict) -> PARAMS_CHECKER: 103 """Build a function that checks if a single Haul record should be filtered. 104 105 Args: 106 params: Dictionary of filters to apply to the query where a value of 107 None means no filter should be applied on that field. 108 109 Returns: 110 Function which returns true if the record given to it should be included 111 (is not filtered out) or false if it should be excluded from results to 112 meet a query requirement (is filtered out). 113 """ 114 115 def build_query_function(key: str, checker): 116 return lambda target: checker(target[key]) 117 118 params_py = afscgap.query_util.interpret_query_to_py(params) 119 params_py_items = params_py.items() 120 params_py_items_given = filter(lambda x: x[1] is not None, params_py_items) 121 params_py_items_valid = filter( 122 lambda x: x[0] in HAUL_FILTERABLE_FIELDS, 123 params_py_items_given 124 ) 125 params_funcs = map( 126 lambda x: build_query_function(x[0], x[1]), 127 params_py_items_valid 128 ) 129 params_funcs_realized = list(params_funcs) 130 131 def check_all(target: afscgap.model.Haul) -> bool: 132 target_dict = target.to_dict() 133 not_allowed = filter( 134 lambda x: not x(target_dict), 135 params_funcs_realized 136 ) 137 num_not_allowed = sum(map(lambda x: 1, not_allowed)) 138 return num_not_allowed == 0 139 140 return check_all 141 142 143def get_hauls_data(params: dict, requestor: OPT_REQUESTOR = None, 144 hauls_url: afscgap.client.OPT_STR = None) -> HAUL_LIST: 145 """Download Hauls from a URL and apply a filter specified by params. 146 147 Args: 148 params: Dictionary of filters to apply to the query where a value of 149 None means no filter should be applied on that field. 150 requestor: Strategy to make HTTP GET requests. If None, will default 151 to requests.get. 152 hauls_url: The URL at which the Hauls file can be found or None to use 153 a default. Defaults to None. 154 155 Returns: 156 List of Haul data after having applied the filters described by params. 157 """ 158 if hauls_url is None: 159 hauls_url = DEFAULT_HAULS_URL 160 161 params_checker = build_params_checker(params) 162 163 if requestor is None: 164 requestor = afscgap.http_util.build_requestor() 165 166 response = requestor(hauls_url) 167 afscgap.http_util.check_result(response) 168 169 response.encoding = 'utf-8' 170 response_io = io.StringIO(response.text, newline='') 171 172 response_rows = csv.DictReader(response_io) 173 response_hauls = map(parse_haul, response_rows) 174 response_hauls_filtered = filter(params_checker, response_hauls) 175 176 return list(response_hauls_filtered) 177 178 179class NegativeInferenceCursorDecorator(afscgap.cursor.Cursor): 180 """Cursor augmenting another cursor with inferred negative records. 181 182 Cursor which exausts an inner cursor and then supplies inferred zero catch 183 records. Specifically, a Cursor which 1) first iterates over the 184 inner_cursor and then 2) provides inferred zero catch records (for any hauls 185 without observed data from inner_cursor for a species). 186 """ 187 188 def __init__(self, inner_cursor: afscgap.cursor.Cursor, 189 hauls_data: HAUL_LIST): 190 """Decorate a cursor. 191 192 Args: 193 inner_cursor: The cursor to augment and from which to observe 194 presence data. 195 hauls_data: Metadata on all hauls relevant to the query. 196 """ 197 self._inner_cursor = inner_cursor 198 self._hauls_data = hauls_data 199 200 self._started_inference = False 201 self._inferences_iter: typing.Iterator[afscgap.model.Record] = iter([]) 202 203 self._species_seen: SPECIES_DICT = dict() 204 self._species_hauls_seen: typing.Set[str] = set() 205 self._ak_survey_ids: typing.Dict[str, int] = dict() 206 207 def get_base_url(self) -> str: 208 """Get the URL at which the first page of query results can be found. 209 210 Returns: 211 The URL for the query without pagination information. 212 """ 213 return self._inner_cursor.get_base_url() 214 215 def get_limit(self) -> OPT_INT: 216 """Get the page size limit. 217 218 Returns: 219 The maximum number of records to return per page. 220 """ 221 return self._inner_cursor.get_limit() 222 223 def get_start_offset(self) -> OPT_INT: 224 """Get the number of inital records to ignore. 225 226 Returns: 227 The number of records being skipped at the start of the result set. 228 """ 229 return self._inner_cursor.get_start_offset() 230 231 def get_filtering_incomplete(self) -> bool: 232 """Determine if this cursor is silently filtering incomplete records. 233 234 Returns: 235 Flag indicating if incomplete records should be silently filtered. 236 If true, they will not be returned during iteration and placed in 237 the queue at get_invalid(). If false, they will be returned and 238 those incomplete records' get_complete() will return false. 239 """ 240 return self._inner_cursor.get_filtering_incomplete() 241 242 def get_page_url(self, offset: OPT_INT = None, 243 limit: OPT_INT = None) -> str: 244 """Get a URL at which a page can be found using this cursor's base url. 245 246 Args: 247 offset: The number of records to skip prior to the page. 248 limit: The maximum number of records to return in the page. 249 Returns: 250 URL at which the requested page can be found. 251 """ 252 return self._inner_cursor.get_page_url(offset=offset, limit=limit) 253 254 def get_page(self, offset: OPT_INT = None, 255 limit: OPT_INT = None, 256 ignore_invalid: bool = False) -> typing.List[afscgap.model.Record]: 257 """Get a page using this cursor's base url. 258 259 Args: 260 offset: The number of records to skip prior to the page. 261 limit: The maximum number of records to return in the page. 262 ignore_invalid: Flag indicating how to handle invalid records. If 263 true, will silently throw away records which could not be 264 parsed. If false, will raise an exception if a record can not 265 be parsed. 266 267 Returns: 268 Results from the page which, regardless of ignore_invalid, may 269 contain a mixture of complete and incomplete records. 270 """ 271 return self._inner_cursor.get_page( 272 offset=offset, 273 limit=limit, 274 ignore_invalid=ignore_invalid 275 ) 276 277 def get_invalid(self) -> 'queue.Queue[dict]': 278 """Get a queue of invalid / incomplete records found so far. 279 280 Returns: 281 Queue with dictionaries containing the raw data returned from the 282 API that did not have valid values for all required fields. Note 283 that this will include incomplete records as well if 284 get_filtering_incomplete() is true and will not contain incomplete 285 records otherwise. 286 """ 287 return self._inner_cursor.get_invalid() 288 289 def to_dicts(self) -> typing.Iterable[dict]: 290 """Create an iterator which converts Records to dicts. 291 292 Returns: 293 Iterator which returns dictionaries instead of Record objects but 294 has otherwise the same beahavior as iterating in this Cursor 295 directly. 296 """ 297 return self._inner_cursor.to_dicts() 298 299 def get_next(self) -> typing.Optional[afscgap.model.Record]: 300 """Get the next value for this Cursor. 301 302 Returns: 303 The next value waiting if cached in the cursor's results queue or 304 as just retrieved from a new page gathered by HTTP request. Will 305 return None if no remain. 306 """ 307 if self._started_inference: 308 return self._get_next_inferred() 309 else: 310 next_record_maybe = self._inner_cursor.get_next() 311 312 if next_record_maybe: 313 self._record_record_meta(next_record_maybe) 314 return next_record_maybe 315 else: 316 self._start_inference() 317 return self._get_next_inferred() 318 319 def _record_record_meta(self, record: afscgap.model.Record): 320 """Record metadata from a record, indicating a haul / species was seen. 321 322 Args: 323 record: The record observed. 324 """ 325 key_with_species = self._get_haul_key( 326 record, 327 species=record.get_scientific_name() 328 ) 329 self._species_hauls_seen.add(key_with_species) 330 331 scientific_name = record.get_scientific_name() 332 common_name = record.get_common_name() 333 species_code = record.get_species_code() 334 tsn = record.get_tsn_maybe() 335 336 self._species_seen[scientific_name] = afscgap.model.SpeciesRecord( 337 scientific_name, 338 common_name, 339 species_code, 340 tsn 341 ) 342 343 survey = record.get_survey() 344 ak_survey_id = record.get_ak_survey_id() 345 346 self._ak_survey_ids[survey] = ak_survey_id 347 348 def _get_haul_key(self, record: afscgap.model.HaulKeyable, 349 species: OPT_STR = None) -> str: 350 """Get a string uniquely identifying an individual haul. 351 352 Args: 353 record: The record from which to derive a haul key. 354 speices: If given, include the species in the key. If not given, the 355 key will refer to the entire haul across all species. Note that 356 this should be the scientific name for a species. 357 358 Returns: 359 String uniquely identifying a haul across the entire dataset. 360 """ 361 ship_info_vals = [ 362 record.get_year(), 363 record.get_vessel_id(), 364 record.get_cruise(), 365 record.get_haul() 366 ] 367 ship_info_vals_int = map(lambda x: round(x), ship_info_vals) 368 ship_info_vals_str = map(str, ship_info_vals_int) 369 ship_info_vals_csv = ','.join(ship_info_vals_str) 370 371 without_species = '%s:%s' % (record.get_srvy(), ship_info_vals_csv) 372 373 if species: 374 return '%s/%s' % (without_species, species) 375 else: 376 return without_species 377 378 def _start_inference(self): 379 """Prepare to start inferrence. 380 381 Indicate that the inner cursor is exhaused, preparing to run inferrence. 382 """ 383 hauls_seen_with_key = map( 384 lambda x: (self._get_haul_key(x), x), 385 self._hauls_data 386 ) 387 hauls_seen_by_key = dict(hauls_seen_with_key) 388 389 scientific_names_seen = self._species_seen.keys() 390 missing_keys = self._get_missing_keys( 391 hauls_seen_by_key.keys(), 392 scientific_names_seen, 393 self._species_hauls_seen 394 ) 395 396 missing_haul_keys_and_species_tuple = map( 397 lambda x: x.split('/'), 398 missing_keys 399 ) 400 missing_haul_keys_and_species = map( 401 lambda x: {'haulKey': x[0], 'species': x[1]}, 402 missing_haul_keys_and_species_tuple 403 ) 404 missing_hauls_and_species = map( 405 lambda x: { 406 'haul': hauls_seen_by_key[x['haulKey']], 407 'species': x['species'] 408 }, 409 missing_haul_keys_and_species 410 ) 411 412 def make_inference_record(target: typing.Dict) -> afscgap.model.Record: 413 scientific_name = target['species'] 414 haul = target['haul'] 415 416 species_record = self._species_seen[scientific_name] 417 common_name = species_record.get_common_name() 418 species_code = species_record.get_species_code() 419 tsn = species_record.get_tsn() 420 421 ak_survey_id = self._ak_survey_ids.get(haul.get_survey(), None) 422 423 return ZeroCatchHaulDecorator( 424 haul, 425 scientific_name, 426 common_name, 427 species_code, 428 tsn, 429 ak_survey_id 430 ) 431 432 inference_map = map(make_inference_record, missing_hauls_and_species) 433 434 self._inferences_iter = iter(inference_map) 435 self._started_inference = True 436 437 def _get_next_inferred(self) -> typing.Optional[afscgap.model.Record]: 438 """Get the next inferred zero catch record. 439 440 Raises: 441 StopIteration: Raised if no records left to infer. 442 AssertionError: Raised if the cursor has not yet started inference. 443 444 Returns: 445 Next inferred absence data record. 446 """ 447 assert self._started_inference 448 449 try: 450 return next(self._inferences_iter) 451 except StopIteration: 452 return None 453 454 def _get_missing_keys(self, hauls_seen: typing.Iterable[str], 455 scientific_names_seen: typing.Iterable[str], 456 species_hauls_seen: typing.Set[str]) -> typing.Iterable[str]: 457 """Determine which species haul keys were expected but not observed. 458 459 Args: 460 hauls_seen: The haus seen (non-species keys). 461 scientific_names_seen: The name of the scientific names for species 462 observed across the entire dataset yielded by the user query. 463 species_hauls_seen: The haul / species keys or combinations actually 464 observed. 465 466 Returns: 467 Haul / species keys expected but not found in species_hauls_seen 468 given the hauls described in hauls_seen and the species seen in 469 scientific_names_seen. 470 """ 471 hauls_with_names = itertools.product( 472 hauls_seen, 473 scientific_names_seen 474 ) 475 hauls_with_names_str = map(lambda x: '%s/%s' % x, hauls_with_names) 476 missing_keys = filter( 477 lambda x: x not in species_hauls_seen, 478 hauls_with_names_str 479 ) 480 return missing_keys 481 482 483class ZeroCatchHaulDecorator(afscgap.model.Record): 484 """Decorator for a Haul that makes it operate like a zero catch Record.""" 485 486 def __init__(self, haul: afscgap.model.Haul, scientific_name: str, 487 common_name: str, species_code: float, tsn: OPT_INT, 488 ak_survey_id: OPT_INT): 489 """Decorate a Haul to conform to the Record interface. 490 491 Args: 492 haul: The haul to decorate. 493 scientific_name: The scientific name of the species to be associated 494 with this record. 495 common_name: The common name of the species to be associated with 496 this record. 497 species_code: The species code of the species to be associated with 498 this record. 499 tsn: The taxonomic information system species code to be associated 500 with this record if known. 501 ak_survey_id: The AK survey ID to be associated with this record if 502 known. 503 """ 504 self._haul = haul 505 self._scientific_name = scientific_name 506 self._common_name = common_name 507 self._species_code = species_code 508 self._tsn = tsn 509 self._ak_survey_id = ak_survey_id 510 511 def get_year(self) -> float: 512 """Get the year of the start date for the haul. 513 514 Returns: 515 Year for the haul. 516 """ 517 return self._haul.get_year() 518 519 def get_srvy(self) -> str: 520 """Get the field labeled as srvy in the API. 521 522 Returns: 523 The name of the survey in which this haul was conducted. NBS (N 524 Bearing Sea), EBS (SE Bearing Sea), BSS (Bearing Sea Slope), or GOA 525 (Gulf of Alaska) 526 """ 527 return self._haul.get_srvy() 528 529 def get_survey(self) -> str: 530 """Get the field labeled as survey in the API. 531 532 Returns: 533 Long form description of the survey in which the haul was conducted. 534 """ 535 return self._haul.get_survey() 536 537 def get_survey_id(self) -> float: 538 """Get the field labeled as survey_id in the API. 539 540 Returns: 541 Unique numeric ID for the survey. 542 """ 543 return self._haul.get_survey_id() 544 545 def get_cruise(self) -> float: 546 """Get the field labeled as cruise in the API. 547 548 Returns: 549 An ID uniquely identifying the cruise in which the haul was made. 550 Multiple cruises in a survey. 551 """ 552 return self._haul.get_cruise() 553 554 def get_haul(self) -> float: 555 """Get the field labeled as haul in the API. 556 557 Returns: 558 An ID uniquely identifying the haul. Multiple hauls per cruises. 559 """ 560 return self._haul.get_haul() 561 562 def get_stratum(self) -> float: 563 """Get the field labeled as stratum in the API. 564 565 Returns: 566 Unique ID for statistical area / survey combination as described in 567 the metadata or 0 if an experimental tow. 568 """ 569 return self._haul.get_stratum() 570 571 def get_station(self) -> str: 572 """Get the field labeled as station in the API. 573 574 Returns: 575 Station associated with the survey. 576 """ 577 return self._haul.get_station() 578 579 def get_vessel_name(self) -> str: 580 """Get the field labeled as vessel_name in the API. 581 582 Returns: 583 Unique ID describing the vessel that made this haul. Note this is 584 left as a string but, in practice, is likely numeric. 585 """ 586 return self._haul.get_vessel_name() 587 588 def get_vessel_id(self) -> float: 589 """Get the field labeled as vessel_id in the API. 590 591 Returns: 592 Name of the vessel at the time the haul was made. Note that there 593 may be multiple names potentially associated with a vessel ID. 594 """ 595 return self._haul.get_vessel_id() 596 597 def get_date_time(self) -> str: 598 """Get the field labeled as date_time in the API. 599 600 Returns: 601 The date and time of the haul which has been attempted to be 602 transformed to an ISO 8601 string without timezone info. If it 603 couldn’t be transformed, the original string is reported. 604 """ 605 return self._haul.get_date_time() 606 607 def get_latitude(self, units: str = 'dd') -> float: 608 """Get the field labeled as latitude_dd in the API. 609 610 Args: 611 units: The units to return this value in. Only supported is dd for 612 degrees. Deafults to dd. 613 614 Returns: 615 Latitude in decimal degrees associated with the haul. 616 """ 617 return afscgap.model.assert_float_present( 618 afscgap.convert.convert_degrees( 619 self._haul.get_latitude_dd(), 620 units 621 ) 622 ) 623 624 def get_longitude(self, units: str = 'dd') -> float: 625 """Get the field labeled as longitude_dd in the API. 626 627 Args: 628 units: The units to return this value in. Only supported is dd for 629 degrees. Deafults to dd. 630 631 Returns: 632 Longitude in decimal degrees associated with the haul. 633 """ 634 return afscgap.model.assert_float_present( 635 afscgap.convert.convert_degrees( 636 self._haul.get_longitude_dd(), 637 units 638 ) 639 ) 640 641 def get_species_code(self) -> float: 642 """Get the field labeled as species_code in the API. 643 644 Returns: 645 Unique ID associated with the species observed. 646 """ 647 return self._species_code 648 649 def get_common_name(self) -> str: 650 """Get the field labeled as common_name in the API. 651 652 Returns: 653 The “common name” associated with the species observed. Example: 654 Pacific glass shrimp. 655 """ 656 return self._common_name 657 658 def get_scientific_name(self) -> str: 659 """Get the field labeled as scientific_name in the API. 660 661 Returns: 662 The “scientific name” associated with the species observed. Example: 663 Pasiphaea pacifica. 664 """ 665 return self._scientific_name 666 667 def get_taxon_confidence(self) -> str: 668 """Get rating of taxon identification confidence. 669 670 Returns: 671 Always returns Unassessed. 672 """ 673 return 'Unassessed' 674 675 def get_cpue_weight_maybe(self, units: str = 'kg/ha') -> OPT_FLOAT: 676 """Get a field labeled as cpue_* in the API. 677 678 Args: 679 units: The desired units for the catch per unit effort. Options: 680 kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha. 681 682 Returns: 683 Catch weight divided by net area (in given units) if available. See 684 metadata. None if could not interpret as a float. If an inferred 685 zero catch record, will be zero. 686 """ 687 return 0 688 689 def get_cpue_count_maybe(self, units: str = 'kg/ha') -> OPT_FLOAT: 690 """Get the field labeled as cpue_* in the API. 691 692 Get the catch per unit effort from the record with one of the following 693 units: kg/ha, kg/km2, kg1000/km2. 694 695 Args: 696 units: The desired units for the catch per unit effort. Options: 697 count/ha, count/km2, and count1000/km2. Defaults to count/ha. 698 699 Returns: 700 Catch weight divided by net area (in given units) if available. See 701 metadata. None if could not interpret as a float. If an inferred 702 zero catch record, will be zero. 703 """ 704 return 0 705 706 def get_weight_maybe(self, units='kg') -> OPT_FLOAT: 707 """Get the field labeled as weight_kg in the API. 708 709 Args: 710 units: The units in which the weight should be returned. Options are 711 g, kg for grams and kilograms respectively. Deafults to kg. 712 713 Returns: 714 Taxon weight if available. See metadata. None if could not 715 interpret as a float. If an inferred zero catch record, will be 716 zero. 717 """ 718 return 0 719 720 def get_count_maybe(self) -> OPT_FLOAT: 721 """Get total number of organism individuals in haul. 722 723 Returns: 724 Always returns 0. 725 """ 726 return 0 727 728 def get_bottom_temperature_maybe(self, units: str = 'c') -> OPT_FLOAT: 729 """Get the field labeled as bottom_temperature_c in the API. 730 731 Args: 732 units: The units in which the temperature should be returned. 733 Options: c or f for Celcius and Fahrenheit respectively. 734 Defaults to c. 735 736 Returns: 737 Bottom temperature associated with observation / inferrence if 738 available in desired units. None if not given or could not interpret 739 as a float. 740 """ 741 return afscgap.convert.convert_temperature( 742 self._haul.get_bottom_temperature_c_maybe(), 743 units 744 ) 745 746 def get_surface_temperature_maybe(self, units: str = 'c') -> OPT_FLOAT: 747 """Get the field labeled as surface_temperature_c in the API. 748 749 Args: 750 units: The units in which the temperature should be returned. 751 Options: c or f for Celcius and Fahrenheit respectively. 752 Defaults to c. 753 754 Returns: 755 Surface temperature associated with observation / inferrence if 756 available. None if not given or could not interpret as a float. 757 """ 758 return afscgap.convert.convert_temperature( 759 self._haul.get_surface_temperature_c_maybe(), 760 units 761 ) 762 763 def get_depth(self, units: str = 'm') -> float: 764 """Get the field labeled as depth_m in the API. 765 766 Args: 767 units: The units in which the distance should be returned. Options: 768 m or km for meters and kilometers respectively. Defaults to m. 769 770 Returns: 771 Depth of the bottom. 772 """ 773 return afscgap.model.assert_float_present( 774 afscgap.convert.convert_distance(self._haul.get_depth_m(), units) 775 ) 776 777 def get_distance_fished(self, units: str = 'm') -> float: 778 """Get the field labeled as distance_fished_km in the API. 779 780 Args: 781 units: The units in which the distance should be returned. Options: 782 m or km for meters and kilometers respectively. Defaults to km. 783 784 Returns: 785 Distance of the net fished. 786 """ 787 return afscgap.model.assert_float_present( 788 afscgap.convert.convert_distance( 789 self._haul.get_distance_fished_km() * 1000, 790 units 791 ) 792 ) 793 794 def get_net_width_maybe(self, units: str = 'm') -> OPT_FLOAT: 795 """Get the field labeled as net_width_m in the API. 796 797 Args: 798 units: The units in which the distance should be returned. Options: 799 m or km for meters and kilometers respectively. Defaults to m. 800 801 Returns: 802 Distance of the net fished or None if not given. 803 """ 804 return afscgap.convert.convert_distance( 805 self._haul.get_net_width_m_maybe(), 806 units 807 ) 808 809 def get_net_height_maybe(self, units: str = 'm') -> OPT_FLOAT: 810 """Get the field labeled as net_height_m in the API. 811 812 Args: 813 units: The units in which the distance should be returned. Options: 814 m or km for meters and kilometers respectively. Defaults to m. 815 816 Returns: 817 Height of the net fished or None if not given. 818 """ 819 return afscgap.convert.convert_distance( 820 self._haul.get_net_height_m_maybe(), 821 units 822 ) 823 824 def get_net_width(self, units: str = 'm') -> float: 825 """Get the field labeled as net_width_m in the API. 826 827 Args: 828 units: The units in which the distance should be returned. Options: 829 m or km for meters and kilometers respectively. Defaults to m. 830 831 Returns: 832 Distance of the net fished after asserting it is given. 833 """ 834 return afscgap.model.assert_float_present( 835 self.get_net_width_maybe(units=units) 836 ) 837 838 def get_net_height(self, units: str = 'm') -> float: 839 """Get the field labeled as net_height_m in the API. 840 841 Args: 842 units: The units in which the distance should be returned. Options: 843 m or km for meters and kilometers respectively. Defaults to m. 844 845 Returns: 846 Height of the net fished after asserting it is given. 847 """ 848 return afscgap.model.assert_float_present( 849 self.get_net_height_maybe(units=units) 850 ) 851 852 def get_area_swept(self, units: str = 'ha') -> float: 853 """Get the field labeled as area_swept_ha in the API. 854 855 Args: 856 units: The units in which the area should be returned. Options: 857 ha, m2, km2. Defaults to ha. 858 859 Returns: 860 Area covered by the net while fishing in desired units. 861 """ 862 return afscgap.model.assert_float_present( 863 afscgap.convert.convert_area( 864 self._haul.get_area_swept_ha(), 865 units 866 ) 867 ) 868 869 def get_duration(self, units: str = 'hr') -> float: 870 """Get the field labeled as duration_hr in the API. 871 872 Args: 873 units: The units in which the duration should be returned. Options: 874 day, hr, min. Defaults to hr. 875 876 Returns: 877 Duration of the haul. 878 """ 879 return afscgap.model.assert_float_present( 880 afscgap.convert.convert_time(self._haul.get_duration_hr(), units) 881 ) 882 883 def get_tsn(self) -> int: 884 """Get taxonomic information system species code. 885 886 Returns: 887 TSN for species. 888 """ 889 return afscgap.model.assert_int_present(self._tsn) 890 891 def get_tsn_maybe(self) -> OPT_INT: 892 """Get taxonomic information system species code. 893 894 Returns: 895 TSN for species. 896 """ 897 return self._tsn 898 899 def get_ak_survey_id(self) -> int: 900 """Get the field labeled as ak_survey_id in the API. 901 902 Returns: 903 AK survey ID if found. 904 """ 905 return afscgap.model.assert_int_present(self._ak_survey_id) 906 907 def get_ak_survey_id_maybe(self) -> OPT_INT: 908 """Get the field labeled as ak_survey_id in the API. 909 910 Returns: 911 AK identifier for the survey or None if not given. 912 """ 913 return self._ak_survey_id 914 915 def get_cpue_weight(self, units: str = 'kg/ha') -> float: 916 """Get the value of field cpue_kgha with validity assert. 917 918 Args: 919 units: The desired units for the catch per unit effort. Options: 920 kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha. 921 922 Raises: 923 AssertionError: Raised if this field was not given by the API or 924 could not be parsed as expected. 925 926 Returns: 927 Catch weight divided by net area (kg / hectares) if available. See 928 metadata. Always returns 0. 929 """ 930 return 0 931 932 def get_cpue_count(self, units: str = 'count/ha') -> float: 933 """Get the value of field cpue_noha with validity assert. 934 935 Args: 936 units: The desired units for the catch per unit effort. Options: 937 count/ha, count/km2, and count1000/km2. Defaults to count/ha. 938 939 Raises: 940 AssertionError: Raised if this field was not given by the API or 941 could not be parsed as expected. 942 943 Returns: 944 Catch number divided by net sweep area if available (count / 945 hectares). See metadata. Always returns 0. 946 """ 947 return 0 948 949 def get_weight(self, units: str = 'kg') -> float: 950 """Get the value of field weight_kg with validity assert. 951 952 Args: 953 units: The units in which the weight should be returned. Options are 954 g, kg for grams and kilograms respectively. Deafults to kg. 955 956 Raises: 957 AssertionError: Raised if this field was not given by the API or 958 could not be parsed as expected. 959 960 Returns: 961 Taxon weight (kg) if available. See metadata. Always returns 0. 962 """ 963 return 0 964 965 def get_count(self) -> float: 966 """Get the value of field count with validity assert. 967 968 Raises: 969 AssertionError: Raised if this field was not given by the API or 970 could not be parsed as expected. 971 972 Returns: 973 Always returns 0 974 """ 975 return 0 976 977 def get_bottom_temperature(self, units='c') -> float: 978 """Get the value of field bottom_temperature_c with validity assert. 979 980 Args: 981 units: The units in which the temperature should be returned. 982 Options: c or f for Celcius and Fahrenheit respectively. 983 Defaults to c. 984 985 Raises: 986 AssertionError: Raised if this field was not given by the API or 987 could not be parsed as expected. 988 989 Returns: 990 Bottom temperature associated with observation / inferrence if 991 available. 992 """ 993 return afscgap.model.assert_float_present( 994 self.get_bottom_temperature_maybe(units=units) 995 ) 996 997 def get_surface_temperature(self, units='c') -> float: 998 """Get the value of field surface_temperature_c with validity assert. 999 1000 Args: 1001 units: The units in which the temperature should be returned. 1002 Options: c or f for Celcius and Fahrenheit respectively. 1003 Defaults to c. 1004 1005 Raises: 1006 AssertionError: Raised if this field was not given by the API or 1007 could not be parsed as expected. 1008 1009 Returns: 1010 Surface temperature associated with observation / inferrence if 1011 available. 1012 """ 1013 return afscgap.model.assert_float_present( 1014 self.get_surface_temperature_maybe(units=units) 1015 ) 1016 1017 def is_complete(self) -> bool: 1018 """Determine if this record has all of its values filled in. 1019 1020 Returns: 1021 True if all optional fields have a parsed value with the expected 1022 type and false otherwise. 1023 """ 1024 tsn_given = self._tsn is not None 1025 ak_survey_id_given = self._ak_survey_id is not None 1026 return tsn_given and ak_survey_id_given and self._haul.is_complete() 1027 1028 1029def parse_haul(target: dict) -> afscgap.model.Haul: 1030 """Parse a Haul record from a row in the community Hauls flat file. 1031 1032 Args: 1033 target: Dict describing a single row from the community-maintained 1034 Hauls flat file. 1035 1036 Returns: 1037 Haul record constructed from the input row. 1038 """ 1039 srvy = str(target['Srvy']) 1040 survey = str(target['Survey']) 1041 survey_id = float(target['Survey Id']) 1042 cruise = float(target['Cruise']) 1043 haul = float(target['Haul']) 1044 stratum = float(target['Stratum']) 1045 station = str(target['Station']) 1046 vessel_name = str(target['Vessel Name']) 1047 vessel_id = float(target['Vessel Id']) 1048 date_time = str(afscgap.convert.convert_to_iso8601(target['Date Time'])) 1049 latitude_dd = float(target['Latitude Dd']) 1050 longitude_dd = float(target['Longitude Dd']) 1051 bottom_temperature_c = afscgap.model.get_opt_float( 1052 target['Bottom Temperature C'] 1053 ) 1054 surface_temperature_c = afscgap.model.get_opt_float( 1055 target['Surface Temperature C'] 1056 ) 1057 depth_m = float(target['Depth M']) 1058 distance_fished_km = float(target['Distance Fished Km']) 1059 net_width_m = afscgap.model.get_opt_float(target['Net Width M']) 1060 net_height_m = afscgap.model.get_opt_float(target['Net Height M']) 1061 area_swept_ha = float(target['Area Swept Ha']) 1062 duration_hr = float(target['Duration Hr']) 1063 1064 return afscgap.model.Haul( 1065 srvy, 1066 survey, 1067 survey_id, 1068 cruise, 1069 haul, 1070 stratum, 1071 station, 1072 vessel_name, 1073 vessel_id, 1074 date_time, 1075 latitude_dd, 1076 longitude_dd, 1077 bottom_temperature_c, 1078 surface_temperature_c, 1079 depth_m, 1080 distance_fished_km, 1081 net_width_m, 1082 net_height_m, 1083 area_swept_ha, 1084 duration_hr 1085 )
62def build_inference_cursor(params: dict, inner_cursor: afscgap.cursor.Cursor, 63 requestor: OPT_REQUESTOR = None, hauls_url: afscgap.client.OPT_STR = None, 64 hauls_prefetch: OPT_HAUL_LIST = None): 65 """Build a cursor which infers zero catch records. 66 67 Args: 68 params: Dictionary of filters to apply to the query where a value of 69 None means no filter should be applied on that field. 70 inner_cursor: Cursor which yields records which, when appearing, should 71 not be later inferred as zero catch records. 72 requestor: Strategy to make HTTP GET requests. If None, will default 73 to requests.get. 74 hauls_url: The URL at which the Hauls file can be found or None to use 75 a default. Defaults to None. 76 hauls_prefetch: List of hauls data to use. If None, will request from 77 hauls_url. If not None, will use this instead. 78 79 Returns: 80 Cursor which 1) first iterates over the inner_cursor and then 81 2) provides inferred zero catch records (for any hauls without observed 82 data from inner_cursor for a species). 83 """ 84 params_safe = copy.deepcopy(params) 85 86 if 'date_time' in params_safe: 87 params_safe['date_time'] = afscgap.convert.convert_from_iso8601( 88 params_safe['date_time'] 89 ) 90 91 if hauls_prefetch is not None: 92 hauls_data = hauls_prefetch 93 else: 94 hauls_data = get_hauls_data( 95 params_safe, 96 requestor=requestor, 97 hauls_url=hauls_url 98 ) 99 100 return NegativeInferenceCursorDecorator(inner_cursor, hauls_data)
Build a cursor which infers zero catch records.
Arguments:
- params: Dictionary of filters to apply to the query where a value of None means no filter should be applied on that field.
- inner_cursor: Cursor which yields records which, when appearing, should not be later inferred as zero catch records.
- requestor: Strategy to make HTTP GET requests. If None, will default to requests.get.
- hauls_url: The URL at which the Hauls file can be found or None to use a default. Defaults to None.
- hauls_prefetch: List of hauls data to use. If None, will request from hauls_url. If not None, will use this instead.
Returns:
Cursor which 1) first iterates over the inner_cursor and then 2) provides inferred zero catch records (for any hauls without observed data from inner_cursor for a species).
103def build_params_checker(params: dict) -> PARAMS_CHECKER: 104 """Build a function that checks if a single Haul record should be filtered. 105 106 Args: 107 params: Dictionary of filters to apply to the query where a value of 108 None means no filter should be applied on that field. 109 110 Returns: 111 Function which returns true if the record given to it should be included 112 (is not filtered out) or false if it should be excluded from results to 113 meet a query requirement (is filtered out). 114 """ 115 116 def build_query_function(key: str, checker): 117 return lambda target: checker(target[key]) 118 119 params_py = afscgap.query_util.interpret_query_to_py(params) 120 params_py_items = params_py.items() 121 params_py_items_given = filter(lambda x: x[1] is not None, params_py_items) 122 params_py_items_valid = filter( 123 lambda x: x[0] in HAUL_FILTERABLE_FIELDS, 124 params_py_items_given 125 ) 126 params_funcs = map( 127 lambda x: build_query_function(x[0], x[1]), 128 params_py_items_valid 129 ) 130 params_funcs_realized = list(params_funcs) 131 132 def check_all(target: afscgap.model.Haul) -> bool: 133 target_dict = target.to_dict() 134 not_allowed = filter( 135 lambda x: not x(target_dict), 136 params_funcs_realized 137 ) 138 num_not_allowed = sum(map(lambda x: 1, not_allowed)) 139 return num_not_allowed == 0 140 141 return check_all
Build a function that checks if a single Haul record should be filtered.
Arguments:
- params: Dictionary of filters to apply to the query where a value of None means no filter should be applied on that field.
Returns:
Function which returns true if the record given to it should be included (is not filtered out) or false if it should be excluded from results to meet a query requirement (is filtered out).
144def get_hauls_data(params: dict, requestor: OPT_REQUESTOR = None, 145 hauls_url: afscgap.client.OPT_STR = None) -> HAUL_LIST: 146 """Download Hauls from a URL and apply a filter specified by params. 147 148 Args: 149 params: Dictionary of filters to apply to the query where a value of 150 None means no filter should be applied on that field. 151 requestor: Strategy to make HTTP GET requests. If None, will default 152 to requests.get. 153 hauls_url: The URL at which the Hauls file can be found or None to use 154 a default. Defaults to None. 155 156 Returns: 157 List of Haul data after having applied the filters described by params. 158 """ 159 if hauls_url is None: 160 hauls_url = DEFAULT_HAULS_URL 161 162 params_checker = build_params_checker(params) 163 164 if requestor is None: 165 requestor = afscgap.http_util.build_requestor() 166 167 response = requestor(hauls_url) 168 afscgap.http_util.check_result(response) 169 170 response.encoding = 'utf-8' 171 response_io = io.StringIO(response.text, newline='') 172 173 response_rows = csv.DictReader(response_io) 174 response_hauls = map(parse_haul, response_rows) 175 response_hauls_filtered = filter(params_checker, response_hauls) 176 177 return list(response_hauls_filtered)
Download Hauls from a URL and apply a filter specified by params.
Arguments:
- params: Dictionary of filters to apply to the query where a value of None means no filter should be applied on that field.
- requestor: Strategy to make HTTP GET requests. If None, will default to requests.get.
- hauls_url: The URL at which the Hauls file can be found or None to use a default. Defaults to None.
Returns:
List of Haul data after having applied the filters described by params.
180class NegativeInferenceCursorDecorator(afscgap.cursor.Cursor): 181 """Cursor augmenting another cursor with inferred negative records. 182 183 Cursor which exausts an inner cursor and then supplies inferred zero catch 184 records. Specifically, a Cursor which 1) first iterates over the 185 inner_cursor and then 2) provides inferred zero catch records (for any hauls 186 without observed data from inner_cursor for a species). 187 """ 188 189 def __init__(self, inner_cursor: afscgap.cursor.Cursor, 190 hauls_data: HAUL_LIST): 191 """Decorate a cursor. 192 193 Args: 194 inner_cursor: The cursor to augment and from which to observe 195 presence data. 196 hauls_data: Metadata on all hauls relevant to the query. 197 """ 198 self._inner_cursor = inner_cursor 199 self._hauls_data = hauls_data 200 201 self._started_inference = False 202 self._inferences_iter: typing.Iterator[afscgap.model.Record] = iter([]) 203 204 self._species_seen: SPECIES_DICT = dict() 205 self._species_hauls_seen: typing.Set[str] = set() 206 self._ak_survey_ids: typing.Dict[str, int] = dict() 207 208 def get_base_url(self) -> str: 209 """Get the URL at which the first page of query results can be found. 210 211 Returns: 212 The URL for the query without pagination information. 213 """ 214 return self._inner_cursor.get_base_url() 215 216 def get_limit(self) -> OPT_INT: 217 """Get the page size limit. 218 219 Returns: 220 The maximum number of records to return per page. 221 """ 222 return self._inner_cursor.get_limit() 223 224 def get_start_offset(self) -> OPT_INT: 225 """Get the number of inital records to ignore. 226 227 Returns: 228 The number of records being skipped at the start of the result set. 229 """ 230 return self._inner_cursor.get_start_offset() 231 232 def get_filtering_incomplete(self) -> bool: 233 """Determine if this cursor is silently filtering incomplete records. 234 235 Returns: 236 Flag indicating if incomplete records should be silently filtered. 237 If true, they will not be returned during iteration and placed in 238 the queue at get_invalid(). If false, they will be returned and 239 those incomplete records' get_complete() will return false. 240 """ 241 return self._inner_cursor.get_filtering_incomplete() 242 243 def get_page_url(self, offset: OPT_INT = None, 244 limit: OPT_INT = None) -> str: 245 """Get a URL at which a page can be found using this cursor's base url. 246 247 Args: 248 offset: The number of records to skip prior to the page. 249 limit: The maximum number of records to return in the page. 250 Returns: 251 URL at which the requested page can be found. 252 """ 253 return self._inner_cursor.get_page_url(offset=offset, limit=limit) 254 255 def get_page(self, offset: OPT_INT = None, 256 limit: OPT_INT = None, 257 ignore_invalid: bool = False) -> typing.List[afscgap.model.Record]: 258 """Get a page using this cursor's base url. 259 260 Args: 261 offset: The number of records to skip prior to the page. 262 limit: The maximum number of records to return in the page. 263 ignore_invalid: Flag indicating how to handle invalid records. If 264 true, will silently throw away records which could not be 265 parsed. If false, will raise an exception if a record can not 266 be parsed. 267 268 Returns: 269 Results from the page which, regardless of ignore_invalid, may 270 contain a mixture of complete and incomplete records. 271 """ 272 return self._inner_cursor.get_page( 273 offset=offset, 274 limit=limit, 275 ignore_invalid=ignore_invalid 276 ) 277 278 def get_invalid(self) -> 'queue.Queue[dict]': 279 """Get a queue of invalid / incomplete records found so far. 280 281 Returns: 282 Queue with dictionaries containing the raw data returned from the 283 API that did not have valid values for all required fields. Note 284 that this will include incomplete records as well if 285 get_filtering_incomplete() is true and will not contain incomplete 286 records otherwise. 287 """ 288 return self._inner_cursor.get_invalid() 289 290 def to_dicts(self) -> typing.Iterable[dict]: 291 """Create an iterator which converts Records to dicts. 292 293 Returns: 294 Iterator which returns dictionaries instead of Record objects but 295 has otherwise the same beahavior as iterating in this Cursor 296 directly. 297 """ 298 return self._inner_cursor.to_dicts() 299 300 def get_next(self) -> typing.Optional[afscgap.model.Record]: 301 """Get the next value for this Cursor. 302 303 Returns: 304 The next value waiting if cached in the cursor's results queue or 305 as just retrieved from a new page gathered by HTTP request. Will 306 return None if no remain. 307 """ 308 if self._started_inference: 309 return self._get_next_inferred() 310 else: 311 next_record_maybe = self._inner_cursor.get_next() 312 313 if next_record_maybe: 314 self._record_record_meta(next_record_maybe) 315 return next_record_maybe 316 else: 317 self._start_inference() 318 return self._get_next_inferred() 319 320 def _record_record_meta(self, record: afscgap.model.Record): 321 """Record metadata from a record, indicating a haul / species was seen. 322 323 Args: 324 record: The record observed. 325 """ 326 key_with_species = self._get_haul_key( 327 record, 328 species=record.get_scientific_name() 329 ) 330 self._species_hauls_seen.add(key_with_species) 331 332 scientific_name = record.get_scientific_name() 333 common_name = record.get_common_name() 334 species_code = record.get_species_code() 335 tsn = record.get_tsn_maybe() 336 337 self._species_seen[scientific_name] = afscgap.model.SpeciesRecord( 338 scientific_name, 339 common_name, 340 species_code, 341 tsn 342 ) 343 344 survey = record.get_survey() 345 ak_survey_id = record.get_ak_survey_id() 346 347 self._ak_survey_ids[survey] = ak_survey_id 348 349 def _get_haul_key(self, record: afscgap.model.HaulKeyable, 350 species: OPT_STR = None) -> str: 351 """Get a string uniquely identifying an individual haul. 352 353 Args: 354 record: The record from which to derive a haul key. 355 speices: If given, include the species in the key. If not given, the 356 key will refer to the entire haul across all species. Note that 357 this should be the scientific name for a species. 358 359 Returns: 360 String uniquely identifying a haul across the entire dataset. 361 """ 362 ship_info_vals = [ 363 record.get_year(), 364 record.get_vessel_id(), 365 record.get_cruise(), 366 record.get_haul() 367 ] 368 ship_info_vals_int = map(lambda x: round(x), ship_info_vals) 369 ship_info_vals_str = map(str, ship_info_vals_int) 370 ship_info_vals_csv = ','.join(ship_info_vals_str) 371 372 without_species = '%s:%s' % (record.get_srvy(), ship_info_vals_csv) 373 374 if species: 375 return '%s/%s' % (without_species, species) 376 else: 377 return without_species 378 379 def _start_inference(self): 380 """Prepare to start inferrence. 381 382 Indicate that the inner cursor is exhaused, preparing to run inferrence. 383 """ 384 hauls_seen_with_key = map( 385 lambda x: (self._get_haul_key(x), x), 386 self._hauls_data 387 ) 388 hauls_seen_by_key = dict(hauls_seen_with_key) 389 390 scientific_names_seen = self._species_seen.keys() 391 missing_keys = self._get_missing_keys( 392 hauls_seen_by_key.keys(), 393 scientific_names_seen, 394 self._species_hauls_seen 395 ) 396 397 missing_haul_keys_and_species_tuple = map( 398 lambda x: x.split('/'), 399 missing_keys 400 ) 401 missing_haul_keys_and_species = map( 402 lambda x: {'haulKey': x[0], 'species': x[1]}, 403 missing_haul_keys_and_species_tuple 404 ) 405 missing_hauls_and_species = map( 406 lambda x: { 407 'haul': hauls_seen_by_key[x['haulKey']], 408 'species': x['species'] 409 }, 410 missing_haul_keys_and_species 411 ) 412 413 def make_inference_record(target: typing.Dict) -> afscgap.model.Record: 414 scientific_name = target['species'] 415 haul = target['haul'] 416 417 species_record = self._species_seen[scientific_name] 418 common_name = species_record.get_common_name() 419 species_code = species_record.get_species_code() 420 tsn = species_record.get_tsn() 421 422 ak_survey_id = self._ak_survey_ids.get(haul.get_survey(), None) 423 424 return ZeroCatchHaulDecorator( 425 haul, 426 scientific_name, 427 common_name, 428 species_code, 429 tsn, 430 ak_survey_id 431 ) 432 433 inference_map = map(make_inference_record, missing_hauls_and_species) 434 435 self._inferences_iter = iter(inference_map) 436 self._started_inference = True 437 438 def _get_next_inferred(self) -> typing.Optional[afscgap.model.Record]: 439 """Get the next inferred zero catch record. 440 441 Raises: 442 StopIteration: Raised if no records left to infer. 443 AssertionError: Raised if the cursor has not yet started inference. 444 445 Returns: 446 Next inferred absence data record. 447 """ 448 assert self._started_inference 449 450 try: 451 return next(self._inferences_iter) 452 except StopIteration: 453 return None 454 455 def _get_missing_keys(self, hauls_seen: typing.Iterable[str], 456 scientific_names_seen: typing.Iterable[str], 457 species_hauls_seen: typing.Set[str]) -> typing.Iterable[str]: 458 """Determine which species haul keys were expected but not observed. 459 460 Args: 461 hauls_seen: The haus seen (non-species keys). 462 scientific_names_seen: The name of the scientific names for species 463 observed across the entire dataset yielded by the user query. 464 species_hauls_seen: The haul / species keys or combinations actually 465 observed. 466 467 Returns: 468 Haul / species keys expected but not found in species_hauls_seen 469 given the hauls described in hauls_seen and the species seen in 470 scientific_names_seen. 471 """ 472 hauls_with_names = itertools.product( 473 hauls_seen, 474 scientific_names_seen 475 ) 476 hauls_with_names_str = map(lambda x: '%s/%s' % x, hauls_with_names) 477 missing_keys = filter( 478 lambda x: x not in species_hauls_seen, 479 hauls_with_names_str 480 ) 481 return missing_keys
Cursor augmenting another cursor with inferred negative records.
Cursor which exausts an inner cursor and then supplies inferred zero catch records. Specifically, a Cursor which 1) first iterates over the inner_cursor and then 2) provides inferred zero catch records (for any hauls without observed data from inner_cursor for a species).
189 def __init__(self, inner_cursor: afscgap.cursor.Cursor, 190 hauls_data: HAUL_LIST): 191 """Decorate a cursor. 192 193 Args: 194 inner_cursor: The cursor to augment and from which to observe 195 presence data. 196 hauls_data: Metadata on all hauls relevant to the query. 197 """ 198 self._inner_cursor = inner_cursor 199 self._hauls_data = hauls_data 200 201 self._started_inference = False 202 self._inferences_iter: typing.Iterator[afscgap.model.Record] = iter([]) 203 204 self._species_seen: SPECIES_DICT = dict() 205 self._species_hauls_seen: typing.Set[str] = set() 206 self._ak_survey_ids: typing.Dict[str, int] = dict()
Decorate a cursor.
Arguments:
- inner_cursor: The cursor to augment and from which to observe presence data.
- hauls_data: Metadata on all hauls relevant to the query.
208 def get_base_url(self) -> str: 209 """Get the URL at which the first page of query results can be found. 210 211 Returns: 212 The URL for the query without pagination information. 213 """ 214 return self._inner_cursor.get_base_url()
Get the URL at which the first page of query results can be found.
Returns:
The URL for the query without pagination information.
216 def get_limit(self) -> OPT_INT: 217 """Get the page size limit. 218 219 Returns: 220 The maximum number of records to return per page. 221 """ 222 return self._inner_cursor.get_limit()
Get the page size limit.
Returns:
The maximum number of records to return per page.
224 def get_start_offset(self) -> OPT_INT: 225 """Get the number of inital records to ignore. 226 227 Returns: 228 The number of records being skipped at the start of the result set. 229 """ 230 return self._inner_cursor.get_start_offset()
Get the number of inital records to ignore.
Returns:
The number of records being skipped at the start of the result set.
232 def get_filtering_incomplete(self) -> bool: 233 """Determine if this cursor is silently filtering incomplete records. 234 235 Returns: 236 Flag indicating if incomplete records should be silently filtered. 237 If true, they will not be returned during iteration and placed in 238 the queue at get_invalid(). If false, they will be returned and 239 those incomplete records' get_complete() will return false. 240 """ 241 return self._inner_cursor.get_filtering_incomplete()
Determine if this cursor is silently filtering incomplete records.
Returns:
Flag indicating if incomplete records should be silently filtered. If true, they will not be returned during iteration and placed in the queue at get_invalid(). If false, they will be returned and those incomplete records' get_complete() will return false.
243 def get_page_url(self, offset: OPT_INT = None, 244 limit: OPT_INT = None) -> str: 245 """Get a URL at which a page can be found using this cursor's base url. 246 247 Args: 248 offset: The number of records to skip prior to the page. 249 limit: The maximum number of records to return in the page. 250 Returns: 251 URL at which the requested page can be found. 252 """ 253 return self._inner_cursor.get_page_url(offset=offset, limit=limit)
Get a URL at which a page can be found using this cursor's base url.
Arguments:
- offset: The number of records to skip prior to the page.
- limit: The maximum number of records to return in the page.
Returns:
URL at which the requested page can be found.
255 def get_page(self, offset: OPT_INT = None, 256 limit: OPT_INT = None, 257 ignore_invalid: bool = False) -> typing.List[afscgap.model.Record]: 258 """Get a page using this cursor's base url. 259 260 Args: 261 offset: The number of records to skip prior to the page. 262 limit: The maximum number of records to return in the page. 263 ignore_invalid: Flag indicating how to handle invalid records. If 264 true, will silently throw away records which could not be 265 parsed. If false, will raise an exception if a record can not 266 be parsed. 267 268 Returns: 269 Results from the page which, regardless of ignore_invalid, may 270 contain a mixture of complete and incomplete records. 271 """ 272 return self._inner_cursor.get_page( 273 offset=offset, 274 limit=limit, 275 ignore_invalid=ignore_invalid 276 )
Get a page using this cursor's base url.
Arguments:
- offset: The number of records to skip prior to the page.
- limit: The maximum number of records to return in the page.
- ignore_invalid: Flag indicating how to handle invalid records. If true, will silently throw away records which could not be parsed. If false, will raise an exception if a record can not be parsed.
Returns:
Results from the page which, regardless of ignore_invalid, may contain a mixture of complete and incomplete records.
278 def get_invalid(self) -> 'queue.Queue[dict]': 279 """Get a queue of invalid / incomplete records found so far. 280 281 Returns: 282 Queue with dictionaries containing the raw data returned from the 283 API that did not have valid values for all required fields. Note 284 that this will include incomplete records as well if 285 get_filtering_incomplete() is true and will not contain incomplete 286 records otherwise. 287 """ 288 return self._inner_cursor.get_invalid()
Get a queue of invalid / incomplete records found so far.
Returns:
Queue with dictionaries containing the raw data returned from the API that did not have valid values for all required fields. Note that this will include incomplete records as well if get_filtering_incomplete() is true and will not contain incomplete records otherwise.
290 def to_dicts(self) -> typing.Iterable[dict]: 291 """Create an iterator which converts Records to dicts. 292 293 Returns: 294 Iterator which returns dictionaries instead of Record objects but 295 has otherwise the same beahavior as iterating in this Cursor 296 directly. 297 """ 298 return self._inner_cursor.to_dicts()
Create an iterator which converts Records to dicts.
Returns:
Iterator which returns dictionaries instead of Record objects but has otherwise the same beahavior as iterating in this Cursor directly.
300 def get_next(self) -> typing.Optional[afscgap.model.Record]: 301 """Get the next value for this Cursor. 302 303 Returns: 304 The next value waiting if cached in the cursor's results queue or 305 as just retrieved from a new page gathered by HTTP request. Will 306 return None if no remain. 307 """ 308 if self._started_inference: 309 return self._get_next_inferred() 310 else: 311 next_record_maybe = self._inner_cursor.get_next() 312 313 if next_record_maybe: 314 self._record_record_meta(next_record_maybe) 315 return next_record_maybe 316 else: 317 self._start_inference() 318 return self._get_next_inferred()
Get the next value for this Cursor.
Returns:
The next value waiting if cached in the cursor's results queue or as just retrieved from a new page gathered by HTTP request. Will return None if no remain.
484class ZeroCatchHaulDecorator(afscgap.model.Record): 485 """Decorator for a Haul that makes it operate like a zero catch Record.""" 486 487 def __init__(self, haul: afscgap.model.Haul, scientific_name: str, 488 common_name: str, species_code: float, tsn: OPT_INT, 489 ak_survey_id: OPT_INT): 490 """Decorate a Haul to conform to the Record interface. 491 492 Args: 493 haul: The haul to decorate. 494 scientific_name: The scientific name of the species to be associated 495 with this record. 496 common_name: The common name of the species to be associated with 497 this record. 498 species_code: The species code of the species to be associated with 499 this record. 500 tsn: The taxonomic information system species code to be associated 501 with this record if known. 502 ak_survey_id: The AK survey ID to be associated with this record if 503 known. 504 """ 505 self._haul = haul 506 self._scientific_name = scientific_name 507 self._common_name = common_name 508 self._species_code = species_code 509 self._tsn = tsn 510 self._ak_survey_id = ak_survey_id 511 512 def get_year(self) -> float: 513 """Get the year of the start date for the haul. 514 515 Returns: 516 Year for the haul. 517 """ 518 return self._haul.get_year() 519 520 def get_srvy(self) -> str: 521 """Get the field labeled as srvy in the API. 522 523 Returns: 524 The name of the survey in which this haul was conducted. NBS (N 525 Bearing Sea), EBS (SE Bearing Sea), BSS (Bearing Sea Slope), or GOA 526 (Gulf of Alaska) 527 """ 528 return self._haul.get_srvy() 529 530 def get_survey(self) -> str: 531 """Get the field labeled as survey in the API. 532 533 Returns: 534 Long form description of the survey in which the haul was conducted. 535 """ 536 return self._haul.get_survey() 537 538 def get_survey_id(self) -> float: 539 """Get the field labeled as survey_id in the API. 540 541 Returns: 542 Unique numeric ID for the survey. 543 """ 544 return self._haul.get_survey_id() 545 546 def get_cruise(self) -> float: 547 """Get the field labeled as cruise in the API. 548 549 Returns: 550 An ID uniquely identifying the cruise in which the haul was made. 551 Multiple cruises in a survey. 552 """ 553 return self._haul.get_cruise() 554 555 def get_haul(self) -> float: 556 """Get the field labeled as haul in the API. 557 558 Returns: 559 An ID uniquely identifying the haul. Multiple hauls per cruises. 560 """ 561 return self._haul.get_haul() 562 563 def get_stratum(self) -> float: 564 """Get the field labeled as stratum in the API. 565 566 Returns: 567 Unique ID for statistical area / survey combination as described in 568 the metadata or 0 if an experimental tow. 569 """ 570 return self._haul.get_stratum() 571 572 def get_station(self) -> str: 573 """Get the field labeled as station in the API. 574 575 Returns: 576 Station associated with the survey. 577 """ 578 return self._haul.get_station() 579 580 def get_vessel_name(self) -> str: 581 """Get the field labeled as vessel_name in the API. 582 583 Returns: 584 Unique ID describing the vessel that made this haul. Note this is 585 left as a string but, in practice, is likely numeric. 586 """ 587 return self._haul.get_vessel_name() 588 589 def get_vessel_id(self) -> float: 590 """Get the field labeled as vessel_id in the API. 591 592 Returns: 593 Name of the vessel at the time the haul was made. Note that there 594 may be multiple names potentially associated with a vessel ID. 595 """ 596 return self._haul.get_vessel_id() 597 598 def get_date_time(self) -> str: 599 """Get the field labeled as date_time in the API. 600 601 Returns: 602 The date and time of the haul which has been attempted to be 603 transformed to an ISO 8601 string without timezone info. If it 604 couldn’t be transformed, the original string is reported. 605 """ 606 return self._haul.get_date_time() 607 608 def get_latitude(self, units: str = 'dd') -> float: 609 """Get the field labeled as latitude_dd in the API. 610 611 Args: 612 units: The units to return this value in. Only supported is dd for 613 degrees. Deafults to dd. 614 615 Returns: 616 Latitude in decimal degrees associated with the haul. 617 """ 618 return afscgap.model.assert_float_present( 619 afscgap.convert.convert_degrees( 620 self._haul.get_latitude_dd(), 621 units 622 ) 623 ) 624 625 def get_longitude(self, units: str = 'dd') -> float: 626 """Get the field labeled as longitude_dd in the API. 627 628 Args: 629 units: The units to return this value in. Only supported is dd for 630 degrees. Deafults to dd. 631 632 Returns: 633 Longitude in decimal degrees associated with the haul. 634 """ 635 return afscgap.model.assert_float_present( 636 afscgap.convert.convert_degrees( 637 self._haul.get_longitude_dd(), 638 units 639 ) 640 ) 641 642 def get_species_code(self) -> float: 643 """Get the field labeled as species_code in the API. 644 645 Returns: 646 Unique ID associated with the species observed. 647 """ 648 return self._species_code 649 650 def get_common_name(self) -> str: 651 """Get the field labeled as common_name in the API. 652 653 Returns: 654 The “common name” associated with the species observed. Example: 655 Pacific glass shrimp. 656 """ 657 return self._common_name 658 659 def get_scientific_name(self) -> str: 660 """Get the field labeled as scientific_name in the API. 661 662 Returns: 663 The “scientific name” associated with the species observed. Example: 664 Pasiphaea pacifica. 665 """ 666 return self._scientific_name 667 668 def get_taxon_confidence(self) -> str: 669 """Get rating of taxon identification confidence. 670 671 Returns: 672 Always returns Unassessed. 673 """ 674 return 'Unassessed' 675 676 def get_cpue_weight_maybe(self, units: str = 'kg/ha') -> OPT_FLOAT: 677 """Get a field labeled as cpue_* in the API. 678 679 Args: 680 units: The desired units for the catch per unit effort. Options: 681 kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha. 682 683 Returns: 684 Catch weight divided by net area (in given units) if available. See 685 metadata. None if could not interpret as a float. If an inferred 686 zero catch record, will be zero. 687 """ 688 return 0 689 690 def get_cpue_count_maybe(self, units: str = 'kg/ha') -> OPT_FLOAT: 691 """Get the field labeled as cpue_* in the API. 692 693 Get the catch per unit effort from the record with one of the following 694 units: kg/ha, kg/km2, kg1000/km2. 695 696 Args: 697 units: The desired units for the catch per unit effort. Options: 698 count/ha, count/km2, and count1000/km2. Defaults to count/ha. 699 700 Returns: 701 Catch weight divided by net area (in given units) if available. See 702 metadata. None if could not interpret as a float. If an inferred 703 zero catch record, will be zero. 704 """ 705 return 0 706 707 def get_weight_maybe(self, units='kg') -> OPT_FLOAT: 708 """Get the field labeled as weight_kg in the API. 709 710 Args: 711 units: The units in which the weight should be returned. Options are 712 g, kg for grams and kilograms respectively. Deafults to kg. 713 714 Returns: 715 Taxon weight if available. See metadata. None if could not 716 interpret as a float. If an inferred zero catch record, will be 717 zero. 718 """ 719 return 0 720 721 def get_count_maybe(self) -> OPT_FLOAT: 722 """Get total number of organism individuals in haul. 723 724 Returns: 725 Always returns 0. 726 """ 727 return 0 728 729 def get_bottom_temperature_maybe(self, units: str = 'c') -> OPT_FLOAT: 730 """Get the field labeled as bottom_temperature_c in the API. 731 732 Args: 733 units: The units in which the temperature should be returned. 734 Options: c or f for Celcius and Fahrenheit respectively. 735 Defaults to c. 736 737 Returns: 738 Bottom temperature associated with observation / inferrence if 739 available in desired units. None if not given or could not interpret 740 as a float. 741 """ 742 return afscgap.convert.convert_temperature( 743 self._haul.get_bottom_temperature_c_maybe(), 744 units 745 ) 746 747 def get_surface_temperature_maybe(self, units: str = 'c') -> OPT_FLOAT: 748 """Get the field labeled as surface_temperature_c in the API. 749 750 Args: 751 units: The units in which the temperature should be returned. 752 Options: c or f for Celcius and Fahrenheit respectively. 753 Defaults to c. 754 755 Returns: 756 Surface temperature associated with observation / inferrence if 757 available. None if not given or could not interpret as a float. 758 """ 759 return afscgap.convert.convert_temperature( 760 self._haul.get_surface_temperature_c_maybe(), 761 units 762 ) 763 764 def get_depth(self, units: str = 'm') -> float: 765 """Get the field labeled as depth_m in the API. 766 767 Args: 768 units: The units in which the distance should be returned. Options: 769 m or km for meters and kilometers respectively. Defaults to m. 770 771 Returns: 772 Depth of the bottom. 773 """ 774 return afscgap.model.assert_float_present( 775 afscgap.convert.convert_distance(self._haul.get_depth_m(), units) 776 ) 777 778 def get_distance_fished(self, units: str = 'm') -> float: 779 """Get the field labeled as distance_fished_km in the API. 780 781 Args: 782 units: The units in which the distance should be returned. Options: 783 m or km for meters and kilometers respectively. Defaults to km. 784 785 Returns: 786 Distance of the net fished. 787 """ 788 return afscgap.model.assert_float_present( 789 afscgap.convert.convert_distance( 790 self._haul.get_distance_fished_km() * 1000, 791 units 792 ) 793 ) 794 795 def get_net_width_maybe(self, units: str = 'm') -> OPT_FLOAT: 796 """Get the field labeled as net_width_m in the API. 797 798 Args: 799 units: The units in which the distance should be returned. Options: 800 m or km for meters and kilometers respectively. Defaults to m. 801 802 Returns: 803 Distance of the net fished or None if not given. 804 """ 805 return afscgap.convert.convert_distance( 806 self._haul.get_net_width_m_maybe(), 807 units 808 ) 809 810 def get_net_height_maybe(self, units: str = 'm') -> OPT_FLOAT: 811 """Get the field labeled as net_height_m in the API. 812 813 Args: 814 units: The units in which the distance should be returned. Options: 815 m or km for meters and kilometers respectively. Defaults to m. 816 817 Returns: 818 Height of the net fished or None if not given. 819 """ 820 return afscgap.convert.convert_distance( 821 self._haul.get_net_height_m_maybe(), 822 units 823 ) 824 825 def get_net_width(self, units: str = 'm') -> float: 826 """Get the field labeled as net_width_m in the API. 827 828 Args: 829 units: The units in which the distance should be returned. Options: 830 m or km for meters and kilometers respectively. Defaults to m. 831 832 Returns: 833 Distance of the net fished after asserting it is given. 834 """ 835 return afscgap.model.assert_float_present( 836 self.get_net_width_maybe(units=units) 837 ) 838 839 def get_net_height(self, units: str = 'm') -> float: 840 """Get the field labeled as net_height_m in the API. 841 842 Args: 843 units: The units in which the distance should be returned. Options: 844 m or km for meters and kilometers respectively. Defaults to m. 845 846 Returns: 847 Height of the net fished after asserting it is given. 848 """ 849 return afscgap.model.assert_float_present( 850 self.get_net_height_maybe(units=units) 851 ) 852 853 def get_area_swept(self, units: str = 'ha') -> float: 854 """Get the field labeled as area_swept_ha in the API. 855 856 Args: 857 units: The units in which the area should be returned. Options: 858 ha, m2, km2. Defaults to ha. 859 860 Returns: 861 Area covered by the net while fishing in desired units. 862 """ 863 return afscgap.model.assert_float_present( 864 afscgap.convert.convert_area( 865 self._haul.get_area_swept_ha(), 866 units 867 ) 868 ) 869 870 def get_duration(self, units: str = 'hr') -> float: 871 """Get the field labeled as duration_hr in the API. 872 873 Args: 874 units: The units in which the duration should be returned. Options: 875 day, hr, min. Defaults to hr. 876 877 Returns: 878 Duration of the haul. 879 """ 880 return afscgap.model.assert_float_present( 881 afscgap.convert.convert_time(self._haul.get_duration_hr(), units) 882 ) 883 884 def get_tsn(self) -> int: 885 """Get taxonomic information system species code. 886 887 Returns: 888 TSN for species. 889 """ 890 return afscgap.model.assert_int_present(self._tsn) 891 892 def get_tsn_maybe(self) -> OPT_INT: 893 """Get taxonomic information system species code. 894 895 Returns: 896 TSN for species. 897 """ 898 return self._tsn 899 900 def get_ak_survey_id(self) -> int: 901 """Get the field labeled as ak_survey_id in the API. 902 903 Returns: 904 AK survey ID if found. 905 """ 906 return afscgap.model.assert_int_present(self._ak_survey_id) 907 908 def get_ak_survey_id_maybe(self) -> OPT_INT: 909 """Get the field labeled as ak_survey_id in the API. 910 911 Returns: 912 AK identifier for the survey or None if not given. 913 """ 914 return self._ak_survey_id 915 916 def get_cpue_weight(self, units: str = 'kg/ha') -> float: 917 """Get the value of field cpue_kgha with validity assert. 918 919 Args: 920 units: The desired units for the catch per unit effort. Options: 921 kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha. 922 923 Raises: 924 AssertionError: Raised if this field was not given by the API or 925 could not be parsed as expected. 926 927 Returns: 928 Catch weight divided by net area (kg / hectares) if available. See 929 metadata. Always returns 0. 930 """ 931 return 0 932 933 def get_cpue_count(self, units: str = 'count/ha') -> float: 934 """Get the value of field cpue_noha with validity assert. 935 936 Args: 937 units: The desired units for the catch per unit effort. Options: 938 count/ha, count/km2, and count1000/km2. Defaults to count/ha. 939 940 Raises: 941 AssertionError: Raised if this field was not given by the API or 942 could not be parsed as expected. 943 944 Returns: 945 Catch number divided by net sweep area if available (count / 946 hectares). See metadata. Always returns 0. 947 """ 948 return 0 949 950 def get_weight(self, units: str = 'kg') -> float: 951 """Get the value of field weight_kg with validity assert. 952 953 Args: 954 units: The units in which the weight should be returned. Options are 955 g, kg for grams and kilograms respectively. Deafults to kg. 956 957 Raises: 958 AssertionError: Raised if this field was not given by the API or 959 could not be parsed as expected. 960 961 Returns: 962 Taxon weight (kg) if available. See metadata. Always returns 0. 963 """ 964 return 0 965 966 def get_count(self) -> float: 967 """Get the value of field count with validity assert. 968 969 Raises: 970 AssertionError: Raised if this field was not given by the API or 971 could not be parsed as expected. 972 973 Returns: 974 Always returns 0 975 """ 976 return 0 977 978 def get_bottom_temperature(self, units='c') -> float: 979 """Get the value of field bottom_temperature_c with validity assert. 980 981 Args: 982 units: The units in which the temperature should be returned. 983 Options: c or f for Celcius and Fahrenheit respectively. 984 Defaults to c. 985 986 Raises: 987 AssertionError: Raised if this field was not given by the API or 988 could not be parsed as expected. 989 990 Returns: 991 Bottom temperature associated with observation / inferrence if 992 available. 993 """ 994 return afscgap.model.assert_float_present( 995 self.get_bottom_temperature_maybe(units=units) 996 ) 997 998 def get_surface_temperature(self, units='c') -> float: 999 """Get the value of field surface_temperature_c with validity assert. 1000 1001 Args: 1002 units: The units in which the temperature should be returned. 1003 Options: c or f for Celcius and Fahrenheit respectively. 1004 Defaults to c. 1005 1006 Raises: 1007 AssertionError: Raised if this field was not given by the API or 1008 could not be parsed as expected. 1009 1010 Returns: 1011 Surface temperature associated with observation / inferrence if 1012 available. 1013 """ 1014 return afscgap.model.assert_float_present( 1015 self.get_surface_temperature_maybe(units=units) 1016 ) 1017 1018 def is_complete(self) -> bool: 1019 """Determine if this record has all of its values filled in. 1020 1021 Returns: 1022 True if all optional fields have a parsed value with the expected 1023 type and false otherwise. 1024 """ 1025 tsn_given = self._tsn is not None 1026 ak_survey_id_given = self._ak_survey_id is not None 1027 return tsn_given and ak_survey_id_given and self._haul.is_complete()
Decorator for a Haul that makes it operate like a zero catch Record.
487 def __init__(self, haul: afscgap.model.Haul, scientific_name: str, 488 common_name: str, species_code: float, tsn: OPT_INT, 489 ak_survey_id: OPT_INT): 490 """Decorate a Haul to conform to the Record interface. 491 492 Args: 493 haul: The haul to decorate. 494 scientific_name: The scientific name of the species to be associated 495 with this record. 496 common_name: The common name of the species to be associated with 497 this record. 498 species_code: The species code of the species to be associated with 499 this record. 500 tsn: The taxonomic information system species code to be associated 501 with this record if known. 502 ak_survey_id: The AK survey ID to be associated with this record if 503 known. 504 """ 505 self._haul = haul 506 self._scientific_name = scientific_name 507 self._common_name = common_name 508 self._species_code = species_code 509 self._tsn = tsn 510 self._ak_survey_id = ak_survey_id
Decorate a Haul to conform to the Record interface.
Arguments:
- haul: The haul to decorate.
- scientific_name: The scientific name of the species to be associated with this record.
- common_name: The common name of the species to be associated with this record.
- species_code: The species code of the species to be associated with this record.
- tsn: The taxonomic information system species code to be associated with this record if known.
- ak_survey_id: The AK survey ID to be associated with this record if known.
512 def get_year(self) -> float: 513 """Get the year of the start date for the haul. 514 515 Returns: 516 Year for the haul. 517 """ 518 return self._haul.get_year()
Get the year of the start date for the haul.
Returns:
Year for the haul.
520 def get_srvy(self) -> str: 521 """Get the field labeled as srvy in the API. 522 523 Returns: 524 The name of the survey in which this haul was conducted. NBS (N 525 Bearing Sea), EBS (SE Bearing Sea), BSS (Bearing Sea Slope), or GOA 526 (Gulf of Alaska) 527 """ 528 return self._haul.get_srvy()
Get the field labeled as srvy in the API.
Returns:
The name of the survey in which this haul was conducted. NBS (N Bearing Sea), EBS (SE Bearing Sea), BSS (Bearing Sea Slope), or GOA (Gulf of Alaska)
530 def get_survey(self) -> str: 531 """Get the field labeled as survey in the API. 532 533 Returns: 534 Long form description of the survey in which the haul was conducted. 535 """ 536 return self._haul.get_survey()
Get the field labeled as survey in the API.
Returns:
Long form description of the survey in which the haul was conducted.
538 def get_survey_id(self) -> float: 539 """Get the field labeled as survey_id in the API. 540 541 Returns: 542 Unique numeric ID for the survey. 543 """ 544 return self._haul.get_survey_id()
Get the field labeled as survey_id in the API.
Returns:
Unique numeric ID for the survey.
546 def get_cruise(self) -> float: 547 """Get the field labeled as cruise in the API. 548 549 Returns: 550 An ID uniquely identifying the cruise in which the haul was made. 551 Multiple cruises in a survey. 552 """ 553 return self._haul.get_cruise()
Get the field labeled as cruise in the API.
Returns:
An ID uniquely identifying the cruise in which the haul was made. Multiple cruises in a survey.
555 def get_haul(self) -> float: 556 """Get the field labeled as haul in the API. 557 558 Returns: 559 An ID uniquely identifying the haul. Multiple hauls per cruises. 560 """ 561 return self._haul.get_haul()
Get the field labeled as haul in the API.
Returns:
An ID uniquely identifying the haul. Multiple hauls per cruises.
563 def get_stratum(self) -> float: 564 """Get the field labeled as stratum in the API. 565 566 Returns: 567 Unique ID for statistical area / survey combination as described in 568 the metadata or 0 if an experimental tow. 569 """ 570 return self._haul.get_stratum()
Get the field labeled as stratum in the API.
Returns:
Unique ID for statistical area / survey combination as described in the metadata or 0 if an experimental tow.
572 def get_station(self) -> str: 573 """Get the field labeled as station in the API. 574 575 Returns: 576 Station associated with the survey. 577 """ 578 return self._haul.get_station()
Get the field labeled as station in the API.
Returns:
Station associated with the survey.
580 def get_vessel_name(self) -> str: 581 """Get the field labeled as vessel_name in the API. 582 583 Returns: 584 Unique ID describing the vessel that made this haul. Note this is 585 left as a string but, in practice, is likely numeric. 586 """ 587 return self._haul.get_vessel_name()
Get the field labeled as vessel_name in the API.
Returns:
Unique ID describing the vessel that made this haul. Note this is left as a string but, in practice, is likely numeric.
589 def get_vessel_id(self) -> float: 590 """Get the field labeled as vessel_id in the API. 591 592 Returns: 593 Name of the vessel at the time the haul was made. Note that there 594 may be multiple names potentially associated with a vessel ID. 595 """ 596 return self._haul.get_vessel_id()
Get the field labeled as vessel_id in the API.
Returns:
Name of the vessel at the time the haul was made. Note that there may be multiple names potentially associated with a vessel ID.
598 def get_date_time(self) -> str: 599 """Get the field labeled as date_time in the API. 600 601 Returns: 602 The date and time of the haul which has been attempted to be 603 transformed to an ISO 8601 string without timezone info. If it 604 couldn’t be transformed, the original string is reported. 605 """ 606 return self._haul.get_date_time()
Get the field labeled as date_time in the API.
Returns:
The date and time of the haul which has been attempted to be transformed to an ISO 8601 string without timezone info. If it couldn’t be transformed, the original string is reported.
608 def get_latitude(self, units: str = 'dd') -> float: 609 """Get the field labeled as latitude_dd in the API. 610 611 Args: 612 units: The units to return this value in. Only supported is dd for 613 degrees. Deafults to dd. 614 615 Returns: 616 Latitude in decimal degrees associated with the haul. 617 """ 618 return afscgap.model.assert_float_present( 619 afscgap.convert.convert_degrees( 620 self._haul.get_latitude_dd(), 621 units 622 ) 623 )
Get the field labeled as latitude_dd in the API.
Arguments:
- units: The units to return this value in. Only supported is dd for degrees. Deafults to dd.
Returns:
Latitude in decimal degrees associated with the haul.
625 def get_longitude(self, units: str = 'dd') -> float: 626 """Get the field labeled as longitude_dd in the API. 627 628 Args: 629 units: The units to return this value in. Only supported is dd for 630 degrees. Deafults to dd. 631 632 Returns: 633 Longitude in decimal degrees associated with the haul. 634 """ 635 return afscgap.model.assert_float_present( 636 afscgap.convert.convert_degrees( 637 self._haul.get_longitude_dd(), 638 units 639 ) 640 )
Get the field labeled as longitude_dd in the API.
Arguments:
- units: The units to return this value in. Only supported is dd for degrees. Deafults to dd.
Returns:
Longitude in decimal degrees associated with the haul.
642 def get_species_code(self) -> float: 643 """Get the field labeled as species_code in the API. 644 645 Returns: 646 Unique ID associated with the species observed. 647 """ 648 return self._species_code
Get the field labeled as species_code in the API.
Returns:
Unique ID associated with the species observed.
650 def get_common_name(self) -> str: 651 """Get the field labeled as common_name in the API. 652 653 Returns: 654 The “common name” associated with the species observed. Example: 655 Pacific glass shrimp. 656 """ 657 return self._common_name
Get the field labeled as common_name in the API.
Returns:
The “common name” associated with the species observed. Example: Pacific glass shrimp.
659 def get_scientific_name(self) -> str: 660 """Get the field labeled as scientific_name in the API. 661 662 Returns: 663 The “scientific name” associated with the species observed. Example: 664 Pasiphaea pacifica. 665 """ 666 return self._scientific_name
Get the field labeled as scientific_name in the API.
Returns:
The “scientific name” associated with the species observed. Example: Pasiphaea pacifica.
668 def get_taxon_confidence(self) -> str: 669 """Get rating of taxon identification confidence. 670 671 Returns: 672 Always returns Unassessed. 673 """ 674 return 'Unassessed'
Get rating of taxon identification confidence.
Returns:
Always returns Unassessed.
676 def get_cpue_weight_maybe(self, units: str = 'kg/ha') -> OPT_FLOAT: 677 """Get a field labeled as cpue_* in the API. 678 679 Args: 680 units: The desired units for the catch per unit effort. Options: 681 kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha. 682 683 Returns: 684 Catch weight divided by net area (in given units) if available. See 685 metadata. None if could not interpret as a float. If an inferred 686 zero catch record, will be zero. 687 """ 688 return 0
Get a field labeled as cpue_* in the API.
Arguments:
- units: The desired units for the catch per unit effort. Options: kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha.
Returns:
Catch weight divided by net area (in given units) if available. See metadata. None if could not interpret as a float. If an inferred zero catch record, will be zero.
690 def get_cpue_count_maybe(self, units: str = 'kg/ha') -> OPT_FLOAT: 691 """Get the field labeled as cpue_* in the API. 692 693 Get the catch per unit effort from the record with one of the following 694 units: kg/ha, kg/km2, kg1000/km2. 695 696 Args: 697 units: The desired units for the catch per unit effort. Options: 698 count/ha, count/km2, and count1000/km2. Defaults to count/ha. 699 700 Returns: 701 Catch weight divided by net area (in given units) if available. See 702 metadata. None if could not interpret as a float. If an inferred 703 zero catch record, will be zero. 704 """ 705 return 0
Get the field labeled as cpue_* in the API.
Get the catch per unit effort from the record with one of the following units: kg/ha, kg/km2, kg1000/km2.
Arguments:
- units: The desired units for the catch per unit effort. Options: count/ha, count/km2, and count1000/km2. Defaults to count/ha.
Returns:
Catch weight divided by net area (in given units) if available. See metadata. None if could not interpret as a float. If an inferred zero catch record, will be zero.
707 def get_weight_maybe(self, units='kg') -> OPT_FLOAT: 708 """Get the field labeled as weight_kg in the API. 709 710 Args: 711 units: The units in which the weight should be returned. Options are 712 g, kg for grams and kilograms respectively. Deafults to kg. 713 714 Returns: 715 Taxon weight if available. See metadata. None if could not 716 interpret as a float. If an inferred zero catch record, will be 717 zero. 718 """ 719 return 0
Get the field labeled as weight_kg in the API.
Arguments:
- units: The units in which the weight should be returned. Options are g, kg for grams and kilograms respectively. Deafults to kg.
Returns:
Taxon weight if available. See metadata. None if could not interpret as a float. If an inferred zero catch record, will be zero.
721 def get_count_maybe(self) -> OPT_FLOAT: 722 """Get total number of organism individuals in haul. 723 724 Returns: 725 Always returns 0. 726 """ 727 return 0
Get total number of organism individuals in haul.
Returns:
Always returns 0.
729 def get_bottom_temperature_maybe(self, units: str = 'c') -> OPT_FLOAT: 730 """Get the field labeled as bottom_temperature_c in the API. 731 732 Args: 733 units: The units in which the temperature should be returned. 734 Options: c or f for Celcius and Fahrenheit respectively. 735 Defaults to c. 736 737 Returns: 738 Bottom temperature associated with observation / inferrence if 739 available in desired units. None if not given or could not interpret 740 as a float. 741 """ 742 return afscgap.convert.convert_temperature( 743 self._haul.get_bottom_temperature_c_maybe(), 744 units 745 )
Get the field labeled as bottom_temperature_c in the API.
Arguments:
- units: The units in which the temperature should be returned. Options: c or f for Celcius and Fahrenheit respectively. Defaults to c.
Returns:
Bottom temperature associated with observation / inferrence if available in desired units. None if not given or could not interpret as a float.
747 def get_surface_temperature_maybe(self, units: str = 'c') -> OPT_FLOAT: 748 """Get the field labeled as surface_temperature_c in the API. 749 750 Args: 751 units: The units in which the temperature should be returned. 752 Options: c or f for Celcius and Fahrenheit respectively. 753 Defaults to c. 754 755 Returns: 756 Surface temperature associated with observation / inferrence if 757 available. None if not given or could not interpret as a float. 758 """ 759 return afscgap.convert.convert_temperature( 760 self._haul.get_surface_temperature_c_maybe(), 761 units 762 )
Get the field labeled as surface_temperature_c in the API.
Arguments:
- units: The units in which the temperature should be returned. Options: c or f for Celcius and Fahrenheit respectively. Defaults to c.
Returns:
Surface temperature associated with observation / inferrence if available. None if not given or could not interpret as a float.
764 def get_depth(self, units: str = 'm') -> float: 765 """Get the field labeled as depth_m in the API. 766 767 Args: 768 units: The units in which the distance should be returned. Options: 769 m or km for meters and kilometers respectively. Defaults to m. 770 771 Returns: 772 Depth of the bottom. 773 """ 774 return afscgap.model.assert_float_present( 775 afscgap.convert.convert_distance(self._haul.get_depth_m(), units) 776 )
Get the field labeled as depth_m in the API.
Arguments:
- units: The units in which the distance should be returned. Options: m or km for meters and kilometers respectively. Defaults to m.
Returns:
Depth of the bottom.
778 def get_distance_fished(self, units: str = 'm') -> float: 779 """Get the field labeled as distance_fished_km in the API. 780 781 Args: 782 units: The units in which the distance should be returned. Options: 783 m or km for meters and kilometers respectively. Defaults to km. 784 785 Returns: 786 Distance of the net fished. 787 """ 788 return afscgap.model.assert_float_present( 789 afscgap.convert.convert_distance( 790 self._haul.get_distance_fished_km() * 1000, 791 units 792 ) 793 )
Get the field labeled as distance_fished_km in the API.
Arguments:
- units: The units in which the distance should be returned. Options: m or km for meters and kilometers respectively. Defaults to km.
Returns:
Distance of the net fished.
795 def get_net_width_maybe(self, units: str = 'm') -> OPT_FLOAT: 796 """Get the field labeled as net_width_m in the API. 797 798 Args: 799 units: The units in which the distance should be returned. Options: 800 m or km for meters and kilometers respectively. Defaults to m. 801 802 Returns: 803 Distance of the net fished or None if not given. 804 """ 805 return afscgap.convert.convert_distance( 806 self._haul.get_net_width_m_maybe(), 807 units 808 )
Get the field labeled as net_width_m in the API.
Arguments:
- units: The units in which the distance should be returned. Options: m or km for meters and kilometers respectively. Defaults to m.
Returns:
Distance of the net fished or None if not given.
810 def get_net_height_maybe(self, units: str = 'm') -> OPT_FLOAT: 811 """Get the field labeled as net_height_m in the API. 812 813 Args: 814 units: The units in which the distance should be returned. Options: 815 m or km for meters and kilometers respectively. Defaults to m. 816 817 Returns: 818 Height of the net fished or None if not given. 819 """ 820 return afscgap.convert.convert_distance( 821 self._haul.get_net_height_m_maybe(), 822 units 823 )
Get the field labeled as net_height_m in the API.
Arguments:
- units: The units in which the distance should be returned. Options: m or km for meters and kilometers respectively. Defaults to m.
Returns:
Height of the net fished or None if not given.
825 def get_net_width(self, units: str = 'm') -> float: 826 """Get the field labeled as net_width_m in the API. 827 828 Args: 829 units: The units in which the distance should be returned. Options: 830 m or km for meters and kilometers respectively. Defaults to m. 831 832 Returns: 833 Distance of the net fished after asserting it is given. 834 """ 835 return afscgap.model.assert_float_present( 836 self.get_net_width_maybe(units=units) 837 )
Get the field labeled as net_width_m in the API.
Arguments:
- units: The units in which the distance should be returned. Options: m or km for meters and kilometers respectively. Defaults to m.
Returns:
Distance of the net fished after asserting it is given.
839 def get_net_height(self, units: str = 'm') -> float: 840 """Get the field labeled as net_height_m in the API. 841 842 Args: 843 units: The units in which the distance should be returned. Options: 844 m or km for meters and kilometers respectively. Defaults to m. 845 846 Returns: 847 Height of the net fished after asserting it is given. 848 """ 849 return afscgap.model.assert_float_present( 850 self.get_net_height_maybe(units=units) 851 )
Get the field labeled as net_height_m in the API.
Arguments:
- units: The units in which the distance should be returned. Options: m or km for meters and kilometers respectively. Defaults to m.
Returns:
Height of the net fished after asserting it is given.
853 def get_area_swept(self, units: str = 'ha') -> float: 854 """Get the field labeled as area_swept_ha in the API. 855 856 Args: 857 units: The units in which the area should be returned. Options: 858 ha, m2, km2. Defaults to ha. 859 860 Returns: 861 Area covered by the net while fishing in desired units. 862 """ 863 return afscgap.model.assert_float_present( 864 afscgap.convert.convert_area( 865 self._haul.get_area_swept_ha(), 866 units 867 ) 868 )
Get the field labeled as area_swept_ha in the API.
Arguments:
- units: The units in which the area should be returned. Options: ha, m2, km2. Defaults to ha.
Returns:
Area covered by the net while fishing in desired units.
870 def get_duration(self, units: str = 'hr') -> float: 871 """Get the field labeled as duration_hr in the API. 872 873 Args: 874 units: The units in which the duration should be returned. Options: 875 day, hr, min. Defaults to hr. 876 877 Returns: 878 Duration of the haul. 879 """ 880 return afscgap.model.assert_float_present( 881 afscgap.convert.convert_time(self._haul.get_duration_hr(), units) 882 )
Get the field labeled as duration_hr in the API.
Arguments:
- units: The units in which the duration should be returned. Options: day, hr, min. Defaults to hr.
Returns:
Duration of the haul.
884 def get_tsn(self) -> int: 885 """Get taxonomic information system species code. 886 887 Returns: 888 TSN for species. 889 """ 890 return afscgap.model.assert_int_present(self._tsn)
Get taxonomic information system species code.
Returns:
TSN for species.
892 def get_tsn_maybe(self) -> OPT_INT: 893 """Get taxonomic information system species code. 894 895 Returns: 896 TSN for species. 897 """ 898 return self._tsn
Get taxonomic information system species code.
Returns:
TSN for species.
900 def get_ak_survey_id(self) -> int: 901 """Get the field labeled as ak_survey_id in the API. 902 903 Returns: 904 AK survey ID if found. 905 """ 906 return afscgap.model.assert_int_present(self._ak_survey_id)
Get the field labeled as ak_survey_id in the API.
Returns:
AK survey ID if found.
908 def get_ak_survey_id_maybe(self) -> OPT_INT: 909 """Get the field labeled as ak_survey_id in the API. 910 911 Returns: 912 AK identifier for the survey or None if not given. 913 """ 914 return self._ak_survey_id
Get the field labeled as ak_survey_id in the API.
Returns:
AK identifier for the survey or None if not given.
916 def get_cpue_weight(self, units: str = 'kg/ha') -> float: 917 """Get the value of field cpue_kgha with validity assert. 918 919 Args: 920 units: The desired units for the catch per unit effort. Options: 921 kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha. 922 923 Raises: 924 AssertionError: Raised if this field was not given by the API or 925 could not be parsed as expected. 926 927 Returns: 928 Catch weight divided by net area (kg / hectares) if available. See 929 metadata. Always returns 0. 930 """ 931 return 0
Get the value of field cpue_kgha with validity assert.
Arguments:
- units: The desired units for the catch per unit effort. Options: kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha.
Raises:
- AssertionError: Raised if this field was not given by the API or
- could not be parsed as expected.
Returns:
Catch weight divided by net area (kg / hectares) if available. See metadata. Always returns 0.
933 def get_cpue_count(self, units: str = 'count/ha') -> float: 934 """Get the value of field cpue_noha with validity assert. 935 936 Args: 937 units: The desired units for the catch per unit effort. Options: 938 count/ha, count/km2, and count1000/km2. Defaults to count/ha. 939 940 Raises: 941 AssertionError: Raised if this field was not given by the API or 942 could not be parsed as expected. 943 944 Returns: 945 Catch number divided by net sweep area if available (count / 946 hectares). See metadata. Always returns 0. 947 """ 948 return 0
Get the value of field cpue_noha with validity assert.
Arguments:
- units: The desired units for the catch per unit effort. Options: count/ha, count/km2, and count1000/km2. Defaults to count/ha.
Raises:
- AssertionError: Raised if this field was not given by the API or
- could not be parsed as expected.
Returns:
Catch number divided by net sweep area if available (count / hectares). See metadata. Always returns 0.
950 def get_weight(self, units: str = 'kg') -> float: 951 """Get the value of field weight_kg with validity assert. 952 953 Args: 954 units: The units in which the weight should be returned. Options are 955 g, kg for grams and kilograms respectively. Deafults to kg. 956 957 Raises: 958 AssertionError: Raised if this field was not given by the API or 959 could not be parsed as expected. 960 961 Returns: 962 Taxon weight (kg) if available. See metadata. Always returns 0. 963 """ 964 return 0
Get the value of field weight_kg with validity assert.
Arguments:
- units: The units in which the weight should be returned. Options are g, kg for grams and kilograms respectively. Deafults to kg.
Raises:
- AssertionError: Raised if this field was not given by the API or
- could not be parsed as expected.
Returns:
Taxon weight (kg) if available. See metadata. Always returns 0.
966 def get_count(self) -> float: 967 """Get the value of field count with validity assert. 968 969 Raises: 970 AssertionError: Raised if this field was not given by the API or 971 could not be parsed as expected. 972 973 Returns: 974 Always returns 0 975 """ 976 return 0
Get the value of field count with validity assert.
Raises:
- AssertionError: Raised if this field was not given by the API or
- could not be parsed as expected.
Returns:
Always returns 0
978 def get_bottom_temperature(self, units='c') -> float: 979 """Get the value of field bottom_temperature_c with validity assert. 980 981 Args: 982 units: The units in which the temperature should be returned. 983 Options: c or f for Celcius and Fahrenheit respectively. 984 Defaults to c. 985 986 Raises: 987 AssertionError: Raised if this field was not given by the API or 988 could not be parsed as expected. 989 990 Returns: 991 Bottom temperature associated with observation / inferrence if 992 available. 993 """ 994 return afscgap.model.assert_float_present( 995 self.get_bottom_temperature_maybe(units=units) 996 )
Get the value of field bottom_temperature_c with validity assert.
Arguments:
- units: The units in which the temperature should be returned. Options: c or f for Celcius and Fahrenheit respectively. Defaults to c.
Raises:
- AssertionError: Raised if this field was not given by the API or
- could not be parsed as expected.
Returns:
Bottom temperature associated with observation / inferrence if available.
998 def get_surface_temperature(self, units='c') -> float: 999 """Get the value of field surface_temperature_c with validity assert. 1000 1001 Args: 1002 units: The units in which the temperature should be returned. 1003 Options: c or f for Celcius and Fahrenheit respectively. 1004 Defaults to c. 1005 1006 Raises: 1007 AssertionError: Raised if this field was not given by the API or 1008 could not be parsed as expected. 1009 1010 Returns: 1011 Surface temperature associated with observation / inferrence if 1012 available. 1013 """ 1014 return afscgap.model.assert_float_present( 1015 self.get_surface_temperature_maybe(units=units) 1016 )
Get the value of field surface_temperature_c with validity assert.
Arguments:
- units: The units in which the temperature should be returned. Options: c or f for Celcius and Fahrenheit respectively. Defaults to c.
Raises:
- AssertionError: Raised if this field was not given by the API or
- could not be parsed as expected.
Returns:
Surface temperature associated with observation / inferrence if available.
1018 def is_complete(self) -> bool: 1019 """Determine if this record has all of its values filled in. 1020 1021 Returns: 1022 True if all optional fields have a parsed value with the expected 1023 type and false otherwise. 1024 """ 1025 tsn_given = self._tsn is not None 1026 ak_survey_id_given = self._ak_survey_id is not None 1027 return tsn_given and ak_survey_id_given and self._haul.is_complete()
Determine if this record has all of its values filled in.
Returns:
True if all optional fields have a parsed value with the expected type and false otherwise.
Inherited Members
1030def parse_haul(target: dict) -> afscgap.model.Haul: 1031 """Parse a Haul record from a row in the community Hauls flat file. 1032 1033 Args: 1034 target: Dict describing a single row from the community-maintained 1035 Hauls flat file. 1036 1037 Returns: 1038 Haul record constructed from the input row. 1039 """ 1040 srvy = str(target['Srvy']) 1041 survey = str(target['Survey']) 1042 survey_id = float(target['Survey Id']) 1043 cruise = float(target['Cruise']) 1044 haul = float(target['Haul']) 1045 stratum = float(target['Stratum']) 1046 station = str(target['Station']) 1047 vessel_name = str(target['Vessel Name']) 1048 vessel_id = float(target['Vessel Id']) 1049 date_time = str(afscgap.convert.convert_to_iso8601(target['Date Time'])) 1050 latitude_dd = float(target['Latitude Dd']) 1051 longitude_dd = float(target['Longitude Dd']) 1052 bottom_temperature_c = afscgap.model.get_opt_float( 1053 target['Bottom Temperature C'] 1054 ) 1055 surface_temperature_c = afscgap.model.get_opt_float( 1056 target['Surface Temperature C'] 1057 ) 1058 depth_m = float(target['Depth M']) 1059 distance_fished_km = float(target['Distance Fished Km']) 1060 net_width_m = afscgap.model.get_opt_float(target['Net Width M']) 1061 net_height_m = afscgap.model.get_opt_float(target['Net Height M']) 1062 area_swept_ha = float(target['Area Swept Ha']) 1063 duration_hr = float(target['Duration Hr']) 1064 1065 return afscgap.model.Haul( 1066 srvy, 1067 survey, 1068 survey_id, 1069 cruise, 1070 haul, 1071 stratum, 1072 station, 1073 vessel_name, 1074 vessel_id, 1075 date_time, 1076 latitude_dd, 1077 longitude_dd, 1078 bottom_temperature_c, 1079 surface_temperature_c, 1080 depth_m, 1081 distance_fished_km, 1082 net_width_m, 1083 net_height_m, 1084 area_swept_ha, 1085 duration_hr 1086 )
Parse a Haul record from a row in the community Hauls flat file.
Arguments:
- target: Dict describing a single row from the community-maintained Hauls flat file.
Returns:
Haul record constructed from the input row.