Repository: pvlib/pvlib-python
Pull request #652 (2019-01-26 04:50:27)

Create CODE_OF_CONDUCT.md
Will, you are the only member of the pvlib org, so I put you as the email. Is that okay?

pvlib python pull request guidelines
====================================

Thank you for your contribution to pvlib python! You may delete all of these instructions except for the list below.

You may submit a pull request with your code at any stage of completion.

The following items must be addressed before the code can be merged. Please don't hesitate to ask for help if you're unsure of how to accomplish any of the items below:

 - [ ] Closes issue #xxxx
 - [ ] I am familiar with the [contributing guidelines](http://pvlib-python.readthedocs.io/en/latest/contributing.html).
 - [ ] Fully tested. Added and/or modified tests to ensure correct behavior for all reasonable inputs. Tests (usually) must pass on the TravisCI and Appveyor testing services.
 - [ ] Updates entries to `docs/sphinx/source/api.rst` for API changes.
 - [ ] Adds description and name entries in the appropriate `docs/sphinx/source/whatsnew` file for all changes.
 - [ ] Code quality and style is sufficient. Passes LGTM and SticklerCI checks.
 - [ ] New code is fully documented. Includes sphinx/numpydoc compliant docstrings and comments in the code where necessary.
 - [ ] Pull request is nearly complete and ready for detailed review.

Brief description of the problem and proposed solution (if not already fully described in the issue linked to above):


wholmgren: @mikofski, thanks for the PR. I've long thought that we should add a code of conduct based on the Contributor Covenant but never did it because the reporting/enforcement implications. I am ok with emails going to me, but I am not ok with being the only person that gets the emails. So we should list multiple people or create an organization list (preferred). The emails should go to my arizona.edu address. Note that if/when something is reported, I could be obligated to report it to the university.
mikofski: You can add me at mikofski@berkeley.edu I'm okay with that.
cwhanse: You can add me at cwhanse@sandia.gov. 
mikofski: I created a [collaborative inbox](https://support.google.com/a/answer/167430?hl=en) called [pvlib-admin](https://groups.google.com/forum/#!forum/pvlib-admin), part of [Google Groups](https://gsuite.google.com/learning-center/products/groups/get-started/#!/). There's more [help](https://support.google.com/groups#topic=9216).

I think you can maybe add this to the existing "pvlib-python" group, but not sure. I don't even know if this will work, but it seemed to work in some tests I did. Basically any can email the group, which is preconfigured by google groups to be a "collaborative inbox" which they recommend for support type emails. It also comes with a bunch of moderation tools and other settings that I don't understand. So if someone emails the "pvlib-admin" group, then you can click more options next to reply and select "reply privately to author" and it will send an email to their inbox, but only if they are using Gmail. Otherwise, you can view their email address by hovering over their name, or you can respond from your personal inbox directly to them.

![image](https://user-images.githubusercontent.com/1385621/51791972-18e8d580-2160-11e9-9cfe-c6bd48bcbc64.png)

What I don't understand is how to do this using the pvlib-admin@googlegroups.com email address instead of your own email address. Maybe this isn't an issue? If you are responding to the group, then you can select the option to use the group email, but that wouldn't go to the poster since they are not members of the pvlib-admin group. And if the poster isn't using Gmail, then you have to respond from your personal email anyway?

So this may not be the correct solution. Sorry 😕 
mikofski: @wholmgren or @cwhanse do you know of another way to create an "organization list" other than google groups collaborative inbox? [Googling](https://www.google.com/search?q=share+inbox+for+open+source+software) I found [helpmonk](https://helpmonks.com/opensource) free for open source.
wholmgren: Thanks for looking into that. That approach seems fine for this purpose given that it should be relatively little email - hopefully none! Some people might try to use it for support and we'll need to steer them back to the other resources. We've set up organization lists using AWS for a custom domain, but we don't need that complexity here.

I'm ok with listing multiple email addresses too. I preferred the list because I thought it was better if more than one person received this kind of email. But I guess that individual addresses could actually be preferable if someone thinks one of the people on the list is causing a problem. I don't know if there's a right answer and I'll leave it up to you.

Patch:
diff --git a/.codecov.yml b/.codecov.yml
index f8a38af..48df15e 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -20,8 +20,8 @@ coverage:
         paths:
           - "pvlib/(\w+/)?[^/]+\.py$"
       tests:
-        target: 96%
+        target: 95%
         paths:
           - "pvlib/test/.*"
 
-comment: off
\ No newline at end of file
+comment: off
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..f1b63e8
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,78 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at pvlib-admin@googlegroups.com, which
+will forward your email to holmgren@email.arizona.edu, cwhanse@sandia.gov, and
+mikofski@berkeley.edu, or you can email an individual directly if you prefer. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/docs/sphinx/source/whatsnew/v0.6.1.rst b/docs/sphinx/source/whatsnew/v0.6.1.rst
index 6721b0a..d4f132b 100644
--- a/docs/sphinx/source/whatsnew/v0.6.1.rst
+++ b/docs/sphinx/source/whatsnew/v0.6.1.rst
@@ -101,3 +101,4 @@ Contributors
 * Cameron Stark (:ghuser:`camerontstark`)
 * Jonathan Gaffiot (:ghuser:`jgaffiot`)
 * Marc Anoma (:ghuser:`anomam`)
+* Anton Driesse (:ghuser:`adriesse`)
diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py
index 0dfdb07..35e4833 100644
--- a/pvlib/pvsystem.py
+++ b/pvlib/pvsystem.py
@@ -46,7 +46,7 @@ DC_MODEL_PARAMS = {
 }
 
 
-TEMP_MODELS = {
+TEMP_MODEL_PARAMS = {
     'sapm': {'open_rack_cell_glassback': (-3.47, -.0594, 3),
              'roof_mount_cell_glassback': (-2.98, -.0471, 1),
              'open_rack_cell_polymerback': (-3.56, -.0750, 3),
@@ -521,7 +521,7 @@ class PVSystem(object):
             poa_direct, poa_diffuse, airmass_absolute, aoi,
             self.module_parameters, reference_irradiance=reference_irradiance)
 
-    def pvsyst_celltemp(self, poa_global, wind_speed, temp_air):
+    def pvsyst_celltemp(self, poa_global, temp_air, wind_speed=1.0):
         """Uses :py:func:`pvsyst_celltemp` to calculate module temperatures
         based on ``self.racking_model`` and the input parameters.
 
@@ -535,8 +535,8 @@ class PVSystem(object):
         """
         kwargs = _build_kwargs(['eta_m', 'alpha_absorption'],
                                self.module_parameters)
-        return pvsyst_celltemp(poa_global, wind_speed, temp_air,
-                               temp_model=self.racking_model, **kwargs)
+        return pvsyst_celltemp(poa_global, temp_air, wind_speed,
+                               model_params=self.racking_model, **kwargs)
 
     def first_solar_spectral_loss(self, pw, airmass_absolute):
 
@@ -1887,7 +1887,7 @@ def sapm_celltemp(poa_global, wind_speed, temp_air,
     sapm
     '''
 
-    temp_models = TEMP_MODELS['sapm']
+    temp_models = TEMP_MODEL_PARAMS['sapm']
 
     if isinstance(model, str):
         model = temp_models[model.lower()]
@@ -1908,30 +1908,37 @@ def sapm_celltemp(poa_global, wind_speed, temp_air,
     return pd.DataFrame({'temp_cell': temp_cell, 'temp_module': temp_module})
 
 
-def pvsyst_celltemp(poa_global, wind_speed, temp_air, eta_m=0.1,
-                    alpha_absorption=0.9, temp_model="freestanding"):
+def pvsyst_celltemp(poa_global, temp_air, wind_speed=1.0, eta_m=0.1,
+                    alpha_absorption=0.9, model_params='freestanding'):
     """
-    Calculate cell temperature using the PVSyst model.
+    Calculate cell temperature using an emperical heat loss factor model
+    as implemented in PVsyst.
+
+    The heat loss factors provided through the 'model_params' argument
+    represent the combined effect of convection, radiation and conduction,
+    and their values are experimentally determined.
 
     Parameters
     ----------
     poa_global : numeric
         Total incident irradiance in W/m^2.
 
-    wind_speed : numeric
-        Wind speed in m/s at a height of 10 meters.
-
     temp_air : numeric
         Ambient dry bulb temperature in degrees C.
 
-    eta_m : numeric
+    wind_speed : numeric, default 1.0
+        Wind speed in m/s measured at the same height for which the wind loss
+        factor was determined.  The default value is 1.0, which is the wind
+        speed at module height used to determine NOCT.
+
+    eta_m : numeric, default 0.1
         Module external efficiency as a fraction, i.e., DC power / poa_global.
 
-    alpha_absorption : float
-        Absorption coefficient, default is 0.9.
+    alpha_absorption : numeric, default 0.9
+        Absorption coefficient
 
-    temp_model : string, tuple, or list, default 'freestanding' (no dict)
-        Model to be used.
+    model_params : string, tuple, or list (no dict), default 'freestanding'
+        Heat loss factors to be used.
 
         If string, can be:
 
@@ -1944,12 +1951,12 @@ def pvsyst_celltemp(poa_global, wind_speed, temp_air, eta_m=0.1,
 
         If tuple/list, supply parameters in the following order:
 
-            * natural_convenction_coeff : float
-                Natural convection coefficient. Freestanding default is 29,
-                fully insulated arrays is 15.
+            * constant_loss_factor : float
+                Combined heat loss factor coefficient. Freestanding
+                default is 29, fully insulated arrays is 15.
 
-            * forced_convection_coeff : float
-                Forced convection coefficient, default is 0.
+            * wind_loss_factor : float
+                Combined heat loss factor influenced by wind. Default is 0.
 
     Returns
     -------
@@ -1965,25 +1972,21 @@ def pvsyst_celltemp(poa_global, wind_speed, temp_air, eta_m=0.1,
     photovoltaic modules." Progress in Photovoltaics 16(4): 307-315.
     """
 
-    temp_models = TEMP_MODELS['pvsyst']
+    pvsyst_presets = TEMP_MODEL_PARAMS['pvsyst']
 
-    if isinstance(temp_model, str):
-        natural_convenction_coeff, forced_convection_coeff = temp_models[
-            temp_model.lower()
-        ]
-    elif isinstance(temp_model, (tuple, list)):
-        natural_convenction_coeff, forced_convection_coeff = temp_model
+    if isinstance(model_params, str):
+        model_params = model_params.lower()
+        constant_loss_factor, wind_loss_factor = pvsyst_presets[model_params]
+    elif isinstance(model_params, (tuple, list)):
+        constant_loss_factor, wind_loss_factor = model_params
     else:
         raise TypeError(
-            "Please format temp_model as a str, or tuple/list."
+            "Please provide model_params as a str, or tuple/list."
         )
 
-    combined_convection_coeff = (
-        forced_convection_coeff * wind_speed
-    ) + natural_convenction_coeff
-
-    absorption_coeff = alpha_absorption * poa_global * (1 - eta_m)
-    temp_difference = absorption_coeff / combined_convection_coeff
+    total_loss_factor = wind_loss_factor * wind_speed + constant_loss_factor
+    heat_input = poa_global * alpha_absorption * (1 - eta_m)
+    temp_difference = heat_input / total_loss_factor
     temp_cell = temp_air + temp_difference
 
     return temp_cell


Test patch:
diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py
index 4a1ef52..2f0eeb8 100644
--- a/pvlib/test/test_pvsystem.py
+++ b/pvlib/test/test_pvsystem.py
@@ -1079,32 +1079,32 @@ def test_PVSystem_sapm_celltemp(mocker):
 
 
 def test_pvsyst_celltemp_default():
-    default = pvsystem.pvsyst_celltemp(900, 5, 20, 0.1)
+    default = pvsystem.pvsyst_celltemp(900, 20, 5)
     assert_allclose(default, 45.137, 0.001)
 
 
 def test_pvsyst_celltemp_non_model():
-    tup_non_model = pvsystem.pvsyst_celltemp(900, 5, 20, 0.1,
-                                             temp_model=(23.5, 6.25))
+    tup_non_model = pvsystem.pvsyst_celltemp(900, 20, 5, 0.1,
+                                             model_params=(23.5, 6.25))
     assert_allclose(tup_non_model, 33.315, 0.001)
 
-    list_non_model = pvsystem.pvsyst_celltemp(900, 5, 20, 0.1,
-                                              temp_model=[26.5, 7.68])
+    list_non_model = pvsystem.pvsyst_celltemp(900, 20, 5, 0.1,
+                                              model_params=[26.5, 7.68])
     assert_allclose(list_non_model, 31.233, 0.001)
 
 
 def test_pvsyst_celltemp_model_wrong_type():
     with pytest.raises(TypeError):
         pvsystem.pvsyst_celltemp(
-            900, 5, 20, 0.1,
-            temp_model={"won't": 23.5, "work": 7.68})
+            900, 20, 5, 0.1,
+            model_params={"won't": 23.5, "work": 7.68})
 
 
 def test_pvsyst_celltemp_model_non_option():
     with pytest.raises(KeyError):
         pvsystem.pvsyst_celltemp(
-            900, 5, 20, 0.1,
-            temp_model="not_an_option")
+            900, 20, 5, 0.1,
+            model_params="not_an_option")
 
 
 def test_pvsyst_celltemp_with_index():
@@ -1113,7 +1113,7 @@ def test_pvsyst_celltemp_with_index():
     irrads = pd.Series([0, 500, 0], index=times)
     winds = pd.Series([10, 5, 0], index=times)
 
-    pvtemps = pvsystem.pvsyst_celltemp(irrads, winds, temps)
+    pvtemps = pvsystem.pvsyst_celltemp(irrads, temps, wind_speed=winds)
     expected = pd.Series([0.0, 23.96551, 5.0], index=times)
     assert_series_equal(expected, pvtemps)
 
@@ -1131,9 +1131,9 @@ def test_PVSystem_pvsyst_celltemp(mocker):
     irrad = 800
     temp = 45
     wind = 0.5
-    out = system.pvsyst_celltemp(irrad, wind, temp)
+    out = system.pvsyst_celltemp(irrad, temp, wind_speed=wind)
     pvsystem.pvsyst_celltemp.assert_called_once_with(
-        irrad, wind, temp, eta_m, alpha_absorption, racking_model)
+        irrad, temp, wind, eta_m, alpha_absorption, racking_model)
     assert isinstance(out, float)
     assert out < 90 and out > 70