Repository: natcap/invest (python)

Issue:
Standardize field names in biophysical tables
If a biophysical table is an invest input that has a row for each landcover type, then all tables need a landcover column. Most models call this column `lucode`, but some do not:

* Habitat Quality: `lulc`
* Coastal Blue Carbon: `code`
* Crop Production: this one is kinda weird. There is a whole table devoted to mapping `lucode` to `crop_name`, and then the biophysical table (fertilization rate table) uses `crop_name`. Probably there is a reason for this, so maybe it's okay not to touch it.

I propose we change to `lucode`. And I guess that would be a breaking change so should go in a minor release. 

Pull request description:
Standardize LULC column names to `lucode`
## Description
Fixes #1249. Specifically, Habitat Quality & Coastal Blue Carbon now use `lucode` as column names where these were previously called `lulc`, `code`, or `lulc_code`.

## Checklist
- [x] Updated HISTORY.rst and link to any relevant issue (if these changes are user-facing)
- [x] Updated sample data and revision hash in Makefile
- [x] Updated test data and revision hash in Makefile
- [x] Updated the user's guide (see [invest.users-guide PR #151](https://github.com/natcap/invest.users-guide/pull/151))
- [x] Update user guide revision hash in Makefile (after the UG PR has been merged)
- [x] Tested the Workbench UI (no UI changes, but I did confirm the models run successfully via the workbench)


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 8419211bd..6b7743bd4 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -35,9 +35,20 @@
 
 .. :changelog:
 
-..
-  Unreleased Changes
-  ------------------
+
+Unreleased Changes
+------------------
+* Coastal Blue Carbon
+    * The ``code`` column in the model's biophysical table input, as well as
+      the ``code`` column in the preprocessor's LULC lookup table input and
+      ``carbon_pool_transient_template`` output, have been renamed ``lucode``,
+      for consistency with other InVEST models (`InVEST #1249
+      <https://github.com/natcap/invest/issues/1249>`_).
+* Habitat Quality
+    * The ``lulc`` column in the sensitivity table input, and the ``lulc_code``
+      column in the rarity table outputs, have been renamed ``lucode``, for
+      consistency with other InVEST models (`InVEST #1249
+      <https://github.com/natcap/invest/issues/1249>`_).
 
 3.14.3 (2024-12-19)
 -------------------
diff --git a/Makefile b/Makefile
index a4af63ba6..4b11c3da2 100644
--- a/Makefile
+++ b/Makefile
@@ -2,15 +2,15 @@
 DATA_DIR := data
 GIT_SAMPLE_DATA_REPO        := https://bitbucket.org/natcap/invest-sample-data.git
 GIT_SAMPLE_DATA_REPO_PATH   := $(DATA_DIR)/invest-sample-data
-GIT_SAMPLE_DATA_REPO_REV    := 0f8b41557753dad3670ba8220f41650b51435a93
+GIT_SAMPLE_DATA_REPO_REV    := dfcb820269fa50f65eed55b830314c853c2eb943
 
 GIT_TEST_DATA_REPO          := https://bitbucket.org/natcap/invest-test-data.git
 GIT_TEST_DATA_REPO_PATH     := $(DATA_DIR)/invest-test-data
-GIT_TEST_DATA_REPO_REV      := 324abde73e1d770ad75921466ecafd1ec6297752
+GIT_TEST_DATA_REPO_REV      := 4eb5a2fac4818a37663780c4cbdff1b41e7019be
 
 GIT_UG_REPO                 := https://github.com/natcap/invest.users-guide
 GIT_UG_REPO_PATH            := doc/users-guide
-GIT_UG_REPO_REV             := fd3194f35bc1652a93cf1c0241f98be1e7c9d43d
+GIT_UG_REPO_REV             := 79cc80687052c870bbba67e9ee2e68e67dcec145
 
 ENV = "./env"
 ifeq ($(OS),Windows_NT)
diff --git a/src/natcap/invest/coastal_blue_carbon/coastal_blue_carbon.py b/src/natcap/invest/coastal_blue_carbon/coastal_blue_carbon.py
index 7e118cdd5..5423908ec 100644
--- a/src/natcap/invest/coastal_blue_carbon/coastal_blue_carbon.py
+++ b/src/natcap/invest/coastal_blue_carbon/coastal_blue_carbon.py
@@ -207,9 +207,9 @@ MODEL_SPEC = {
         "biophysical_table_path": {
             "name": gettext("biophysical table"),
             "type": "csv",
-            "index_col": "code",
+            "index_col": "lucode",
             "columns": {
-                "code": {
+                "lucode": {
                     "type": "integer",
                     "about": gettext(
                         "The LULC code that represents this LULC "
@@ -1679,7 +1679,7 @@ def _track_disturbance(
         disturbed_carbon_volume[:] = NODATA_FLOAT32_MIN
         disturbed_carbon_volume[
             ~pygeoprocessing.array_equals_nodata(disturbance_magnitude_matrix,
-                           NODATA_FLOAT32_MIN)] = 0.0
+                                                 NODATA_FLOAT32_MIN)] = 0.0
 
         if year_of_disturbance_band:
             known_transition_years_matrix = (
diff --git a/src/natcap/invest/coastal_blue_carbon/preprocessor.py b/src/natcap/invest/coastal_blue_carbon/preprocessor.py
index 03fda7714..d78191711 100644
--- a/src/natcap/invest/coastal_blue_carbon/preprocessor.py
+++ b/src/natcap/invest/coastal_blue_carbon/preprocessor.py
@@ -36,9 +36,9 @@ MODEL_SPEC = {
                 "A table mapping LULC codes from the snapshot rasters to the "
                 "corresponding LULC class names, and whether or not the "
                 "class is a coastal blue carbon habitat."),
-            "index_col": "code",
+            "index_col": "lucode",
             "columns": {
-                "code": {
+                "lucode": {
                     "type": "integer",
                     "about": gettext(
                         "LULC code. Every value in the "
@@ -86,9 +86,9 @@ MODEL_SPEC = {
             "index_col": "lulc-class",
             "columns": {
                 "lulc-class": {
-                    "type": "integer",
+                    "type": "freestyle_string",
                     "about": gettext(
-                        "LULC codes matching the codes in the biophysical "
+                        "LULC class names matching those in the biophysical "
                         "table.")},
                 "[LULC]": {
                     "type": "option_string",
@@ -114,7 +114,7 @@ MODEL_SPEC = {
                 "Table mapping each LULC type to impact and accumulation "
                 "information. This is a template that you will fill out to "
                 "create the biophysical table input to the main model."),
-            "index_col": "code",
+            "index_col": "lucode",
             "columns": {
                 **BIOPHYSICAL_COLUMNS_SPEC,
                 # remove "expression" property which doesn't go in output spec
@@ -394,7 +394,7 @@ def _create_biophysical_table(landcover_df, target_biophysical_table_path):
             # have commas between fields.
             row = []
             for colname in target_column_names:
-                if colname == 'code':
+                if colname == 'lucode':
                     row.append(str(lulc_code))
                 else:
                     try:
diff --git a/src/natcap/invest/habitat_quality.py b/src/natcap/invest/habitat_quality.py
index c167bca01..08eacd90c 100644
--- a/src/natcap/invest/habitat_quality.py
+++ b/src/natcap/invest/habitat_quality.py
@@ -172,9 +172,9 @@ MODEL_SPEC = {
         },
         "sensitivity_table_path": {
             "type": "csv",
-            "index_col": "lulc",
+            "index_col": "lucode",
             "columns": {
-                "lulc": spec_utils.LULC_TABLE_COLUMN,
+                "lucode": spec_utils.LULC_TABLE_COLUMN,
                 "name": {
                     "type": "freestyle_string",
                     "required": False
@@ -282,9 +282,9 @@ MODEL_SPEC = {
                 "rarity_c.csv": {
                     "about": ("Table of rarity values by LULC code for the "
                               "current landscape."),
-                    "index_col": "lulc_code",
+                    "index_col": "lucode",
                     "columns": {
-                        "lulc_code": {
+                        "lucode": {
                             "type": "number",
                             "units": u.none,
                             "about": "LULC class",
@@ -314,9 +314,9 @@ MODEL_SPEC = {
                 "rarity_f.csv": {
                     "about": ("Table of rarity values by LULC code for the "
                               "future landscape."),
-                    "index_col": "lulc_code",
+                    "index_col": "lucode",
                     "columns": {
-                        "lulc_code": {
+                        "lucode": {
                             "type": "number",
                             "units": u.none,
                             "about": "LULC class",
@@ -986,7 +986,7 @@ def _generate_rarity_csv(rarity_dict, target_csv_path):
     lulc_codes = sorted(rarity_dict)
     with open(target_csv_path, 'w', newline='') as csvfile:
         writer = csv.writer(csvfile, delimiter=',')
-        writer.writerow(['lulc_code', 'rarity_value'])
+        writer.writerow(['lucode', 'rarity_value'])
         for lulc_code in lulc_codes:
             writer.writerow([lulc_code, rarity_dict[lulc_code]])
 


Test patch:
diff --git a/tests/test_coastal_blue_carbon.py b/tests/test_coastal_blue_carbon.py
index 27999bbb8..18b062811 100644
--- a/tests/test_coastal_blue_carbon.py
+++ b/tests/test_coastal_blue_carbon.py
@@ -77,7 +77,7 @@ class TestPreprocessor(unittest.TestCase):
         with open(lulc_attributes_path, 'w') as attributes_table:
             attributes_table.write(textwrap.dedent(
                 """\
-                lulc-class,code,is_coastal_blue_carbon_habitat
+                lulc-class,lucode,is_coastal_blue_carbon_habitat
                 mangrove,1,TRUE
                 parkinglot,2,FALSE
                 """))
@@ -156,7 +156,7 @@ class TestPreprocessor(unittest.TestCase):
         expected_landcover_codes = set(range(0, 24))
         found_landcover_codes = set(pandas.read_csv(
             os.path.join(outputs_dir, 'carbon_biophysical_table_template_150225.csv')
-        )['code'].values)
+        )['lucode'].values)
         self.assertEqual(expected_landcover_codes, found_landcover_codes)
 
     def test_transition_table(self):
@@ -186,7 +186,7 @@ class TestPreprocessor(unittest.TestCase):
         landcover_table_path = os.path.join(self.workspace_dir,
                                             'lulc_table.csv')
         with open(landcover_table_path, 'w') as lulc_csv:
-            lulc_csv.write('code,lulc-class,is_coastal_blue_carbon_habitat\n')
+            lulc_csv.write('lucode,lulc-class,is_coastal_blue_carbon_habitat\n')
             lulc_csv.write('0,mangrove,True\n')
             lulc_csv.write('1,parking lot,False\n')
 
@@ -430,7 +430,7 @@ class TestCBC2(unittest.TestCase):
         wkt = srs.ExportToWkt()
 
         biophysical_table = [
-            ['code', 'lulc-class', 'biomass-initial', 'soil-initial',
+            ['lucode', 'lulc-class', 'biomass-initial', 'soil-initial',
                 'litter-initial', 'biomass-half-life',
                 'biomass-low-impact-disturb', 'biomass-med-impact-disturb',
                 'biomass-high-impact-disturb', 'biomass-yearly-accumulation',
diff --git a/tests/test_habitat_quality.py b/tests/test_habitat_quality.py
index db5d6b5d5..fd3179797 100644
--- a/tests/test_habitat_quality.py
+++ b/tests/test_habitat_quality.py
@@ -14,6 +14,7 @@ from shapely.geometry import Polygon
 
 gdal.UseExceptions()
 
+
 def make_raster_from_array(
         base_array, base_raster_path, nodata_val=-1, gdal_type=gdal.GDT_Int32):
     """Make a raster from an array on a designated path.
@@ -133,14 +134,14 @@ def make_sensitivity_samp_csv(
     """
     if include_threat:
         with open(csv_path, 'w') as open_table:
-            open_table.write('LULC,NAME,HABITAT,threat_1,threat_2\n')
+            open_table.write('lucode,name,habitat,threat_1,threat_2\n')
             open_table.write('1,"lulc 1",1,1,1\n')
             if not missing_lines:
                 open_table.write('2,"lulc 2",0.5,0.5,1\n')
                 open_table.write('3,"lulc 3",0,0.3,1\n')
     else:
         with open(csv_path, 'w') as open_table:
-            open_table.write('LULC,NAME,HABITAT\n')
+            open_table.write('lucode,name,habitat\n')
             open_table.write('1,"lulc 1",1\n')
             if not missing_lines:
                 open_table.write('2,"lulc 2",0.5\n')
@@ -266,11 +267,11 @@ class HabitatQualityTests(unittest.TestCase):
             with open(csv_path, newline='') as csvfile:
                 reader = csv.DictReader(csvfile, delimiter=',')
                 self.assertEqual(reader.fieldnames,
-                                 ['lulc_code', 'rarity_value'])
+                                 ['lucode', 'rarity_value'])
                 for (exp_lulc, exp_rarity,
                      places_to_round) in expected_csv_values[csv_filename]:
                     row = next(reader)
-                    self.assertEqual(int(row['lulc_code']), exp_lulc)
+                    self.assertEqual(int(row['lucode']), exp_lulc)
                     self.assertAlmostEqual(float(row['rarity_value']),
                                            exp_rarity, places_to_round)
 
@@ -599,7 +600,7 @@ class HabitatQualityTests(unittest.TestCase):
 
         with open(args['sensitivity_table_path'], 'w') as open_table:
             open_table.write(
-                'LULC,NAME,HABITAT,%s,%s\n' % tuple(threatnames))
+                'lucode,name,habitat,%s,%s\n' % tuple(threatnames))
             open_table.write('1,"lulc 1",1,1,1\n')
             open_table.write('2,"lulc 2",0.5,0.5,1\n')
             open_table.write('3,"lulc 3",0,0.3,1\n')
@@ -1046,7 +1047,7 @@ class HabitatQualityTests(unittest.TestCase):
             args['workspace_dir'], 'sensitivity_samp.csv')
 
         with open(args['sensitivity_table_path'], 'w') as open_table:
-            open_table.write('Lulc,Name,habitat,Threat_1,THREAT_2\n')
+            open_table.write('LuCode,Name,habitat,Threat_1,THREAT_2\n')
             open_table.write('1,"lulc 1",1,1,1\n')
             open_table.write('2,"lulc 2",0.5,0.5,1\n')
             open_table.write('3,"lulc 3",0,0.3,1\n')
@@ -2135,7 +2136,7 @@ class HabitatQualityTests(unittest.TestCase):
         self.assertEqual(validate_result, expected)
 
     def test_habitat_quality_missing_lulc_val_in_sens_table(self):
-        """Habitat Quality: test for empty value in LULC column of
+        """Habitat Quality: test for empty value in lucode column of
         sensitivity table. Expects TypeError"""
         from natcap.invest import habitat_quality
 
@@ -2154,9 +2155,9 @@ class HabitatQualityTests(unittest.TestCase):
         args['sensitivity_table_path'] = os.path.join(
             args['workspace_dir'], 'sensitivity_samp.csv')
         with open(args['sensitivity_table_path'], 'w') as open_table:
-            open_table.write('LULC,NAME,HABITAT,threat_1,threat_2\n')
+            open_table.write('lucode,name,habitat,threat_1,threat_2\n')
             open_table.write('1,"lulc 1",1,1,1\n')
-            open_table.write(',"lulc 2",0.5,0.5,1\n')  # missing LULC value
+            open_table.write(',"lulc 2",0.5,0.5,1\n')  # missing lucode value
             open_table.write('3,"lulc 3",0,0.3,1\n')
 
         make_threats_raster(