Spaces:
Running
Running
Stephen S. Lee
feat: source-derived Q&A with statewide CA ocean species coverage (#1)
c1b067e unverified | """Phase-2 seed expansion: statewide CA ocean species (esp. Southern + inverts). | |
| Every value verified against the cited CDFW page or CCR Title 14 during the | |
| 2026-06-15 research pass. Species without a curated rule card are detected via | |
| the fact alias index, so facts alone make them answerable. | |
| """ | |
| import json | |
| from pathlib import Path | |
| FACTS_PATH = Path("data/source-cache/seed/facts.json") | |
| SOUTH = "cdfw_region_southern" | |
| SOUTH_URL = "https://wildlife.ca.gov/Fishing/Ocean/Regulations/Fishing-Map/Southern" | |
| OCEAN = "cdfw_ocean_regulations" | |
| GENERAL_URL = "https://wildlife.ca.gov/Fishing/Ocean/Regulations/Sport-Fishing/General-Ocean-Fishing-Regs" | |
| INVERT_URL = "https://wildlife.ca.gov/Fishing/Ocean/Regulations/Sport-Fishing/Invertebrate-Fishing-Regs" | |
| NOW = "2026-06-15T12:00:00+00:00" | |
| GENERAL_BAG = "10" | |
| GENERAL_BAG_UNITS = ( | |
| "fish of any one species per day, within the general limit of 20 finfish in " | |
| "combination of all species (no species-specific minimum size limit; CCR T14 27.60)" | |
| ) | |
| NO_TAKE = "Take and possession prohibited in California ocean waters; may not be taken or possessed." | |
| def f(id, species, display, fact_type, value, *, units=None, source_id, source_url, | |
| snippet, aliases=(), region_key=None, eff_start=None, eff_end=None): | |
| entry = { | |
| "id": id, "species_or_category": species, "display_name": display, | |
| "aliases": list(aliases), "area_or_scope": "California ocean sport fishing regulations", | |
| "fact_type": fact_type, "value": value, "units": units, | |
| "source_id": source_id, "source_url": source_url, "retrieved_at": NOW, | |
| "supporting_chunk_id": "", "supporting_snippet": snippet, "validation_set": False, | |
| } | |
| if region_key: | |
| entry["region_key"] = region_key | |
| if eff_start: | |
| entry["effective_start"] = eff_start | |
| if eff_end: | |
| entry["effective_end"] = eff_end | |
| return entry | |
| def general_limit(key, display, aliases): | |
| return f(f"{key}_bag_limit", key, display, "bag_limit", GENERAL_BAG, units=GENERAL_BAG_UNITS, | |
| source_id=OCEAN, source_url=GENERAL_URL, aliases=aliases, | |
| snippet="No more than 20 finfish in combination of all species, with not more than 10 of any one species, may be taken or possessed (CCR T14 27.60).") | |
| def no_limit(key, display, aliases): | |
| return f(f"{key}_bag_limit", key, display, "bag_limit", "no daily bag limit", units="(CCR T14 27.60(b))", | |
| source_id=OCEAN, source_url=GENERAL_URL, aliases=aliases, | |
| snippet="There is no limit on the following species: anchovy, jacksmelt, topsmelt, Pacific butterfish (pompano), queenfish, sanddabs, skipjack, jack mackerel, Pacific mackerel, ... Pacific sardine ... (CCR T14 27.60(b)).") | |
| def no_take(key, display, aliases, source_url=GENERAL_URL, source_id=OCEAN, snippet=None): | |
| return f(f"{key}_closure", key, display, "closure", NO_TAKE, source_id=source_id, source_url=source_url, | |
| aliases=aliases, snippet=snippet or f"{display}: may not be taken or possessed.") | |
| NEW = [ | |
| # --- Southern California bass complex (CCR 28.30 / Southern page) --- | |
| f("kelp_bass_min_size", "kelp_bass", "Kelp bass", "minimum_size", "14", units="inches total length", | |
| source_id=SOUTH, source_url=SOUTH_URL, aliases=["calico bass", "calico", "kelp bass"], | |
| snippet="Kelp bass, barred sand bass and spotted sand bass: the minimum size limit is 14 inches total length."), | |
| f("kelp_bass_bag_limit", "kelp_bass", "Kelp bass", "bag_limit", "5", | |
| units="fish in any combination of kelp, barred sand, and spotted sand bass per day", | |
| source_id=SOUTH, source_url=SOUTH_URL, aliases=["calico bass"], | |
| snippet="Five fish in any combination of species (kelp bass, barred sand bass, spotted sand bass)."), | |
| f("barred_sand_bass_min_size", "barred_sand_bass", "Barred sand bass", "minimum_size", "14", units="inches total length", | |
| source_id=SOUTH, source_url=SOUTH_URL, aliases=["sand bass"], | |
| snippet="Kelp bass, barred sand bass and spotted sand bass: the minimum size limit is 14 inches total length."), | |
| f("barred_sand_bass_bag_limit", "barred_sand_bass", "Barred sand bass", "bag_limit", "5", | |
| units="fish in any combination of kelp/barred sand/spotted sand bass, of which no more than 4 may be barred sand bass", | |
| source_id=SOUTH, source_url=SOUTH_URL, aliases=["sand bass"], | |
| snippet="Five fish in any combination of species, except no more than 4 barred sand bass may be taken. The minimum size limit is 14 inches."), | |
| f("spotted_sand_bass_min_size", "spotted_sand_bass", "Spotted sand bass", "minimum_size", "14", units="inches total length", | |
| source_id=SOUTH, source_url=SOUTH_URL, aliases=["spotted bay bass", "spotty"], | |
| snippet="Kelp bass, barred sand bass and spotted sand bass: the minimum size limit is 14 inches total length."), | |
| f("spotted_sand_bass_bag_limit", "spotted_sand_bass", "Spotted sand bass", "bag_limit", "5", | |
| units="fish in any combination of kelp/barred sand/spotted sand bass per day", | |
| source_id=SOUTH, source_url=SOUTH_URL, aliases=["spotted bay bass"], | |
| snippet="Five fish in any combination of species (kelp bass, barred sand bass, spotted sand bass)."), | |
| # --- California sheephead (Southern page) --- | |
| f("california_sheephead_min_size", "california_sheephead", "California sheephead", "minimum_size", "12", units="inches total length", | |
| source_id=SOUTH, source_url=SOUTH_URL, aliases=["sheephead", "sheepshead"], | |
| snippet="California sheephead: 2 fish, with a minimum size limit of 12 inches total length."), | |
| f("california_sheephead_bag_limit", "california_sheephead", "California sheephead", "bag_limit", "2", units="fish per day", | |
| source_id=SOUTH, source_url=SOUTH_URL, aliases=["sheephead"], | |
| snippet="California sheephead: 2 fish, with a minimum size limit of 12 inches total length."), | |
| # --- California barracuda (CCR 28.25) --- | |
| f("california_barracuda_min_size", "california_barracuda", "California barracuda", "minimum_size", "28", | |
| units="inches total length (or 17 inches alternate length)", | |
| source_id=OCEAN, source_url=GENERAL_URL, aliases=["barracuda", "pacific barracuda"], | |
| snippet="California barracuda minimum size: twenty-eight inches total length or seventeen inches alternate length (CCR T14 28.25)."), | |
| # --- Pacific bonito (CCR 28.32) --- | |
| f("pacific_bonito_min_size", "pacific_bonito", "Pacific bonito", "minimum_size", "24", | |
| units="inches fork length or 5 pounds, except up to 5 smaller fish may be taken", | |
| source_id=OCEAN, source_url=GENERAL_URL, aliases=["bonito"], | |
| snippet="Pacific bonito minimum size: twenty-four inches fork length or five pounds, except that five fish less than twenty-four inches or five pounds may be taken (CCR T14 28.32)."), | |
| f("pacific_bonito_bag_limit", "pacific_bonito", "Pacific bonito", "bag_limit", "10", units="fish per day", | |
| source_id=OCEAN, source_url=GENERAL_URL, aliases=["bonito"], | |
| snippet="Pacific bonito limit: ten (CCR T14 28.32)."), | |
| # --- California yellowtail (Southern page) --- | |
| f("california_yellowtail_min_size", "california_yellowtail", "California yellowtail", "minimum_size", "24", | |
| units="inches fork length, except up to 5 smaller fish may be taken", | |
| source_id=SOUTH, source_url=SOUTH_URL, aliases=["yellowtail"], | |
| snippet="California yellowtail: the minimum size limit is 24 inches fork length, except that up to five fish less than 24 inches fork length may be taken."), | |
| f("california_yellowtail_bag_limit", "california_yellowtail", "California yellowtail", "bag_limit", "10", units="fish per day", | |
| source_id=SOUTH, source_url=SOUTH_URL, aliases=["yellowtail"], | |
| snippet="California yellowtail: ten fish."), | |
| # --- Ocean whitefish (Southern page) --- | |
| f("ocean_whitefish_min_size", "ocean_whitefish", "Ocean whitefish", "minimum_size", "no minimum size limit", | |
| source_id=SOUTH, source_url=SOUTH_URL, aliases=["whitefish"], | |
| snippet="Ocean whitefish: 10 fish within the general daily bag limit of 20 fish total, with no minimum size limit."), | |
| f("ocean_whitefish_bag_limit", "ocean_whitefish", "Ocean whitefish", "bag_limit", "10", | |
| units="fish within the general daily bag limit of 20 finfish total", | |
| source_id=SOUTH, source_url=SOUTH_URL, aliases=["whitefish"], | |
| snippet="Ocean whitefish: 10 fish within the general daily bag limit of 20 fish total."), | |
| # --- California grunion (CCR 28.00) --- | |
| f("california_grunion_season", "california_grunion", "California grunion", "season", | |
| "open to take by hand only, except closed (no take) from April 1 through June 30.", | |
| source_id=OCEAN, source_url=GENERAL_URL, aliases=["grunion"], eff_start="2026-04-01", eff_end="2026-06-30", | |
| snippet="It shall be unlawful to take grunion from April 1 through June 30 (CCR T14 28.00)."), | |
| f("california_grunion_bag_limit", "california_grunion", "California grunion", "bag_limit", "30", units="fish per day", | |
| source_id=OCEAN, source_url=GENERAL_URL, aliases=["grunion"], | |
| snippet="California grunion limit: 30 (CCR T14 28.00)."), | |
| # --- Giant (black) sea bass: no-take off California (CCR 28.10) --- | |
| no_take("giant_sea_bass", "Giant sea bass", ["black sea bass", "giant black sea bass"], | |
| snippet="Giant sea bass may not be taken off California; fish taken incidental to other fishing must be immediately returned to the water (CCR T14 28.10)."), | |
| # --- No-limit baitfish/pelagics (CCR 27.60(b)) --- | |
| no_limit("northern_anchovy", "Northern anchovy", ["anchovy", "anchovies"]), | |
| no_limit("pacific_sardine", "Pacific sardine", ["sardine", "sardines"]), | |
| no_limit("pacific_mackerel", "Pacific mackerel", ["mackerel", "spanish mackerel"]), | |
| no_limit("jack_mackerel", "Jack mackerel", ["jack mackerel"]), | |
| no_limit("queenfish", "Queenfish", ["queenfish", "herring queenfish"]), | |
| no_limit("pacific_butterfish", "Pacific butterfish (pompano)", ["butterfish", "pompano", "pacific pompano"]), | |
| # --- General-limit nearshore SoCal finfish (CCR 27.60) --- | |
| general_limit("white_croaker", "White croaker", ["white croaker", "kingfish", "tomcod"]), | |
| general_limit("yellowfin_croaker", "Yellowfin croaker", ["yellowfin croaker"]), | |
| general_limit("spotfin_croaker", "Spotfin croaker", ["spotfin croaker"]), | |
| general_limit("california_corbina", "California corbina", ["corbina", "corvina"]), | |
| general_limit("opaleye", "Opaleye", ["opaleye", "opal eye"]), | |
| general_limit("halfmoon", "Halfmoon", ["halfmoon", "blue perch", "catalina blue perch"]), | |
| general_limit("sargo", "Sargo", ["sargo", "china croaker"]), | |
| # --- Sharks / rays / skates --- | |
| general_limit("spiny_dogfish", "Spiny dogfish", ["spiny dogfish", "dogfish", "mud shark"]), | |
| general_limit("skates", "Skates", ["skate", "skates", "big skate", "longnose skate"]), | |
| general_limit("bat_ray", "Bat ray", ["bat ray", "bat stingray"]), | |
| general_limit("shovelnose_guitarfish", "Shovelnose guitarfish", ["guitarfish", "shovelnose guitarfish", "shovelnose shark"]), | |
| general_limit("thornback_ray", "Thornback ray", ["thornback", "thornback ray"]), | |
| general_limit("round_stingray", "Round stingray", ["round stingray", "stingray", "round ray"]), | |
| no_take("white_shark", "White shark (great white)", ["great white", "white shark", "great white shark"], | |
| source_id=SOUTH, source_url=SOUTH_URL, | |
| snippet="White sharks may not be taken or possessed at any time (CCR T14 28.06; CDFW Southern region)."), | |
| no_take("gulf_grouper", "Gulf grouper", ["gulf grouper"], | |
| snippet="Gulf grouper may not be taken or possessed (CCR T14 28.12)."), | |
| no_take("broomtail_grouper", "Broomtail grouper", ["broomtail grouper"], | |
| snippet="Broomtail grouper may not be taken or possessed (CCR T14 28.12)."), | |
| no_take("basking_shark", "Basking shark", ["basking shark"], | |
| snippet="Basking sharks are prohibited from general retention in the U.S. West Coast HMS fishery (50 CFR 660.705)."), | |
| no_take("megamouth_shark", "Megamouth shark", ["megamouth", "megamouth shark"], | |
| snippet="Megamouth sharks are prohibited from general retention in the U.S. West Coast HMS fishery (50 CFR 660.705)."), | |
| # --- Invertebrates --- | |
| f("california_spiny_lobster_min_size", "california_spiny_lobster", "California spiny lobster", "minimum_size", | |
| "3¼", units="inches carapace (straight line on the mid-line of the back, rear of eye socket to rear of body shell)", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["spiny lobster"], | |
| snippet="California spiny lobster minimum size: 3 and 1/4 inches measured on the mid-line of the back from the rear edge of the eye socket to the rear edge of the body shell (CCR T14 29.90)."), | |
| f("california_spiny_lobster_bag_limit", "california_spiny_lobster", "California spiny lobster", "bag_limit", "7", units="lobster per day", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["spiny lobster"], | |
| snippet="California spiny lobster daily bag limit: seven (CCR T14 29.90)."), | |
| f("california_spiny_lobster_season", "california_spiny_lobster", "California spiny lobster", "season", | |
| "open from 6:00 p.m. on the Friday preceding the first Wednesday in October through the first Wednesday after the 15th of March.", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["spiny lobster"], | |
| snippet="Open season: from 6:00 p.m. on the Friday before the first Wednesday in October through the first Wednesday after the 15th of March (CCR T14 29.90)."), | |
| f("rock_scallop_bag_limit", "rock_scallop", "Rock scallop", "bag_limit", "10", units="scallops per day", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["rock scallop", "scallop", "scallops"], | |
| snippet="Rock scallop limit: ten (CCR T14 29.60)."), | |
| no_take("speckled_scallop", "Speckled (bay) scallop", ["bay scallop", "speckled scallop"], | |
| source_id=OCEAN, source_url=INVERT_URL, | |
| snippet="Speckled (bay) scallops may not be taken or possessed (CCR T14 29.65)."), | |
| f("market_squid_bag_limit", "market_squid", "Market squid", "bag_limit", "no daily bag limit", units="(may be taken with hand-held dip nets)", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["squid", "calamari", "market squid"], | |
| snippet="Squid may be taken with hand-held dip nets. There is no limit (CCR T14 29.70)."), | |
| f("kellets_whelk_bag_limit", "kellets_whelk", "Kellet's whelk", "bag_limit", "35", units="whelks per day", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["whelk", "kellets whelk", "kellet's whelk"], | |
| snippet="Kellet's whelk: 35 whelks; season July 1 through the first Wednesday after the 15th of March (CCR T14 29.17, 29.05)."), | |
| f("octopus_bag_limit", "octopus", "Octopus", "bag_limit", "35", units="octopus per day", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["octopus", "octopuses", "octopi"], | |
| snippet="Octopus: 35 octopus, year round (CCR T14 29.05)."), | |
| f("native_oyster_bag_limit", "native_oyster", "Native oyster", "bag_limit", "35", units="oysters per day", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["oyster", "oysters", "native oyster"], | |
| snippet="Oysters, all: 35 oysters, year round (CCR T14 29.05)."), | |
| f("ghost_shrimp_bag_limit", "ghost_shrimp", "Ghost shrimp and blue mud shrimp", "bag_limit", "50", units="in combination per day", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["ghost shrimp", "mud shrimp", "blue mud shrimp"], | |
| snippet="Ghost shrimp and blue mud shrimp limit: fifty in combination (CCR T14 29.87)."), | |
| f("coonstripe_shrimp_bag_limit", "coonstripe_shrimp", "Coonstripe shrimp", "bag_limit", "20", | |
| units="pounds in the shell, heads on, per day", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["coonstripe shrimp"], | |
| snippet="Coonstripe shrimp: twenty pounds (in the shell, heads on) (CCR T14 29.88)."), | |
| f("sand_crab_bag_limit", "sand_crab", "Sand crab (mole crab)", "bag_limit", "50", units="per day", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["sand crab", "mole crab", "sand crabs"], | |
| snippet="Sand crabs (Emerita analoga): fifty (CCR T14 29.85)."), | |
| # --- Distinct named clams (own CCR sections) --- | |
| f("gaper_clam_bag_limit", "gaper_clam", "Gaper clam (horseneck clam)", "bag_limit", "10", | |
| units="of each species per day (Humboldt Bay: 50 in combination, max 25 gaper; Elkhorn Slough: 12 in combination)", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["gaper clam", "horseneck clam", "horse clam"], | |
| snippet="Gaper/horseneck clams: limit ten of each species, except in Humboldt Bay fifty in combination (no more than 25 gaper) and in Elkhorn Slough twelve in combination (CCR T14 29.25)."), | |
| f("geoduck_clam_bag_limit", "geoduck_clam", "Geoduck clam", "bag_limit", "3", units="clams per day", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["geoduck", "geoduck clam"], | |
| snippet="Geoduck clams: limit three; the first three dug must be retained as the bag limit (CCR T14 29.30)."), | |
| f("pismo_clam_min_size", "pismo_clam", "Pismo clam", "minimum_size", "5", units="inches greatest diameter", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["pismo clam"], | |
| snippet="Pismo clams: minimum size five inches in greatest diameter; limit ten (CCR T14 29.40)."), | |
| f("pismo_clam_bag_limit", "pismo_clam", "Pismo clam", "bag_limit", "10", units="clams per day", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["pismo clam"], | |
| snippet="Pismo clams: limit ten (CCR T14 29.40)."), | |
| f("razor_clam_bag_limit", "razor_clam", "Razor clam", "bag_limit", "20", | |
| units="clams per day (the first 20 dug must be retained regardless of size or condition)", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["razor clam", "razor clams"], | |
| snippet="Razor clams: limit twenty; the first twenty dug must be retained as the bag limit (CCR T14 29.45)."), | |
| f("littleneck_clam_bag_limit", "littleneck_clam", "Littleneck, cockle, and softshell clams", "bag_limit", "50", | |
| units="in combination per day (minimum size 1½ inches, except no size limit for soft-shell clams)", | |
| source_id=OCEAN, source_url=INVERT_URL, aliases=["littleneck clam", "cockle", "softshell clam", "soft-shell clam", "quahog", "chione"], | |
| snippet="Littleneck clams, soft-shell clams, chiones, northern quahogs and cockles: limit fifty in combination; minimum size one and one-half inches, except no size limit for soft-shell clams (CCR T14 29.35)."), | |
| ] | |
| def main(): | |
| data = json.loads(FACTS_PATH.read_text()) | |
| by_id = {f["id"]: i for i, f in enumerate(data["structured_facts"])} | |
| added = updated = 0 | |
| for entry in NEW: | |
| if entry["id"] in by_id: | |
| data["structured_facts"][by_id[entry["id"]]] = entry | |
| updated += 1 | |
| else: | |
| data["structured_facts"].append(entry) | |
| added += 1 | |
| FACTS_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n") | |
| species = {f["species_or_category"] for f in data["structured_facts"]} | |
| print(f"added {added}, updated {updated}; total facts {len(data['structured_facts'])}, distinct species {len(species)}") | |
| if __name__ == "__main__": | |
| main() | |