• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

blue-marble / gridpath / 17590270559

09 Sep 2025 05:12PM UTC coverage: 88.959% (-0.001%) from 88.96%
17590270559

push

github

web-flow
Default project availability type to 'exogenous' (#1285)

23 of 24 new or added lines in 4 files covered. (95.83%)

1 existing line in 1 file now uncovered.

27347 of 30741 relevant lines covered (88.96%)

2.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.84
/gridpath/project/availability/availability_types/exogenous.py
1
# Copyright 2016-2024 Blue Marble Analytics LLC.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#     http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

15
"""
16
For each project assigned this *availability type*, the user may specify an
17
(un)availability schedule, i.e. a capacity derate for each timepoint in
18
which the project may be operated. If fully derated in a given timepoint,
19
the available project capacity will be 0 in that timepoint and all
20
operational decision variables will therefore also be constrained to 0 in the
21
optimization.
22

23
"""
24

25
import csv
3✔
26
import os.path
3✔
27
from pyomo.environ import Param, Set, NonNegativeReals
3✔
28

29
from gridpath.auxiliary.auxiliary import cursor_to_df, subset_init_by_set_membership
3✔
30
from gridpath.auxiliary.db_interface import directories_to_db_values
3✔
31
from gridpath.auxiliary.validations import (
3✔
32
    write_validation_to_database,
33
    get_expected_dtypes,
34
    validate_dtypes,
35
    validate_values,
36
    validate_missing_inputs,
37
)
38
from gridpath.project.common_functions import (
3✔
39
    determine_project_subset,
40
)
41
from gridpath.project.operations.operational_types.common_functions import (
3✔
42
    write_tab_file_model_inputs,
43
)
44

45

46
def add_model_components(
3✔
47
    m,
48
    d,
49
    scenario_directory,
50
    weather_iteration,
51
    hydro_iteration,
52
    availability_iteration,
53
    subproblem,
54
    stage,
55
):
56
    """
57
    The following Pyomo model components are defined in this module:
58

59
    +-------------------------------------------------------------------------+
60
    | Sets                                                                    |
61
    +=========================================================================+
62
    | | :code:`AVL_EXOG`                                                      |
63
    |                                                                         |
64
    | The set of projects of the :code:`exogenous` availability type.         |
65
    +-------------------------------------------------------------------------+
66
    | | :code:`AVL_EXOG_OPR_TMPS`                                             |
67
    |                                                                         |
68
    | Two-dimensional set with projects of the :code:`exogenous`              |
69
    | availability type and their operational timepoints.                     |
70
    +-------------------------------------------------------------------------+
71

72
    |
73

74
    +-------------------------------------------------------------------------+
75
    | Optional Input Params                                                   |
76
    +=========================================================================+
77
    | | :code:`avl_exog_cap_derate_independent`                               |
78
    | | *Defined over*: :code:`AVL_EXOG_OPR_TMPS`                             |
79
    | | *Within*: :code:`NonNegativeReals`                                    |
80
    | | *Default*: :code:`1`                                                  |
81
    |                                                                         |
82
    | The pre-specified availability derate (e.g. for maintenance/planned     |
83
    | outages) that does not depend on weather. Defaults to 1 if not          |
84
    | specified. Availaibility can also be more than 1.                       |
85
    +-------------------------------------------------------------------------+
86
    | | :code:`avl_exog_cap_derate_weather`                                   |
87
    | | *Defined over*: :code:`AVL_EXOG_OPR_TMPS`                             |
88
    | | *Within*: :code:`NonNegativeReals`                                    |
89
    | | *Default*: :code:`1`                                                  |
90
    |                                                                         |
91
    | The pre-specified availability derate (e.g. for maintenance/planned     |
92
    | outages) that depends on weather. Defaults to 1 if not specified.       |
93
    | Availaibility can also be more than 1.                                  |
94
    +-------------------------------------------------------------------------+
95

96
    """
97

98
    # Sets
99
    ###########################################################################
100

101
    m.AVL_EXOG = Set(within=m.PROJECTS)
3✔
102

103
    m.AVL_EXOG_OPR_TMPS = Set(
3✔
104
        dimen=2,
105
        within=m.PRJ_OPR_TMPS,
106
        initialize=lambda mod: subset_init_by_set_membership(
107
            mod=mod, superset="PRJ_OPR_TMPS", index=0, membership_set=mod.AVL_EXOG
108
        ),
109
    )
110

111
    m.AVL_EXOG_PRJ_BT_HRZ_W_WEATHER_DERATES = Set(
3✔
112
        dimen=3, within=m.PROJECTS * m.BLN_TYPE_HRZS
113
    )
114

115
    m.AVL_EXOG_PRJ_BT_HRZ_W_INDEPENDENT_DERATES = Set(
3✔
116
        dimen=3, within=m.PROJECTS * m.BLN_TYPE_HRZS
117
    )
118

119
    # Required Params
120
    ###########################################################################
121

122
    # For hybrids, this is the derate applied to the generator component
123
    m.avl_exog_cap_derate_independent = Param(
3✔
124
        m.AVL_EXOG_OPR_TMPS, within=NonNegativeReals, default=1
125
    )
126

127
    m.avl_exog_cap_derate_weather = Param(
3✔
128
        m.AVL_EXOG_OPR_TMPS, within=NonNegativeReals, default=1
129
    )
130

131
    m.avl_exog_hyb_stor_cap_derate_independent = Param(
3✔
132
        m.AVL_EXOG_OPR_TMPS, within=NonNegativeReals, default=1
133
    )
134

135
    m.avl_exog_cap_derate_weather_bt_hrz = Param(
3✔
136
        m.AVL_EXOG_PRJ_BT_HRZ_W_WEATHER_DERATES, within=NonNegativeReals, default=1
137
    )
138

139
    m.avl_exog_cap_derate_independent_bt_hrz = Param(
3✔
140
        m.AVL_EXOG_PRJ_BT_HRZ_W_INDEPENDENT_DERATES, within=NonNegativeReals, default=1
141
    )
142

143
    # Make timepoint params from the bt-hrz params
144
    # Note that if timepoints fall within multiple bt-hrz with derates,
145
    # the derates will be additive
146
    def hrz_cap_derate_weather_by_tmp_init(mod):
3✔
147
        """
148
        Note that if timepoints fall within multiple bt-hrz with derates,
149
        the bt-hrz derates will be additive.
150
        """
151
        availability_reduction_dict = {}
3✔
152
        for prj, bt, hrz in mod.AVL_EXOG_PRJ_BT_HRZ_W_WEATHER_DERATES:
3✔
153
            for tmp in mod.TMPS_BY_BLN_TYPE_HRZ[bt, hrz]:
3✔
154
                if (prj, tmp) not in availability_reduction_dict.keys():
3✔
155
                    availability_reduction_dict[(prj, tmp)] = (
3✔
156
                        1 - mod.avl_exog_cap_derate_weather_bt_hrz[prj, bt, hrz]
157
                    )
158
                else:
159
                    availability_reduction_dict[(prj, tmp)] += (
×
160
                        1 - mod.avl_exog_cap_derate_weather_bt_hrz[prj, bt, hrz]
161
                    )
162

163
        availability_dict = {
3✔
164
            (prj, tmp): 1 - availability_reduction_dict[(prj, tmp)]
165
            for (prj, tmp) in availability_reduction_dict.keys()
166
        }
167

168
        return availability_dict
3✔
169

170
    m.avl_exog_cap_derate_weather_bt_hrz_by_tmp = Param(
3✔
171
        m.AVL_EXOG_OPR_TMPS,
172
        within=NonNegativeReals,
173
        initialize=hrz_cap_derate_weather_by_tmp_init,
174
        default=1,
175
    )
176

177
    def hrz_cap_derate_independent_by_tmp_init(mod):
3✔
178
        """
179
        Note that if timepoints fall within multiple bt-hrz with derates,
180
        the bt-hrz derates will be additive.
181
        """
182
        availability_reduction_dict = {}
3✔
183
        for prj, bt, hrz in mod.AVL_EXOG_PRJ_BT_HRZ_W_INDEPENDENT_DERATES:
3✔
184
            for tmp in mod.TMPS_BY_BLN_TYPE_HRZ[bt, hrz]:
3✔
185
                if (prj, tmp) not in availability_reduction_dict.keys():
3✔
186
                    availability_reduction_dict[(prj, tmp)] = (
3✔
187
                        1 - mod.avl_exog_cap_derate_independent_bt_hrz[prj, bt, hrz]
188
                    )
189
                else:
190
                    availability_reduction_dict[(prj, tmp)] += (
×
191
                        1 - mod.avl_exog_cap_derate_independent_bt_hrz[prj, bt, hrz]
192
                    )
193

194
        availability_dict = {
3✔
195
            (prj, tmp): 1 - availability_reduction_dict[(prj, tmp)]
196
            for (prj, tmp) in availability_reduction_dict.keys()
197
        }
198

199
        return availability_dict
3✔
200

201
    m.avl_exog_cap_derate_independent_bt_hrz_by_tmp = Param(
3✔
202
        m.AVL_EXOG_OPR_TMPS,
203
        within=NonNegativeReals,
204
        initialize=hrz_cap_derate_independent_by_tmp_init,
205
        default=1,
206
    )
207

208

209
# Availability Type Methods
210
###############################################################################
211

212

213
def availability_derate_cap_rule(mod, g, tmp):
3✔
214
    """ """
215
    return (
3✔
216
        mod.avl_exog_cap_derate_independent[g, tmp]
217
        * mod.avl_exog_cap_derate_weather[g, tmp]
218
        * mod.avl_exog_cap_derate_weather_bt_hrz_by_tmp[g, tmp]
219
        * mod.avl_exog_cap_derate_independent_bt_hrz_by_tmp[g, tmp]
220
    )
221

222

223
def availability_derate_hyb_stor_cap_rule(mod, g, tmp):
3✔
224
    """ """
225
    return mod.avl_exog_hyb_stor_cap_derate_independent[g, tmp]
3✔
226

227

228
# Input-Output
229
###############################################################################
230

231

232
def load_model_data(
3✔
233
    m,
234
    d,
235
    data_portal,
236
    scenario_directory,
237
    weather_iteration,
238
    hydro_iteration,
239
    availability_iteration,
240
    subproblem,
241
    stage,
242
):
243
    """
244
    :param m:
245
    :param data_portal:
246
    :param scenario_directory:
247
    :param subproblem:
248
    :param stage:
249
    :return:
250
    """
251
    # Figure out which projects have this availability type
252
    # Check the 'exogenous' is the default availability type
253
    from gridpath.project import DEFAULT_AVAILABILITY_TYPE
3✔
254

255
    if DEFAULT_AVAILABILITY_TYPE != "exogenous":
3✔
NEW
256
        raise Exception(
×
257
            "The exogenous availability type must be the default."
258
            "The 'exogenous' availabilty type assumes this in determing inputs."
259
        )
260

261
    project_subset = determine_project_subset(
3✔
262
        scenario_directory=scenario_directory,
263
        weather_iteration=weather_iteration,
264
        hydro_iteration=hydro_iteration,
265
        availability_iteration=availability_iteration,
266
        subproblem=subproblem,
267
        stage=stage,
268
        column="availability_type",
269
        type="exogenous",
270
        prj_or_tx="project",
271
    ) + determine_project_subset(
272
        scenario_directory=scenario_directory,
273
        weather_iteration=weather_iteration,
274
        hydro_iteration=hydro_iteration,
275
        availability_iteration=availability_iteration,
276
        subproblem=subproblem,
277
        stage=stage,
278
        column="availability_type",
279
        type=".",  # Projects without availabilty type specified default to
280
        # exogenous
281
        prj_or_tx="project",
282
    )
283

284
    data_portal.data()["AVL_EXOG"] = {None: project_subset}
3✔
285

286
    # Availability derates
287
    # Get any derates from the project_availability.tab file if it exists;
288
    # if it does not exist, all projects will get 1 as derate; if it does
289
    # exist but projects are not specified in it, they will also get 1
290
    # assigned as their derate
291
    # The test examples do not currently have a
292
    # project_availability_exogenous_x.tab, but use the default instead
293

294
    input_directory = os.path.join(
3✔
295
        scenario_directory,
296
        weather_iteration,
297
        hydro_iteration,
298
        availability_iteration,
299
        subproblem,
300
        stage,
301
        "inputs",
302
    )
303

304
    availability_independent_file = os.path.join(
3✔
305
        input_directory,
306
        "project_availability_exogenous_independent.tab",
307
    )
308

309
    if os.path.exists(availability_independent_file):
3✔
310
        data_portal.load(
3✔
311
            filename=availability_independent_file,
312
            param=(
313
                m.avl_exog_cap_derate_independent,
314
                m.avl_exog_hyb_stor_cap_derate_independent,
315
            ),
316
        )
317

318
    availability_weather_file = os.path.join(
3✔
319
        input_directory,
320
        "project_availability_exogenous_weather.tab",
321
    )
322

323
    if os.path.exists(availability_weather_file):
3✔
324
        data_portal.load(
3✔
325
            filename=availability_weather_file,
326
            param=m.avl_exog_cap_derate_weather,
327
        )
328

329
    # Balancing type - horizon inputs
330
    availability_independent_bt_hrz_file = os.path.join(
3✔
331
        input_directory,
332
        "project_availability_exogenous_independent_bt_hrz.tab",
333
    )
334

335
    if os.path.exists(availability_independent_bt_hrz_file):
3✔
336
        data_portal.load(
3✔
337
            filename=availability_independent_bt_hrz_file,
338
            index=m.AVL_EXOG_PRJ_BT_HRZ_W_INDEPENDENT_DERATES,
339
            param=m.avl_exog_cap_derate_independent_bt_hrz,
340
        )
341

342
    availability_weather_bt_hrz_file = os.path.join(
3✔
343
        input_directory,
344
        "project_availability_exogenous_weather_bt_hrz.tab",
345
    )
346

347
    if os.path.exists(availability_weather_bt_hrz_file):
3✔
348
        data_portal.load(
3✔
349
            filename=availability_weather_bt_hrz_file,
350
            index=m.AVL_EXOG_PRJ_BT_HRZ_W_WEATHER_DERATES,
351
            param=m.avl_exog_cap_derate_weather_bt_hrz,
352
        )
353

354

355
# Database
356
###############################################################################
357

358

359
def get_inputs_from_database(
3✔
360
    scenario_id,
361
    subscenarios,
362
    weather_iteration,
363
    hydro_iteration,
364
    availability_iteration,
365
    subproblem,
366
    stage,
367
    conn,
368
):
369
    """
370
    :param subscenarios:
371
    :param subproblem:
372
    :param stage:
373
    :param conn:
374
    :return:
375
    """
376

377
    # Derate by timepoint
378
    ind_sql = f"""
3✔
379
        SELECT project, timepoint, availability_derate_independent, 
380
        hyb_stor_cap_availability_derate_independent
381
        FROM inputs_project_availability_exogenous_independent
382
        -- Portfolio projects only
383
        WHERE project IN (
384
            SELECT project FROM inputs_project_portfolios
385
            WHERE project_portfolio_scenario_id = {subscenarios.PROJECT_PORTFOLIO_SCENARIO_ID}
386
        )
387
        -- Projects from this availability ID and type only
388
        AND project IN (
389
            SELECT project
390
            FROM inputs_project_availability
391
            WHERE project_availability_scenario_id = {subscenarios.PROJECT_AVAILABILITY_SCENARIO_ID}
392
            AND availability_type = 'exogenous'
393
            AND exogenous_availability_independent_scenario_id IS NOT NULL
394
        )
395
        -- Relevant optype opchar ID
396
        AND (project, exogenous_availability_independent_scenario_id) IN (
397
            SELECT project, exogenous_availability_independent_scenario_id
398
            FROM inputs_project_availability
399
            WHERE project_availability_scenario_id = {subscenarios.PROJECT_AVAILABILITY_SCENARIO_ID}
400
        )
401
        -- Relevant temporal index
402
        AND timepoint IN (
403
            SELECT timepoint
404
            FROM inputs_temporal
405
            WHERE temporal_scenario_id = {subscenarios.TEMPORAL_SCENARIO_ID}
406
            AND subproblem_id = {subproblem}
407
            AND stage_id = {stage}
408
        )
409
        -- Get the correct availability iteration
410
        AND availability_iteration = {availability_iteration}
411
        ;
412
    """
413

414
    c1 = conn.cursor()
3✔
415
    independent_availabilities = c1.execute(ind_sql)
3✔
416

417
    # Derate by timepoint and weather
418
    weather_sql = f"""
3✔
419
        SELECT project, timepoint, availability_derate_weather
420
        FROM inputs_project_availability_exogenous_weather
421
        -- Portfolio projects only
422
        WHERE project IN (
423
            SELECT project FROM inputs_project_portfolios
424
            WHERE project_portfolio_scenario_id = {subscenarios.PROJECT_PORTFOLIO_SCENARIO_ID}
425
        )
426
        -- Projects from this availability ID and type only
427
        AND project IN (
428
            SELECT project
429
            FROM inputs_project_availability
430
            WHERE project_availability_scenario_id = {subscenarios.PROJECT_AVAILABILITY_SCENARIO_ID}
431
            AND availability_type = 'exogenous'
432
            AND exogenous_availability_weather_scenario_id IS NOT NULL
433
        )
434
        -- Relevant optype opchar ID
435
        AND (project, exogenous_availability_weather_scenario_id) IN (
436
            SELECT project, exogenous_availability_weather_scenario_id
437
            FROM inputs_project_availability
438
            WHERE project_availability_scenario_id = {subscenarios.PROJECT_AVAILABILITY_SCENARIO_ID}
439
        )
440
        -- Relevant temporal index
441
        AND timepoint IN (
442
            SELECT timepoint
443
            FROM inputs_temporal
444
            WHERE temporal_scenario_id = {subscenarios.TEMPORAL_SCENARIO_ID}
445
            AND subproblem_id = {subproblem}
446
            AND stage_id = {stage}
447
        )
448
        -- Get the correct weather iteration
449
        AND weather_iteration = {weather_iteration}
450
        ;
451
    """
452

453
    c2 = conn.cursor()
3✔
454
    weather_availabilities = c2.execute(weather_sql)
3✔
455

456
    # Derate by balancing type - horizon
457
    bt_hrz_ind_sql = f"""
3✔
458
        SELECT project, balancing_type_project, horizon, availability_derate_independent_bt_hrz
459
        FROM inputs_project_availability_exogenous_independent_bt_hrz
460
        -- Portfolio projects only
461
        WHERE project IN (
462
            SELECT project FROM inputs_project_portfolios
463
            WHERE project_portfolio_scenario_id = {subscenarios.PROJECT_PORTFOLIO_SCENARIO_ID}
464
        )
465
        -- Projects from this availability ID and type only
466
        AND project IN (
467
            SELECT project
468
            FROM inputs_project_availability
469
            WHERE project_availability_scenario_id = {subscenarios.PROJECT_AVAILABILITY_SCENARIO_ID}
470
            AND availability_type = 'exogenous'
471
            AND exogenous_availability_independent_bt_hrz_scenario_id IS NOT NULL
472
        )
473
        -- Relevant optype opchar ID
474
        AND (project, exogenous_availability_independent_bt_hrz_scenario_id) IN (
475
            SELECT project, exogenous_availability_independent_bt_hrz_scenario_id
476
            FROM inputs_project_availability
477
            WHERE project_availability_scenario_id = {subscenarios.PROJECT_AVAILABILITY_SCENARIO_ID}
478
        )
479
        -- Relevant temporal index
480
        AND (balancing_type_project, horizon) IN (
481
            SELECT balancing_type_horizon, horizon
482
            FROM inputs_temporal_horizon_timepoints
483
            WHERE temporal_scenario_id = {subscenarios.TEMPORAL_SCENARIO_ID}
484
            AND subproblem_id = {subproblem}
485
        )
486
        -- Get the correct availability iteration
487
        AND availability_iteration = {availability_iteration}
488
        ;
489
    """
490

491
    c3 = conn.cursor()
3✔
492
    bt_hrz_independent_availabilities = c3.execute(bt_hrz_ind_sql)
3✔
493

494
    # Derate by timepoint and weather
495
    bt_hrz_weather_sql = f"""
3✔
496
        SELECT project, balancing_type_project, horizon, 
497
        availability_derate_weather_bt_hrz
498
        FROM inputs_project_availability_exogenous_weather_bt_hrz
499
        -- Portfolio projects only
500
        WHERE project IN (
501
            SELECT project FROM inputs_project_portfolios
502
            WHERE project_portfolio_scenario_id = {subscenarios.PROJECT_PORTFOLIO_SCENARIO_ID}
503
        )
504
        -- Projects from this availability ID and type only
505
        AND project IN (
506
            SELECT project
507
            FROM inputs_project_availability
508
            WHERE project_availability_scenario_id = {subscenarios.PROJECT_AVAILABILITY_SCENARIO_ID}
509
            AND availability_type = 'exogenous'
510
            AND exogenous_availability_weather_bt_hrz_scenario_id IS NOT NULL
511
        )
512
        -- Relevant optype opchar ID
513
        AND (project, exogenous_availability_weather_bt_hrz_scenario_id) IN (
514
            SELECT project, exogenous_availability_weather_bt_hrz_scenario_id
515
            FROM inputs_project_availability
516
            WHERE project_availability_scenario_id = {subscenarios.PROJECT_AVAILABILITY_SCENARIO_ID}
517
        )
518
        -- Relevant temporal index
519
        AND (balancing_type_project, horizon) IN (
520
            SELECT balancing_type_horizon, horizon
521
            FROM inputs_temporal_horizon_timepoints
522
            WHERE temporal_scenario_id = {subscenarios.TEMPORAL_SCENARIO_ID}
523
            AND subproblem_id = {subproblem}
524
        )
525
        -- Get the correct weather iteration
526
        AND weather_iteration = {weather_iteration}
527
        ;
528
    """
529

530
    c4 = conn.cursor()
3✔
531
    bt_hrz_weather_availabilities = c4.execute(bt_hrz_weather_sql)
3✔
532

533
    return (
3✔
534
        independent_availabilities,
535
        weather_availabilities,
536
        bt_hrz_independent_availabilities,
537
        bt_hrz_weather_availabilities,
538
    )
539

540

541
def write_model_inputs(
3✔
542
    scenario_directory,
543
    scenario_id,
544
    subscenarios,
545
    weather_iteration,
546
    hydro_iteration,
547
    availability_iteration,
548
    subproblem,
549
    stage,
550
    conn,
551
):
552
    """
553
    :param scenario_directory:
554
    :param subscenarios:
555
    :param subproblem:
556
    :param stage:
557
    :param conn:
558
    :return:
559
    """
560

561
    (
3✔
562
        db_weather_iteration,
563
        db_hydro_iteration,
564
        db_availability_iteration,
565
        db_subproblem,
566
        db_stage,
567
    ) = directories_to_db_values(
568
        weather_iteration, hydro_iteration, availability_iteration, subproblem, stage
569
    )
570

571
    (
3✔
572
        independent_availabilities,
573
        weather_availabilities,
574
        independent_availabilities_bt_hrz,
575
        weather_availabilities_bt_hrz,
576
    ) = get_inputs_from_database(
577
        scenario_id,
578
        subscenarios,
579
        db_weather_iteration,
580
        db_hydro_iteration,
581
        db_availability_iteration,
582
        db_subproblem,
583
        db_stage,
584
        conn,
585
    )
586

587
    write_tab_file_model_inputs(
3✔
588
        scenario_directory=scenario_directory,
589
        weather_iteration=weather_iteration,
590
        hydro_iteration=hydro_iteration,
591
        availability_iteration=availability_iteration,
592
        subproblem=subproblem,
593
        stage=stage,
594
        fname="project_availability_exogenous_weather.tab",
595
        data=weather_availabilities,
596
        replace_nulls=True,
597
    )
598

599
    write_tab_file_model_inputs(
3✔
600
        scenario_directory=scenario_directory,
601
        weather_iteration=weather_iteration,
602
        hydro_iteration=hydro_iteration,
603
        availability_iteration=availability_iteration,
604
        subproblem=subproblem,
605
        stage=stage,
606
        fname="project_availability_exogenous_independent.tab",
607
        data=independent_availabilities,
608
        replace_nulls=True,
609
    )
610

611
    write_tab_file_model_inputs(
3✔
612
        scenario_directory=scenario_directory,
613
        weather_iteration=weather_iteration,
614
        hydro_iteration=hydro_iteration,
615
        availability_iteration=availability_iteration,
616
        subproblem=subproblem,
617
        stage=stage,
618
        fname="project_availability_exogenous_weather.tab",
619
        data=weather_availabilities,
620
        replace_nulls=True,
621
    )
622

623
    write_tab_file_model_inputs(
3✔
624
        scenario_directory=scenario_directory,
625
        weather_iteration=weather_iteration,
626
        hydro_iteration=hydro_iteration,
627
        availability_iteration=availability_iteration,
628
        subproblem=subproblem,
629
        stage=stage,
630
        fname="project_availability_exogenous_independent_bt_hrz.tab",
631
        data=independent_availabilities_bt_hrz,
632
        replace_nulls=True,
633
    )
634

635
    write_tab_file_model_inputs(
3✔
636
        scenario_directory=scenario_directory,
637
        weather_iteration=weather_iteration,
638
        hydro_iteration=hydro_iteration,
639
        availability_iteration=availability_iteration,
640
        subproblem=subproblem,
641
        stage=stage,
642
        fname="project_availability_exogenous_weather_bt_hrz.tab",
643
        data=weather_availabilities_bt_hrz,
644
        replace_nulls=True,
645
    )
646

647

648
# Validation
649
###############################################################################
650

651

652
def validate_inputs(
3✔
653
    scenario_id,
654
    subscenarios,
655
    weather_iteration,
656
    hydro_iteration,
657
    availability_iteration,
658
    subproblem,
659
    stage,
660
    conn,
661
):
662
    """
663
    :param subscenarios:
664
    :param subproblem:
665
    :param stage:
666
    :param conn:
667
    :return:
668
    """
669
    (
3✔
670
        independent_availabilities,
671
        weather_availabilities,
672
        independent_availabilities_bt_hrz,
673
        weather_availabilities_bt_hrz,
674
    ) = get_inputs_from_database(
675
        scenario_id,
676
        subscenarios,
677
        weather_iteration,
678
        hydro_iteration,
679
        availability_iteration,
680
        subproblem,
681
        stage,
682
        conn,
683
    )
684

685
    df = cursor_to_df(independent_availabilities)
3✔
686
    idx_cols = ["project", "timepoint"]
3✔
687
    value_cols = ["availability_derate_independent"]
3✔
688

689
    # Check data types availability
690
    expected_dtypes = get_expected_dtypes(
3✔
691
        conn,
692
        [
693
            "inputs_project_availability",
694
            "inputs_project_availability_exogenous_independent",
695
        ],
696
    )
697
    dtype_errors, error_columns = validate_dtypes(df, expected_dtypes)
3✔
698
    write_validation_to_database(
3✔
699
        conn=conn,
700
        scenario_id=scenario_id,
701
        weather_iteration=weather_iteration,
702
        hydro_iteration=hydro_iteration,
703
        availability_iteration=availability_iteration,
704
        subproblem_id=subproblem,
705
        stage_id=stage,
706
        gridpath_module=__name__,
707
        db_table="inputs_project_availability_exogenous_independent",
708
        severity="High",
709
        errors=dtype_errors,
710
    )
711

712
    # Check for missing inputs
713
    msg = (
3✔
714
        "If not specified, availability is assumed to be 100%. If you "
715
        "don't want to specify any availability derates, simply leave the "
716
        "exogenous_availability_scenario_id empty and this message will "
717
        "disappear."
718
    )
719
    write_validation_to_database(
3✔
720
        conn=conn,
721
        scenario_id=scenario_id,
722
        weather_iteration=weather_iteration,
723
        hydro_iteration=hydro_iteration,
724
        availability_iteration=availability_iteration,
725
        subproblem_id=subproblem,
726
        stage_id=stage,
727
        gridpath_module=__name__,
728
        db_table="inputs_project_availability_exogenous_independent",
729
        severity="Low",
730
        errors=validate_missing_inputs(df, value_cols, idx_cols, msg),
731
    )
732

733
    # Check for correct sign
734
    if "availability" not in error_columns:
3✔
735
        write_validation_to_database(
3✔
736
            conn=conn,
737
            scenario_id=scenario_id,
738
            weather_iteration=weather_iteration,
739
            hydro_iteration=hydro_iteration,
740
            availability_iteration=availability_iteration,
741
            subproblem_id=subproblem,
742
            stage_id=stage,
743
            gridpath_module=__name__,
744
            db_table="inputs_project_availability_exogenous_independent",
745
            severity="High",
746
            errors=validate_values(df, value_cols, min=0, max=1),
747
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc