Repository: USEPA/harmonize-wq (language: python)

Issue:
Add QA flag for input lat/lon with limited precision.
Closes #5. The default is for <3 decimal places to be flagged, this should be enough to catch locational information that gets misleading precision added during transformation between datums. Caution should be used if setting the limit to more decimal places as float64 is limited in it's representation. [Decimal](https://docs.python.org/3/library/decimal.html) may be a better way to deal with that in the future, but it would need to be used throughout, and implications of datatype stored in WQP, assigned by pandas, and compatibility with pint all to be considered.


Discussion / hints:

Fix patch:
diff --git a/demos/Harmonize_Pensacola.ipynb b/demos/Harmonize_Pensacola.ipynb
index d35c1e5..e3883a9 100644
--- a/demos/Harmonize_Pensacola.ipynb
+++ b/demos/Harmonize_Pensacola.ipynb
@@ -33,7 +33,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "#### Check requirements.txt for additional dependencies to install."
    ]
@@ -41,17 +43,20 @@
   {
    "cell_type": "code",
    "execution_count": 1,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "# Use pip to install the package from github\n",
-    "#!pip install git+git://github.com/USEPA/harmonize-wq.git\n",
-    "#!pip install git+https://github.com/USEPA/harmonize-wq.git#egg=harmonize-wq"
+    "#!pip install git+https://github.com/USEPA/harmonize-wq.git"
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "#### dataretrieval Query for a geojson"
    ]
@@ -59,7 +64,9 @@
   {
    "cell_type": "code",
    "execution_count": 2,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -527,8 +534,7 @@
     "from harmonize_wq import wrangle\n",
     "\n",
     "# File for area of interest\n",
-    "#aoi_url = r'https://github.com/USEPA/harmonize-wq/raw/master/harmonize_wq/tests/data/PPBays_NCCA.geojson'\n",
-    "aoi_url = r'D:\\code\\harmonize-wq\\harmonize_wq\\tests\\data\\PPBays_NCCA.geojson'  #Local file (temporary)\n",
+    "aoi_url = 'https://raw.githubusercontent.com/USEPA/harmonize-wq/main/harmonize_wq/tests/data/PPBays_NCCA.geojson'\n",
     "\n",
     "# Build query\n",
     "query = {'characteristicName': ['Temperature, water',\n",
@@ -1144,13 +1150,20 @@
     "gdf_temperature.plot(column='mean', cmap='OrRd', legend=True)"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Detailed example workflow for each characteristic"
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {
-    "hidden": true
+    "heading_collapsed": true
    },
    "source": [
-    "##### import the required libraries. Check requirements.txt for dependencies that should be installed."
+    "#### import the required libraries. Check requirements.txt for dependencies that should be installed."
    ]
   },
   {
@@ -1173,7 +1186,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "#### Download location data using dataretrieval"
    ]
@@ -1181,7 +1196,9 @@
   {
    "cell_type": "code",
    "execution_count": 8,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "from harmonize_wq import wrangle\n",
@@ -1195,7 +1212,9 @@
   {
    "cell_type": "code",
    "execution_count": 9,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -1222,8 +1241,7 @@
    ],
    "source": [
     "# Read geometry for Area of Interest from test data or local file\n",
-    "#aoi_url = r'https://github.com/USEPA/harmonize-wq/raw/master/harmonize_wq/tests/data/PPBays_NCCA.geojson'\n",
-    "aoi_url = r'D:\\code\\harmonize-wq\\harmonize_wq\\tests\\data\\PPBays_NCCA.geojson'  #Local file (temporary)\n",
+    "aoi_url = 'https://raw.githubusercontent.com/USEPA/harmonize-wq/main/harmonize_wq/tests/data/PPBays_NCCA.geojson'\n",
     "aoi_gdf = wrangle.as_gdf(aoi_url)\n",
     "\n",
     "# Map aoi\n",
@@ -1233,7 +1251,9 @@
   {
    "cell_type": "code",
    "execution_count": 10,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -1307,7 +1327,9 @@
   {
    "cell_type": "code",
    "execution_count": 11,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "# Spatial query parameters (Note: first poly only)\n",
@@ -1317,7 +1339,9 @@
   {
    "cell_type": "code",
    "execution_count": 12,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "# Build query\n",
@@ -1341,7 +1365,9 @@
   {
    "cell_type": "code",
    "execution_count": 13,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "# Query stations (can be slow)\n",
@@ -1351,7 +1377,9 @@
   {
    "cell_type": "code",
    "execution_count": 14,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -1372,7 +1400,9 @@
   {
    "cell_type": "code",
    "execution_count": 15,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -1631,7 +1661,9 @@
   {
    "cell_type": "code",
    "execution_count": 16,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -1655,7 +1687,9 @@
   {
    "cell_type": "code",
    "execution_count": 17,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "# Harmonize location datums to 4326 (Note we keep intermediate columns using intermediate_columns=True)\n",
@@ -1665,7 +1699,9 @@
   {
    "cell_type": "code",
    "execution_count": 18,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "location.harmonize_locations?"
@@ -1674,7 +1710,9 @@
   {
    "cell_type": "code",
    "execution_count": 19,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -1695,7 +1733,9 @@
   {
    "cell_type": "code",
    "execution_count": 20,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -1721,7 +1761,9 @@
   {
    "cell_type": "code",
    "execution_count": 21,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -1742,7 +1784,9 @@
   {
    "cell_type": "code",
    "execution_count": 22,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -1763,7 +1807,9 @@
   {
    "cell_type": "code",
    "execution_count": 23,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -1795,7 +1841,9 @@
   {
    "cell_type": "code",
    "execution_count": 24,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "# Clip to area of interest\n",
@@ -1806,7 +1854,9 @@
   {
    "cell_type": "code",
    "execution_count": 25,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -1839,7 +1889,9 @@
   {
    "cell_type": "code",
    "execution_count": 26,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -1860,7 +1912,9 @@
   {
    "cell_type": "code",
    "execution_count": 27,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "# To save the results to a shapefile\n",
@@ -1870,7 +1924,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "#### Retrieve Characteristic Data"
    ]
@@ -1878,7 +1934,9 @@
   {
    "cell_type": "code",
    "execution_count": 28,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -1898,7 +1956,9 @@
   {
    "cell_type": "code",
    "execution_count": 29,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -2374,7 +2434,9 @@
   {
    "cell_type": "code",
    "execution_count": 30,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -2432,15 +2494,18 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
-    "##### secchi disk depth"
+    "###### secchi disk depth"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": 32,
    "metadata": {
+    "hidden": true,
     "scrolled": true
    },
    "outputs": [
@@ -2494,7 +2559,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "The threshold is based on standard deviations and is currently only used in the histogram."
    ]
@@ -2502,7 +2569,9 @@
   {
    "cell_type": "code",
    "execution_count": 33,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -2683,7 +2752,9 @@
   {
    "cell_type": "code",
    "execution_count": 34,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -2944,7 +3015,9 @@
   {
    "cell_type": "code",
    "execution_count": 35,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -2965,7 +3038,9 @@
   {
    "cell_type": "code",
    "execution_count": 36,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -3225,7 +3300,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "All these were missing both a value and unit so nothing could be done"
    ]
@@ -3233,7 +3310,9 @@
   {
    "cell_type": "code",
    "execution_count": 37,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -3363,7 +3442,9 @@
   {
    "cell_type": "code",
    "execution_count": 38,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -3397,7 +3478,9 @@
   {
    "cell_type": "code",
    "execution_count": 39,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -3430,14 +3513,18 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
-    "##### Temperature"
+    "###### Temperature"
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "The default error='raise', makes it so that there is an error when there is a dimensionality error (i.e. when units can't be converted). Here we would get the error: DimensionalityError: Cannot convert from 'count' (dimensionless) to 'degree_Celsius' ([temperature])"
    ]
@@ -3445,7 +3532,9 @@
   {
    "cell_type": "code",
    "execution_count": 40,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -3496,7 +3585,9 @@
   {
    "cell_type": "code",
    "execution_count": 41,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -3689,7 +3780,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "In the above we can see examples where the results were in deg F and in the result field they've been converted into degree_Celsius"
    ]
@@ -3697,7 +3790,9 @@
   {
    "cell_type": "code",
    "execution_count": 42,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -3885,7 +3980,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "We can see where the units were missing, the results were assumed to be in degree_Celsius already"
    ]
@@ -3893,7 +3990,9 @@
   {
    "cell_type": "code",
    "execution_count": 43,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -3914,7 +4013,9 @@
   {
    "cell_type": "code",
    "execution_count": 44,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -3983,7 +4084,9 @@
   {
    "cell_type": "code",
    "execution_count": 45,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -4113,7 +4216,9 @@
   {
    "cell_type": "code",
    "execution_count": 46,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -4147,7 +4252,9 @@
   {
    "cell_type": "code",
    "execution_count": 47,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -4180,7 +4287,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "###### Dissolved oxygen"
    ]
@@ -4188,7 +4297,9 @@
   {
    "cell_type": "code",
    "execution_count": 48,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -4206,7 +4317,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "Note: Imediately when we run a harmonization function without the intermediate fields they're deleted."
    ]
@@ -4214,7 +4327,9 @@
   {
    "cell_type": "code",
    "execution_count": 49,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -4383,7 +4498,9 @@
   {
    "cell_type": "code",
    "execution_count": 50,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -4574,7 +4691,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "Though there were no results in %, the conversion from percent saturation (%) to mg/l is special. This equation is being improved by integrating tempertaure and pressure instead of assuming STP (see DO_saturation())"
    ]
@@ -4582,7 +4701,9 @@
   {
    "cell_type": "code",
    "execution_count": 51,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -4712,7 +4833,9 @@
   {
    "cell_type": "code",
    "execution_count": 52,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -4746,7 +4869,9 @@
   {
    "cell_type": "code",
    "execution_count": 53,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -4779,7 +4904,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "###### pH"
    ]
@@ -4787,7 +4914,9 @@
   {
    "cell_type": "code",
    "execution_count": 54,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -4836,7 +4965,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "Note the warnings that occur when a unit is not recognized by the package. These occur even when report=False. Future versions could include these as defined units for pH, but here it wouldn't alter results."
    ]
@@ -4844,7 +4975,9 @@
   {
    "cell_type": "code",
    "execution_count": 55,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -4997,14 +5130,18 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "'None' is uninterpretable and replaced with NaN, which then gets replaced with 'dimensionless' since pH is unitless"
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "###### Salinity"
    ]
@@ -5012,7 +5149,9 @@
   {
    "cell_type": "code",
    "execution_count": 56,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -5062,7 +5201,9 @@
   {
    "cell_type": "code",
    "execution_count": 57,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -5216,7 +5357,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "###### Nitrogen"
    ]
@@ -5224,7 +5367,9 @@
   {
    "cell_type": "code",
    "execution_count": 58,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -5276,7 +5421,9 @@
   {
    "cell_type": "code",
    "execution_count": 59,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -5443,7 +5590,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "###### Conductivity"
    ]
@@ -5451,7 +5600,9 @@
   {
    "cell_type": "code",
    "execution_count": 60,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -5499,7 +5650,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "Note: No report because too few results"
    ]
@@ -5507,7 +5660,9 @@
   {
    "cell_type": "code",
    "execution_count": 61,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -5651,7 +5806,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "###### Chlorophyll a"
    ]
@@ -5659,7 +5816,9 @@
   {
    "cell_type": "code",
    "execution_count": 62,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -5713,7 +5872,9 @@
   {
    "cell_type": "code",
    "execution_count": 63,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -5880,7 +6041,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "###### Organic Carbon"
    ]
@@ -5888,7 +6051,9 @@
   {
    "cell_type": "code",
    "execution_count": 64,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -5938,7 +6103,9 @@
   {
    "cell_type": "code",
    "execution_count": 65,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -6092,7 +6259,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "###### Turbidity"
    ]
@@ -6100,7 +6269,9 @@
   {
    "cell_type": "code",
    "execution_count": 66,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -6150,7 +6321,9 @@
   {
    "cell_type": "code",
    "execution_count": 67,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -6304,7 +6477,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "###### Sediment"
    ]
@@ -6312,7 +6487,9 @@
   {
    "cell_type": "code",
    "execution_count": 68,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "# Sediment\n",
@@ -6322,7 +6499,9 @@
   {
    "cell_type": "code",
    "execution_count": 69,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -6392,7 +6571,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "###### Phosphorus"
    ]
@@ -6400,7 +6581,9 @@
   {
    "cell_type": "code",
    "execution_count": 70,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -6431,7 +6614,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "Note the warnings for unexpected characteristic fractions. Fractions are each seperated out into their own result column."
    ]
@@ -6439,7 +6624,9 @@
   {
    "cell_type": "code",
    "execution_count": 71,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -6735,7 +6922,9 @@
   {
    "cell_type": "code",
    "execution_count": 72,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -6903,7 +7092,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "###### Fecal Coliform"
    ]
@@ -6911,7 +7102,9 @@
   {
    "cell_type": "code",
    "execution_count": 73,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -6960,7 +7153,9 @@
   {
    "cell_type": "code",
    "execution_count": 74,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -7127,7 +7322,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "###### Escherichia coli"
    ]
@@ -7135,7 +7332,9 @@
   {
    "cell_type": "code",
    "execution_count": 75,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -7184,7 +7383,9 @@
   {
    "cell_type": "code",
    "execution_count": 76,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -7338,21 +7539,27 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "### Combining Salinity and Conductivity"
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "Convert module has various functions to convert from one unit or characteristic to another. Some of these are used within a single characteristic during harmonization (e.g. DO saturation to concentration) while others are intended to model one characteristic as an indicator of another (e.g. estimate salinity from conductivity)."
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "Note: this should only be done after both characteristic fields have been harmonized. Results before and after should be inspected, thresholds for outliers applied, and consider adding a QA_flag for modeled data."
    ]
@@ -7360,7 +7567,9 @@
   {
    "cell_type": "code",
    "execution_count": 77,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "from harmonize_wq import convert"
@@ -7368,7 +7577,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "There are not enough conductivity results to demonstrate this fully in pensacola Bay. We'll mainly just explore Salinity results."
    ]
@@ -7376,7 +7587,9 @@
   {
    "cell_type": "code",
    "execution_count": 78,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "#Notes:\n",
@@ -7387,7 +7600,9 @@
   {
    "cell_type": "code",
    "execution_count": 79,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stdout",
@@ -7410,7 +7625,9 @@
   {
    "cell_type": "code",
    "execution_count": 80,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -7431,7 +7648,9 @@
   {
    "cell_type": "code",
    "execution_count": 81,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -7497,7 +7716,9 @@
   {
    "cell_type": "code",
    "execution_count": 82,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -7559,7 +7780,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "Nothing jumps out as problematic for either value"
    ]
@@ -7567,7 +7790,9 @@
   {
    "cell_type": "code",
    "execution_count": 83,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -7590,7 +7815,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "Conductivity thresholds from Freshwater Explorer: 10 > x < 5000 us/cm. The value we have is well within that."
    ]
@@ -7598,7 +7825,9 @@
   {
    "cell_type": "code",
    "execution_count": 84,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -7673,7 +7902,9 @@
   {
    "cell_type": "code",
    "execution_count": 85,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -7697,21 +7928,27 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "0.083 is close to expected PSU"
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "### Datetime"
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "datetime() formats time using dataretrieval and ActivityStart"
    ]
@@ -7719,7 +7956,9 @@
   {
    "cell_type": "code",
    "execution_count": 86,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -7850,7 +8089,9 @@
   {
    "cell_type": "code",
    "execution_count": 87,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -7968,14 +8209,18 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "Activity_datetime combines all three time component columns into UTC. If time is missing this is NaT so a startDate column is used to preserve date only."
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "### Depth"
    ]
@@ -7983,7 +8228,9 @@
   {
    "cell_type": "code",
    "execution_count": 88,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stderr",
@@ -8024,14 +8271,18 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "Data are often lacking sample depth metadata, even this small number including depth is good"
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "heading_collapsed": true
+   },
    "source": [
     "### Characteristic to Column (long to wide format)"
    ]
@@ -8039,7 +8290,9 @@
   {
    "cell_type": "code",
    "execution_count": 89,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -8477,7 +8730,9 @@
   {
    "cell_type": "code",
    "execution_count": 90,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stdout",
@@ -8495,7 +8750,9 @@
   {
    "cell_type": "code",
    "execution_count": 91,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "name": "stdout",
@@ -8513,7 +8770,9 @@
   {
    "cell_type": "code",
    "execution_count": 92,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -8566,7 +8825,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "source": [
     "Next the table is divided into the columns of interest (main_df) and characteristic specific metadata (chars_df)"
    ]
@@ -8574,7 +8835,9 @@
   {
    "cell_type": "code",
    "execution_count": 93,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [],
    "source": [
     "# split table into main and characteristics tables\n",
@@ -8584,7 +8847,9 @@
   {
    "cell_type": "code",
    "execution_count": 94,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -8617,7 +8882,9 @@
   {
    "cell_type": "code",
    "execution_count": 95,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -8848,7 +9115,9 @@
   {
    "cell_type": "code",
    "execution_count": 96,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
@@ -8881,7 +9150,9 @@
   {
    "cell_type": "code",
    "execution_count": 97,
-   "metadata": {},
+   "metadata": {
+    "hidden": true
+   },
    "outputs": [
     {
      "data": {
diff --git a/harmonize_wq/clean.py b/harmonize_wq/clean.py
index 4e7f697..cd0dbd4 100644
--- a/harmonize_wq/clean.py
+++ b/harmonize_wq/clean.py
@@ -34,8 +34,7 @@ def datetime(df_in):
                       'ActivityStartTime/Time',
                       'ActivityStartTime/TimeZoneCode')
     df_out = df_in.copy()
-    # Backup date (if time is NA datetime is NaT)
-    df_out['StartDate'] = df_out[date]
+    # NOTE: even if date, if time is NA datetime is NaT
     df_out = dataretrieval.utils.format_datetime(df_out, date, time, tz)
     df_out = df_out.rename(columns={'datetime': 'Activity_datetime'})
 
@@ -79,6 +78,33 @@ def harmonize_depth(df_in, units='meter'):
     return df_out
 
 
+def check_precision(df_in, col, limit=3):
+    """
+    Note - be cautious of float type and real vs representable precision
+
+    Parameters
+    ----------
+    df_in : pandas.DataFrame
+        DataFrame with the required ResultDepthHeight columns.
+    unit_col : string
+        Desired column in df_in.
+    limit : integer, optional
+        Number of decimal places under which to detect. The default is 3.
+
+    Returns
+    -------
+    df_out : pandas.DataFrame
+        Dataframe with the quality assurance flag for precision
+
+    """
+    df_out = df_in.copy()
+    # Create T/F mask based on len of everything after the decimal
+    c_mask = [len(str(x).split('.')[1]) < limit for x in df_out[col]]
+    flag = '{}: Imprecise: lessthan{}decimaldigits'.format(col, limit)
+    df_out = harmonize.add_qa_flag(df_out, c_mask, flag)  # Assign flags
+    return df_out
+
+
 def methods_check(df_in, char_val, methods=None):
     """
     Check methods against list of accepted methods.
diff --git a/harmonize_wq/domains.py b/harmonize_wq/domains.py
index 8fbeade..b10a926 100644
--- a/harmonize_wq/domains.py
+++ b/harmonize_wq/domains.py
@@ -9,8 +9,6 @@
 
 @author: jbousqui
 """
-from zipfile import ZipFile
-from io import BytesIO
 import requests
 import pint
 import pandas
@@ -64,6 +62,48 @@
              'Phosphorus': 'mg/l'
              }
 
+# Temporary (these are confirmed)
+domain_tables = {'ActivityMedia': 'ActivityMedia_CSV',
+                 'SampleFraction': 'ResultSampleFraction_CSV',
+                 'ActivityMediaSubdivision': 'ActivityMediaSubdivision_CSV',
+                 'ResultValueType': 'ResultValueType_CSV'}
+# Replaces:
+# get_ActivityMediaName():
+# get_SampleFraction():
+# get_ActivityMediaSubdivisionName():
+# get_ResultValueTypeName():
+# get_domain_list(field):
+
+def get_domain_dict(table, cols=None):
+    """
+    Retrieves domain values as dictionary for specified table
+
+    Parameters
+    ----------
+    table : string
+        csv table name (without extension).
+    cols : list, optional
+        Columns to use as {key, value}.
+        The default is None, ['Name', 'Description'].
+
+    Returns
+    -------
+    dictionary
+        Dictionary where {cols[0]: cols[1]}
+
+    """
+    if cols is None:
+        cols = ['Name', 'Description']
+    if not table.endswith('_CSV'):
+        table += '_CSV'
+    url = '{}{}.zip'.format(BASE_URL, table)
+    # Very limited url handling
+    if requests.get(url).status_code != 200:
+        status_code = requests.get(url).status_code
+        print("{} web service response {}".format(url, status_code))
+    df = pandas.read_csv(url, usecols=cols)
+    return dict(df.values)
+
 
 def registry_adds_list(out_col):
     # TODO: 'PSU' = 'PSS' ~ ppth/1.004715
@@ -318,25 +358,6 @@ def characteristic_cols(category=None):
     return col_list
 
 
-def get_ResultValueTypeName():
-    """
-    Get dictionary of possible values (key) and their descriptions (values).
-    url = '{}ResultValueType_CSV.zip'.format(BASE_URL)
-
-    Returns
-    -------
-    dict: dictionary
-        Dictionary where exhaustive {ResultValueTypeName: Description}
-    """
-
-    return {"Actual": "	Existing now; present; current:",
-            "Blank Corrected Calc": "The data were blank corrected using the recommended procedure detailed in the analytical method.",
-            "Calculated": "To ascertain by computation or determined by mathematical calculation, evaluating, and reasoning.",
-            "Control Adjusted": "Requirements are determined from the control test data",
-            "Estimated": "Approximation, educated guess, or projection of a quantity based on experience and/or information available at the time, and the cheapest (and least accurate) type of modeling.",
-            }
-
-
 def xy_datum():
     """
     Get dictionary of possible values (key) and dictionary values for their
@@ -392,151 +413,6 @@ def xy_datum():
             }
 
 
-def get_ActivityMediaName():
-    """
-    Get dictionary of possible values (key) and their descriptions (values).
-    url = '{}ActivityMedia_CSV.zip'.format(BASE_URL)
-
-    Returns
-    -------
-    dict
-        Dictionary where exhaustive {ActivityMediaName:Description}
-
-    """
-    return {'Air': "the invisible gaseous substance surrounding the earth, a mixture of nitrogen, oxygen, and minute amounts of other gases that forms earth's atmosphere",
-            'Biological': 'relating to biology or living organisms',
-            'Habitat': 'the natural home or environment of an animal, plant, or other organism.',
-            'Other': 'other; the area or natural environment in which an organism or substance is found',
-            'Sediment': "matter that settles to the bottom of a liquid; dregs; Geology Solid fragmented material, such as silt, sand, gravel, chemical precipitates, and fossil fragments, that is transported and deposited by water, ice, or wind or that accumulates through chemical precipitation or secretion by organisms, and that forms layers on the Earth's surface.",
-            'Soil': "the loose top layer of the Earth's surface, a black or dark brown material consisting of rock and mineral particles mixed with decayed organic matter (humus), and capable of retaining water, providing nutrients for plants, and supporting a wide range of biotic communities.",
-            'Tissue': 'an aggregate of cells usually of a particular kind together with their intercellular substance that form one of the structural materials of a plant or an animal',
-            'Water': 'a colorless, transparent, odorless, tasteless liquid that forms the seas, lakes, rivers, and rain and is the basis of the fluids of living organisms.'
-            }
-
-
-def get_ActivityMediaSubdivisionName():
-    """
-    Get dictionary of possible values (key) and their descriptions (values).
-    url = '{}ActivityMediaSubdivision_CSV.zip'.format(BASE_URL)
-
-    Returns
-    -------
-    dict
-        Dictionary where exhaustive {ActivityMediaSubdivisionName:Description}
-
-    """
-    return {'Air moisture': 'Water present in air in a gaseous form. Air moisture plays a significant role in weather when it changes from one state to another. These changes include condensation (cloud, fog, dew, and frost) and precipitation (rainfall and snowfall).',
-            'Ambient Air': 'refers to any unconfined portion of the atmosphere or outdoor air. basically the natural state of air in the outdoor environment',
-            'Borehole cuttings': 'Unconsolidated material removed from a pipe or casing during a drilling (coring) operation',
-            'Borrow Soil, Waste Rock, and Protore material': 'material (usually soil, gravel or sand) has been dug for use at another location.soil or Rock may contain sub-economic material from which economic mineral deposits may form by geologic concentration processes such as supergene enrichment.',
-            'Bottom material': 'A mixture of mineral and organic matter that compose the top bed deposits (usually the first few inches) underlying a body of water. Bottom material consists of living and non-living, organic and inorganic material of varying physical, chemical, and biological composition that has been transported by water, ice, and wind and deposited in aquatic systems.\r',
-            'Bulk Deposition': 'the sum of. wet-only deposition and of sedimenting (dry) particles to. a sample collector in ambient air. The fluent delivery of a stream of separate loose pieces onto a receiving surface. The relative size of the pieces is not significant, rather it is the manner in which they are handled, as a mass or stream rather than each particle being individually manipulated.',
-            'Canopy water': 'Water dripping off tree-leaf canopies or running down the trunks of trees.',
-            'Const. Material': 'any material which is used for construction purposes. Many naturally occurring substances, such as clay, rocks, sand, and wood, even twigs and leaves, have been used to construct buildings',
-            'Core material': 'Consolidated or unconsolidated material removed from a pipe or casing during a drilling (coring) operation.',
-            'Deionized Water': 'Water that has had almost all of its mineral ions removed, such as cations like sodium, calcium, iron, and copper, and anions such as chloride and sulfate.',
-            'Domestic Sewage': 'Domestic sewage means sewage that consists of water and human excretions or other waterborne wastes incidental to the occupancy of a residential building. Domestic sewage carries used water from houses and apartments; it is also called sanitary sewage.',
-            'Drinking Water': 'known as potable water or improved drinking water, is water that is safe to drink or to use for food preparation, without risk of health problems.',
-            'Dry Fall Material': 'material formulated so that when applied at specified conditions, their droplets or overspray rapidly dry into a dust-like state before reaching a certain distance',
-            'Elutriate': 'A process by which a mixture of an unconsolidated solid medium (usually soil) and a liquid medium (usually water) has been agitated for a given period of time to dissolve materials from the solid. The solid/liquid mixture is finally separated and the resulting solution is analyzed for materials dissolved during the elutriation process. separate (lighter and heavier particles in a mixture) by suspension in an upward flow of liquid or gas. purify by straining.',
-            'Filter Residue': 'the solid mass remaining on a filter after the liquid that contained it has passed through; specifically :the residue of impurities filtered from clarified juice of sugarcane that is used as a fertilizer',
-            'Finished Water': 'water that is introduced into the distribution system of a public water system and is intended for distribution and consumption without further treatment',
-            'Foam': 'a mass of small bubbles formed on or in liquid, typically by agitation or fermentation.',
-            'Groundwater': "Water below the surface of the Earth contained in the saturated zone. It does not include soil moisture or interstitial water. Water that collects or flows beneath the Earth's surface, filling the porous spaces in soil, sediment, and rocks.",
-            'Hyporheic zone': 'Near-stream subsurface environment where mixing occurs between subsurface water and surface water. Water flows not only in the open stream channel, but also through the interstices of stream-channel and bank sediments, thus creating a mixing zone with subsurface water. There is not a precise separation between groundwater and surface water, thus the hyporheic zone is not precisely defined.',
-            'Indoor Air': 'refers to the air quality within and around buildings and structures, especially as it relates to the health and comfort of building occupants.',
-            'Industrial Effluent': 'Liquid waste flowing out of a factory, farm, commercial establishment',
-            'Industrial Waste': 'waste produced by industrial activity which includes any material that is rendered useless during a manufacturing process such as that of factories, industries, mills, and mining operations',
-            'Interstitial Water': '(pore water), subterranean water in the pores of rocks, soils, and bottom sediments of oceans, seas, and lakes.',
-            'Lake Sediment': 'the accumulation of sand and dirt that settles in the bottom of lakes.',
-            'Landfill effluent': 'A liquid material (usually water) that is drained or pumped from a landfill. It usually is a liquid that has percolated through solid landfill material to become a transport medium for materials dissolved from the landfill.',
-            'Leachate': "A solution obtained by passing a liquid (usually aqueous) through an unconsolidated solid medium, thereby dissolving materials (from the solid medium) which become a part of the solution. It also contains those precipitates that are the result of the solution process and subsequent chemical or biological reactions. any liquid that, in the course of passing through matter. the liquid that drains or 'leaches' from a landfill.",
-            'Manure, green': 'In agriculture, green manure is created by leaving uprooted or sown crop parts to wither on a field so that they serve as a mulch and soil amendment.[1] The plants used for green manure are often cover crops grown primarily for this purpose. Typically, they are ploughed under and incorporated into the soil while green or shortly after flowering.',
-            'Manure, liquid': 'Liquid manure is manure in a liquid form. Manure is changed into a liquid form by mixing the manure with water.',
-            'Manure, solid': 'Manure is organic matter, mostly derived from animal feces except in the case of green manure, which can be used as organic fertilizer in agriculture. Manures contribute to the fertility of the soil by adding organic matter and nutrients, such as nitrogen, that are utilised by bacteria, fungi and other organisms in the soil.',
-            'Mine Tailings Pond': 'Tailing ponds are areas of refused mining tailings where the waterborne refuse material is pumped into a pond to allow the sedimentation (meaning separation) of solids from the water. The pond is generally impounded with a dam, and known as tailings impoundments or tailings dams.',
-            'Mixing Zone': 'A mixing zone is an area of a lake or river where pollutants from a point source discharge are mixed, usually by natural means, with cleaner water. In the mixing zone, the level of toxic pollutants is allowed to be higher than the acceptable concentration for the general water body.',
-            'Mixing Zone, Zone of Initial Dilution': 'A zone of initial dilution or ZID is defined as a small area in the immediate vicinity of an outfall structure in which turbulence from the eflhtent velocity is high and causes rapid mixing with the surrounding water. Turbulence for rapid mixing can be provided in three ways. The most common way is with the use of an effluent diffuser. This method assures adequate velocities and mixing if it is properly designed for the discharge area.',
-            'Municipal Sewage Effluent': 'Sewage treatment is the process of removing contaminants from wastewater, primarily from household sewage. It includes physical, chemical, and biological processes to remove these contaminants and produce environmentally safer treated wastewater (or treated effluent).',
-            'Municipal Waste': 'waste from households, and other waste that, because of its nature or composition, is similar to waste from households.',
-            'Ocean Water': "Seawater, or salt water, is water from a sea or ocean. On average, seawater in the world's oceans has a salinity of about 3.5% (35 g/L, 599 mM) ",
-            'Oil/Oily Sludge': 'Any sludge and/or float generated from the physical and/or chemical separation of oil/water/solids in process wastewaters and oily cooling wastewaters from petroleum refineries.',
-            'Pore water': "water contained in pores in soil or rock. Water beneath the earth's surface, often between saturated soil and rock, that supplies wells and springs.",
-            'Rainwater': 'water that has fallen as or been obtained from rain.',
-            'Rock/Cobbles/Gravel': 'Particle size, also called grain size, refers to the diameter of individual grains of sediment, or the lithified particles in clastic rocks. ... Granular material can range from very small colloidal particles, through clay, silt, sand, gravel, and cobbles, to boulders.',
-            'Septic Effluent': 'collect sewage from residences and businesses, and the effluent that comes out of the tank is sent to either a centralized sewage treatment plant or a distributed treatment system',
-            'Sieved Sediment': 'characterizing the particle size distribution of a sediment sample thru a device for separating wanted elements from unwanted material ',
-            'Sludge': 'thick, soft, wet mud or a similar viscous mixture of liquid and solid components, especially the product of an industrial or refining process',
-            'Snowmelt': 'the melting of fallen snow',
-            'Soil Gas': 'the gases found in the air space between soil components. The primary natural soil gases include nitrogen, carbon dioxide and oxygen. The oxygen is critical because it allows for respiration of both plant roots and soil organisms. Other natural soil gases are atmospheric methane and radon.',
-            'Soil moisture': 'Water occupying voids between loose soil particles within the aerated root zone. The water is held in place by surface tension, capillary and hydroscopic forces in opposition to the pull of gravitational forces.',
-            'Solids': 'Unconsolidated materials that may be soils, cores, borehole cuttings, sediments, matter suspended in water or wastewater, street sweepings, other particulate matter, or the total array of materials that are collected as part of a "clean sweep"',
-            'Stack Gases': 'combustion product gases called flue gases are exhausted to the outside air.',
-            'Stormwater': 'surface water in abnormal quantity resulting from heavy falls of rain or snow.',
-            'Subsurface Soil/Sediment': 'relating to, or situated in an area beneath a surface, especially the surface of the earth or of a body of wate, soil or sediment',
-            'Surface Soil/Sediment': 'relating to or occurring on the upper or outer part of something.soil or sediment',
-            'Surface Water': 'Water on the surface of the Earth stored or transported in rivers, streams, estuaries, lakes, ponds, swamps, glaciers, or other aquatic areas. It also may refer to water in urban drains and storm-sewer systems. water that collects on the surface of the ground. water on the surface of the planet such as in a river, lake, wetland, or ocean.',
-            'Surface Water Sediment': 'Sediments are most often transported by water (fluvial processes), but also wind (aeolian processes) and glaciers. Beach sands and river channel deposits are examples of fluvial transport and deposition, though sediment also often settles out of slow-moving or standing water in lakes and oceans.',
-            'Treated water supply': 'Water after being processed for some particular use(s)',
-            'Unknown': 'Unknown',
-            'UnSieved Sediment': 'preserved characterized particle size distribution of a sediment sample',
-            'Untreated water supply': 'Untreated water supply from a blend of surface and ground waters or from unknown sources.',
-            'USGS Standard Reference Sample': 'Samples provided by the USGS for quality assurance testing',
-            'Waste Gas': 'Waste Gas is meant to represent landfill gas. Waste gas means a natural gas that contains a greater percentage of gaseous chemical impurities than the percentage of methane. For purposes of this definition, gaseous chemical impurities may include carbon dioxide, nitrogen, helium, or hydrogen sulfide.',
-            'Wastewater Treatment Plant Effluent': 'the final product of all earlier treatment processes, and it can be discharged to a stream, river, bay, lagoon or wetland. Sometimes effluent ',
-            'Wastewater Treatment Plant Influent': 'Influent is water, waste water or other liquid flowing into a reservoir, basin or treatment plant.',
-            'Water-Vadose Zone': 'The vadose zone, also termed the unsaturated zone, is the part of Earth between the land surface and the top of the phreatic zone, the position at which the groundwater (the water in the soil\'s pores) is at atmospheric pressure ("vadose" is from the Latin for "shallow")',
-            'Wet Fall Material': 'material formulated so that when applied at specified conditions, formings a droplets-like state before reaching a certain distance',
-            'Wipe': 'a disposable cloth for wiping things clean',
-            }
-
-
-def get_domain_list(field):
-    """
-    Queries for domain list and extracts as dictionary
-
-    Parameters
-    ----------
-    field : string
-        Name of field in returned results
-
-    Returns
-    -------
-    dictionary
-        Dictionary where {Name: Description}
-
-    """
-    # ActivityMediaSubdivisionName -> ActivityMediaSubdivision
-    # ActivityMediaName -> ActivityMedia
-    url = '{}{}_CSV.zip'.format(BASE_URL, field)
-
-    if requests.get(url).status_code != 200:
-        status_code = requests.get(url).status_code
-        print("{} web service response {}".format(url, status_code))
-    res = requests.get(url)
-    if not res.ok:
-        # Use requests lib to print url server request
-        req_str = requests.Request('GET', url, params={}).prepare().url
-        print("Problem with response from {}".format(req_str))
-        print('Try running the query using this url in your browser')
-        print(res.headers['Warning'], 1)
-        # Will typically break with key error
-
-    # zipfile object from response content bytes
-    archive = ZipFile(BytesIO(res.content))
-    item1 = archive.namelist()[0]  # Name of first file
-    cols = ['\tName', '\tDescription']
-    df = pandas.read_csv(archive.open(item1), usecols=cols, low_memory=False)
-    # Drop tabs from values
-    for col in cols:
-        df[col] = [val.replace('\t', '') for val in df[col]]
-    # Drop tabs from column names
-    df.columns = df.columns.str.replace('\t', '')
-    df = df.set_index('Name')  # Set index to cols1
-    # Return key = col for dict by col1
-    return df.to_dict()['Description']
-
-
 def stations_rename():
 #     Default field mapping writes full name to alias but a short name to field
     """
diff --git a/harmonize_wq/harmonize.py b/harmonize_wq/harmonize.py
index 20d0731..d38ad50 100644
--- a/harmonize_wq/harmonize.py
+++ b/harmonize_wq/harmonize.py
@@ -391,7 +391,7 @@ def replace_unit_by_dict(self, val_dict, mask=None):
             replace_in_col(self.df, col, item[0], item[1], mask)
 
     def fraction(self, frac_dict=None, suffix=None,
-                   fract_col='ResultSampleFractionText'):
+                 fract_col='ResultSampleFractionText'):
         """
         Create columns for sample fractions, use frac_dict to set their names.
 
@@ -413,46 +413,59 @@ def fraction(self, frac_dict=None, suffix=None,
             frac_dict updated to include any frac_col not already defined.
         """
         c_mask = self.c_mask
-        if frac_dict is None:
-            frac_dict = {}
         if suffix is None:
             suffix = self.out_col
 
+        catch_all = 'Other_{}'.format(suffix)
+        if frac_dict is None:
+            frac_dict = {catch_all: ''}
+        else:
+            if catch_all not in frac_dict.keys():
+                frac_dict[catch_all] = ['']
+        if not isinstance(frac_dict[catch_all], list):
+            frac_dict[catch_all] = [frac_dict[catch_all]]
+        # Get all domain values
+        for key in domains.get_domain_dict('ResultSampleFraction').keys():
+            if key not in frac_dict.values():
+                frac_dict[catch_all] += [key]
+        # Flatten for some uses
+        samp_fract_set = sorted({x for v in frac_dict.values() for x in v})
+
         # Check for sample fraction column
         df_checks(self.df, ['ResultSampleFractionText'])
         # Replace bad sample fraction w/ nan
         self.df = replace_in_col(self.df, fract_col, ' ', nan, c_mask)
 
         df_out = self.df
+
         # Make column for any unexpected Sample Fraction values, loudly
         for s_f in set(df_out[fract_col].dropna()):
-            if s_f not in frac_dict.values():
+            if s_f not in samp_fract_set:
                 char = '{}_{}'.format(s_f.replace(' ', '_'), suffix)
                 frac_dict[char] = s_f
                 prob = '"{}" column for {}, may be error'.format(char, s_f)
                 warn('Warning: ' + prob)
         # Test we didn't skip any SampleFraction
+        samp_fract_set = sorted({x for v in frac_dict.values() for x in v})
         for s_f in set(df_out[fract_col].dropna()):
-            assert s_f in frac_dict.values(), '{} check in {}'.format(s_f,
+            assert s_f in samp_fract_set, '{} check in {}'.format(s_f,
                                                                       fract_col)
-
         # Create out columns for each sample fraction
         for frac in frac_dict.items():
             col = frac[0]  # New column name
-            if frac[1] in set(df_out.loc[c_mask, fract_col].dropna()):
-                # New subset mask for sample frac
-                f_mask = (c_mask & (df_out[fract_col]==frac[1]))
-                df_out[col] = nan  # Add column
-                # Copy measure to new col (new col name from char_list)
-                df_out.loc[f_mask, col] = df_out.loc[f_mask, suffix]
-            elif frac[1] == '':
-                # Values where sample fraction missing
-                if df_out.loc[c_mask, fract_col].isnull().values.any():
-                    # New subset mask
-                    f_mask = (c_mask & (df_out[fract_col].isnull()))
-                    df_out[col] = nan  # Add column
-                    # Copy measure to new col
+            for smp_frac in frac[1]:
+                if smp_frac in set(df_out.loc[c_mask, fract_col].dropna()):
+                    # New subset mask for sample frac
+                    f_mask = (c_mask & (df_out[fract_col]==smp_frac))
+                    # Copy measure to new col (new col name from char_list)
                     df_out.loc[f_mask, col] = df_out.loc[f_mask, suffix]
+                elif smp_frac == '':
+                    # Values where sample fraction missing go to catch all
+                    if df_out.loc[c_mask, fract_col].isnull().values.any():
+                        # New subset mask
+                        f_mask = (c_mask & (df_out[fract_col].isnull()))
+                        # Copy measure to new col
+                        df_out.loc[f_mask, col] = df_out.loc[f_mask, suffix]
         self.df = df_out
 
         return frac_dict
@@ -634,7 +647,7 @@ def convert_unit_series(quantity_series, unit_series, units, ureg=None, errors='
         ureg = pint.UnitRegistry()
     Q_ = ureg.Quantity
 
-    out_series = pandas.Series(dtype='object')
+    lst_series = [pandas.Series(dtype='object')]
     for unit in list(set(unit_series)):
         # Filter quantity_series by unit_series where == unit
         f_quant_series = quantity_series.where(unit_series==unit).dropna()
@@ -653,10 +666,9 @@ def convert_unit_series(quantity_series, unit_series, units, ureg=None, errors='
                 else:
                     # errors=='raise', or anything else just in case
                     raise exception
-        # Re-index
-        result_series = pandas.Series(result_list, index=f_quant_series.index)
-        out_series = out_series.append(result_series)  # Append to full series
-    return out_series
+        # Re-index and add series to list
+        lst_series.append(pandas.Series(result_list, index=f_quant_series.index))
+    return pandas.concat(lst_series)
 
 
 def add_qa_flag(df_in, mask, flag):
@@ -1093,9 +1105,9 @@ def harmonize_generic(df_in, char_val, units_out=None, errors='raise',
     # Total is TP (digested) from the whole water sample (vs total dissolved)
     # Dissolved is TDP (total) filtered water digested (vs undigested DIP)
     if out_col == 'Phosphorus':
-        frac_dict = {'TP_Phosphorus': 'Total',
-                     'TDP_Phosphorus': 'Dissolved',
-                     'Other_Phosphorus': '',}
+        frac_dict = {'TP_Phosphorus': ['Total'],
+                     'TDP_Phosphorus': ['Dissolved'],
+                     'Other_Phosphorus': [''],}
         # Make columns for Sample Fractions, loudly if unexpected (not in dict)
         frac_dict = wqp.fraction(frac_dict)  # Run sample fraction on WQP
 
diff --git a/harmonize_wq/location.py b/harmonize_wq/location.py
index 17a368b..52186f9 100644
--- a/harmonize_wq/location.py
+++ b/harmonize_wq/location.py
@@ -14,6 +14,7 @@
 from harmonize_wq import harmonize
 from harmonize_wq import domains
 from harmonize_wq import wrangle
+from harmonize_wq import clean
 
 
 def infer_CRS(df_in,
@@ -106,6 +107,10 @@ def harmonize_locations(df_in, out_EPSG=4326,
     # Check columns are in df
     harmonize.df_checks(df2, [crs_col, lat_col, lon_col])
 
+    # Check location precision
+    df2 = clean.check_precision(df2, lat_col)
+    df2 = clean.check_precision(df2, lon_col)
+
     # Create tuple column
     df2['geom_orig'] = list(zip(df2[lon_col], df2[lat_col]))
 
diff --git a/harmonize_wq/wrangle.py b/harmonize_wq/wrangle.py
index 1c49dc1..21307bf 100644
--- a/harmonize_wq/wrangle.py
+++ b/harmonize_wq/wrangle.py
@@ -8,12 +8,10 @@
 """
 import pandas
 import geopandas
-from io import StringIO
 from harmonize_wq import domains
 from harmonize_wq import harmonize
 from harmonize_wq.clean import datetime, harmonize_depth
 import dataretrieval.wqp as wqp
-import dataretrieval.utils
 
 
 def split_table(df_in):
@@ -203,15 +201,6 @@ def collapse_results(df_in, cols=None):
 #     return df_passing, df_fails
 
 
-def what_detection_limits(**kwargs):
-    '''This is only here until PR is merged to add it to data retrieval'''
-    kwargs['zip'] = 'no'
-    kwargs['mimeType'] = 'csv'
-    url = wqp.wqp_url('data/ResultDetectionQuantitationLimit')
-    response = dataretrieval.utils.query(url, payload=kwargs, delimiter=';')
-    return pandas.read_csv(StringIO(response.text), delimiter=',')
-
-
 def get_activities_by_loc(characteristic_names, locations):
     """
     Quick attempt to segment batch what_activities - may not stay
@@ -234,7 +223,7 @@ def get_activities_by_loc(characteristic_names, locations):
     for loc_que in [locations[x:x+seg] for x in range(0, len(locations), seg)]:
         query = {'characteristicName': characteristic_names,
                  'siteid': loc_que}
-        activities_list.append(what_activities(**query))
+        activities_list.append(wqp.what_activities(**query))
     # Combine the dataframe results
     activities = pandas.concat(activities_list).drop_duplicates()
     return activities
@@ -276,15 +265,6 @@ def add_activities_to_df(df_in, mask=None):
     return df_merged
 
 
-def what_activities(**kwargs):
-    '''This is only here until PR is merged to add it to data retrieval'''
-    kwargs['zip'] = 'no'
-    kwargs['mimeType'] = 'csv'
-    url = wqp.wqp_url('data/Activity')
-    response = dataretrieval.utils.query(url, payload=kwargs, delimiter=';')
-    return pandas.read_csv(StringIO(response.text), delimiter=',')
-
-
 def add_detection(df_in, char_val):
     """
     Add detection quantitation information for results where available.
@@ -356,7 +336,7 @@ def get_detection_by_loc(loc_series, result_id_series, char_val=None):
         query = {'siteid': id_que}
         if char_val:
             query['characteristicName'] = char_val
-        detection_list.append(what_detection_limits(**query))
+        detection_list.append(wqp.what_detection_limits(**query))
     # Combine the dataframe results in the list
     detection_df = pandas.concat(detection_list).drop_duplicates()
     # Filter on resultID
diff --git a/pyproject.toml b/pyproject.toml
index a1ef5e4..1d1b910 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,7 +7,7 @@ packages = ["harmonize_wq"]
 
 [project]
 name = "harmonize_wq"
-version = "0.1.0"
+version = "0.2.0"
 authors = [
   { name="Justin Bousquin", email="Bousquin.Justin@epa.gov" },
 ]
diff --git a/requirements.txt b/requirements.txt
index 8679c4b..38e4732 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
-geopandas>=0.10.2           # Has additional dependencies (pandas, numpy, shapely, fiona, etc.), tested on 0.10.2
+pandas<2.0
+geopandas>=0.10.2, <0.13           # Has additional dependencies (pandas, numpy, shapely, fiona, etc.), tested on 0.10.2
 pint>=0.18          # May work on earlier versions but this is what it was tested on
-dataretrieval>=0.7  # Current version 0.7, v0.8 should include some of the functions currently built-in here for activities/detection queries
-
+dataretrieval>1.*
 descartes>=1.1.0           # May be required for mapping in some jupyter notebooks


Test patch:
diff --git a/harmonize_wq/tests/test_harmonize_WQP.py b/harmonize_wq/tests/test_harmonize_WQP.py
index 4243b37..0df0f56 100644
--- a/harmonize_wq/tests/test_harmonize_WQP.py
+++ b/harmonize_wq/tests/test_harmonize_WQP.py
@@ -190,6 +190,10 @@ def test_harmonize_locations():
     # Converted converted
     # Missing unit infered
     # Check QA_flag
+    # Check for precision flag
+    actual_imprecise = actual.iloc[302]['QA_flag']
+    expected_imprecise = 'LatitudeMeasure: Imprecise: lessthan3decimaldigits'
+    assert actual_imprecise == expected_imprecise
 
 
 #@pytest.mark.skip(reason="no change")
@@ -207,14 +211,14 @@ def test_harmonize_phosphorus(merged_tables):
     # TODO: test conversion to moles and other non-standard units
     # Test that the dataframe has expected type, size, cols, and rows
     assert isinstance(actual, pandas.core.frame.DataFrame)  # Test type
-    assert actual.size == 17256240  # Test size
+    assert actual.size == 16896735  # 17256240  # Test size
     # Test for expected columns
     for col in ['TP_Phosphorus', 'TDP_Phosphorus', 'Other_Phosphorus']:
         assert col in actual.columns
     # Number of results in each col
     assert len(actual['TP_Phosphorus'].dropna()) == 11243
     assert len(actual['TDP_Phosphorus'].dropna()) == 601
-    assert len(actual['Other_Phosphorus'].dropna()) == 1075
+    assert len(actual['Other_Phosphorus'].dropna()) == 12968  # 1075 NAN
 
     # Confirm orginal data was not altered
     orig_val_col = 'ResultMeasureValue'  # Values
@@ -1136,14 +1140,6 @@ def test_accept_methods(merged_tables):
 def test_datetime(harmonized_tables):
     # Testit
     actual = clean.datetime(harmonized_tables)
-    # Check for dropped fields
-    drop_fields = ['ActivityStartDate',
-                   'ActivityStartTime/Time',
-                   'ActivityStartTime/TimeZoneCode']
-    for field in drop_fields:
-        assert field not in actual.columns
-    # Type for date field (not formated, just str)
-    #actual['StartDate']
     # Type for time field
     assert isinstance(actual['Activity_datetime'][0],
                       pandas._libs.tslibs.timestamps.Timestamp)
@@ -1183,9 +1179,12 @@ def test_split_table(harmonized_tables):
                 'DetectionQuantitationLimitMeasure/MeasureValue',
                 'DetectionQuantitationLimitMeasure/MeasureUnitCode',
                 'ProviderName', 'QA_flag', 'Nitrogen', 'Speciation',
-                'Conductivity', 'StartDate', 'Activity_datetime', 'Depth']
+                'Conductivity', 'Activity_datetime',
+                'Depth']
     assert list(actual_main.columns) == expected
-    expected = ['ResultDetectionConditionText',
+    expected = ['ActivityStartDate', 'ActivityStartTime/Time',
+                'ActivityStartTime/TimeZoneCode',
+                'ResultDetectionConditionText',
                 'MethodSpecificationName', 'CharacteristicName',
                 'ResultSampleFractionText', 'ResultMeasureValue',
                 'ResultMeasure/MeasureUnitCode', 'MeasureQualifierCode',