Skip to content

Commit

Permalink
Prepped for branching to overhaul front-end, leaving offsets publicly…
Browse files Browse the repository at this point in the history
… available in dev in the meantime. Functional but untested, and could be useful to someone who stumbles upon our little library here.
  • Loading branch information
seekinginfiniteloop committed Jan 17, 2024
1 parent 71b3575 commit accb251
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 137 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

Current version - unstable alpha 0.1

### Current Status

**In active early-stage development.** Completely overhauled/refactored back-end, now it's time for the front end. I'm branching off that development from dev until further along. **Offsets module and its helpers are functional but mostly untested**, but I wanted to make it available while I overhaul the rest of the front-end. With the exception of FedHolidays, offsets are fully vectorized for snappy scalar and array operations (which isn't entirely true for pandas native offsets, at some point I may try to rewrite pd.AbstractHolidayCalendar to be more vectorized... maybe even pull it into Cython). On the branch, I'm **working to fully integrate fedcal functionality into Pandas' extensions API** (and maybe some more direct reverse engineering hacks--if necessary--like I did with the offsets module), **providing fedcal accessors to Index, Series, and DataFrame for seamless integration, and once tested, joining the pydata extensions community**. This will be functionally similar to the 'dt' accessor for datetime Series, (e.g. my_series.fc.fy will yield the Federal fiscal year for the date(s)). I'm not yet sure how I'm going to build in front-end executive department appropriations status data, possibly with a custom pandas ExtensionArray and supporting dtype for rich functionality. In digging through Pandas source to get the library where it is, I learned a lot about how pandas ticks, so I'm hoping to put that knowledge to work for fedcal.

### fedcal

fedcal is a simple calendar library with **_one big goal big goal_:** **enable new perspective on the U.S. Government to build transparency, improve government, and bolster democracy.**

### a calendar... why?
Expand All @@ -15,7 +21,7 @@ Time is at the heart of those impacts. I'm a federal manager, and I started writ
### fedcal is about answering big and small questions, improving predictions, and understanding the U.S. Government, its effect on society and the world

> [!NOTE]
> The Federal Government is a big control group (of sorts) if you think about it. **Two million people are mostly at work... or they aren't** because of holidays, weekends, military passdays, or government shutdowns. One minute the government is 'on', and the next it is 'off'. Of course, it's more complicated than that -- sometimes only half, a third, or 90% of the government is impacted, which offers opportunities for even richer differential analysis (and one fedcal hopes to enable). But it's not just shutdowns -- a continuing resolution instead of a full year appropriation is another kind of binary relationship, or even whether a holiday falls on a Monday or Tuesday. fedcal aims to give you the tools to explore these relationships and their significance (...or insignificance).
> If you think about it, the U.S. Government is a big control group, of sorts. **Two million people are mostly at work... or they aren't** because of holidays, weekends, military passdays, or government shutdowns. One minute the government is 'on', and the next it is 'off'. Of course, it's more complicated than that -- sometimes only half, a third, or 90% of the government is impacted, which offers opportunities for even richer differential analysis (and one fedcal hopes to enable). But it's not just shutdowns -- a continuing resolution instead of a full year appropriation is another kind of binary relationship, or even whether a holiday falls on a Monday or Tuesday. fedcal aims to give you the tools to explore these relationships and their significance (...or insignificance).
#### A few example questions fedcal can help answer:

Expand Down
16 changes: 8 additions & 8 deletions fedcal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@

from ._base import MagicDelegator
from .enum import Dept, DeptStatus, DoW, Month
from .fedindex import FedIndex, to_fedindex
from .fedstamp import FedStamp, to_fedstamp
# from .fedindex import FedIndex, to_fedindex
# from .fedstamp import FedStamp, to_fedstamp
from .fiscal import FedFiscalCal
from .offsets import (
FedBusinessDay,
Expand All @@ -37,6 +37,7 @@
MilitaryPassDay,
MilitaryPayDay,
)
# from .status import GovStatus
from .utils import (
dt64_to_date,
dt64_to_dow,
Expand All @@ -45,7 +46,6 @@
to_dt64,
to_timestamp,
)
from .status import GovStatus

__all__: list[str] = [
"Dept",
Expand All @@ -54,10 +54,10 @@
"FedBusinessDay",
"FedFiscalCal",
"FedHolidays",
"FedIndex",
# "FedIndex",
"FedPayDay",
"FedStamp",
"GovStatus",
# "FedStamp",
# "GovStatus",
"MagicDelegator",
"MilitaryPassDay",
"MilitaryPayDay",
Expand All @@ -67,7 +67,7 @@
"iso_to_ts",
"to_datetimeindex",
"to_dt64",
"to_fedindex",
"to_fedstamp",
# "to_fedindex",
# "to_fedstamp",
"to_timestamp",
]
11 changes: 8 additions & 3 deletions fedcal/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,14 +295,15 @@ def list_vals(cls) -> list[int | str]:
"""
Simple classmethod to return the values of members.
"""
get_
return sorted(list(map(lambda c: c.value, cls)))

@classmethod
def list_by_attr(cls, attr: str) -> list[Any]:
"""
Simple classmethod to return the attributes of members.
"""
return sorted([member.attr for member in cls.members()])
return sorted([getattr(member, attr) for member in cls.members()])

@classmethod
def list_member_attrs(cls, member: EnumType) -> list[Any]:
Expand All @@ -315,12 +316,16 @@ def list_member_attrs(cls, member: EnumType) -> list[Any]:
)

@classmethod
def members(cls) -> list[Type[EnumType]]:
def member_names(cls) -> list[str]:
"""
Simple classmethod to return the members of the enum class.
Simple classmethod to return the member names of the enum class.
"""
return sorted(list(cls.__members__))

@classmethod
def members(cls) -> list[Type[EnumType]]:
return [cls.__members__.get(name) for name in cls.__members__]

@classmethod
def zip(cls) -> list[tuple[Any]]:
"""
Expand Down
3 changes: 2 additions & 1 deletion fedcal/_status_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@
from pathlib import Path

import pandas as pd
from pandas import MultiIndex, Timestamp

from fedcal._typing import RefinedIntervalType
from fedcal.enum import Dept, DeptStatus
from fedcal.utils import iso_to_ts
from pandas import MultiIndex, Timestamp

# set path to our JSON data as path to our module... plus filename, of course.
json_file_path: Path = Path(__file__).parent / "status_intervals.json"
Expand Down
45 changes: 18 additions & 27 deletions fedcal/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,24 @@ def __init__(self, abbreviation: str, full_name: str, short_name: str) -> None:
self.full: str = full_name # full name in mixed case
self.short: str = short_name # shortened name in mixed case

def get_representation(
self,
short_form: bool = True,
long_form: bool = False,
abbrev: bool = False,
obj: bool = False,
) -> Dept | str:
representation_map = {
"obj": self,
"short_form": self.short,
"long_form": self.full,
"abbrev": self.abbrev,
}
return next(
(value for key, value in representation_map.items() if locals()[key]),
self.__str__,
)

def __str__(self) -> str:
"""
Customized string representation of enum
Expand Down Expand Up @@ -161,33 +179,6 @@ def _lookup_attributes(cls: Type[EnumType]) -> Iterable[str]:
return ["abbrev", "full", "short"]


depts_set: set[Dept] = {
Dept.DHS,
Dept.DOC,
Dept.DOD,
Dept.DOE,
Dept.DOI,
Dept.DOJ,
Dept.DOL,
Dept.DOS,
Dept.DOT,
Dept.ED,
Dept.HHS,
Dept.HUD,
Dept.IA,
Dept.PRES,
Dept.USDA,
Dept.USDT,
Dept.VA,
}

"""
depts_set: A set of top-level executive departments as enum
objects. Data currently omit judiciary and legislative budgets (federal courts
and Congress).
"""


@unique
class DeptStatus(EnumBase, HandyEnumMixin, Enum):
"""
Expand Down
4 changes: 2 additions & 2 deletions fedcal/fiscal.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from pandas import DatetimeIndex, Index, PeriodIndex, Series, Timestamp

from fedcal._typing import TimestampSeries
from fedcal.utils import ensure_datetimeindex, to_datetimeindex
from fedcal.utils import ensure_datetimeindex, set_default_range


@dataclass(order=True, slots=True)
Expand Down Expand Up @@ -65,7 +65,7 @@ class FedFiscalCal:
"""

dates: DatetimeIndex | TimestampSeries | Timestamp | None = field(
default=to_datetimeindex((1970, 1, 1), (2199, 12, 31))
default_factory=set_default_range()
)

fys_fqs: PeriodIndex | None = field(default=None, init=False)
Expand Down
36 changes: 20 additions & 16 deletions fedcal/offsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@
from numpy import datetime64, int32, int64, timedelta64
from numpy.typing import NDArray
from pandas import DatetimeIndex, Index, Series, Timedelta, Timestamp
from pandas._libs.tslibs.offsets import SemiMonthOffset, apply_wraps, shift_month
from pandas._libs.tslibs.offsets import apply_wraps, SemiMonthOffset, shift_month
from pandas.tseries.holiday import (
AbstractHolidayCalendar,
Holiday,
nearest_workday,
USColumbusDay,
USLaborDay,
USMartinLutherKingJr,
USMemorialDay,
USPresidentsDay,
USThanksgivingDay,
nearest_workday,
)
from pandas.tseries.offsets import CustomBusinessDay, Week

Expand Down Expand Up @@ -581,7 +581,7 @@ class FedBusinessDay(CustomBusinessDay):
_normalize: bool = field(default=True, init=False)

_holidays: list[Timestamp] | NDArray[np.datetime64] | None = field(
default=FedHolidays().np_holidays
default_factory=FedHolidays().np_holidays
)
off_set: timedelta | Timedelta = field(default=timedelta(days=0), init=False)

Expand Down Expand Up @@ -715,8 +715,8 @@ class MilitaryPayDay(SemiMonthOffset):
_min_day_of_month: int = 7

_normalize: bool = field(default=True, init=False)
b_day: FedBusinessDay = FedBusinessDay()
calendar: np.busdaycalendar = field(default=b_day.calendar, init=False)
b_day: FedBusinessDay = None
calendar: np.busdaycalendar = field(init=False)

def __post_init__(self) -> None:
"""
Expand Down Expand Up @@ -856,6 +856,19 @@ def rollforward(self, dt: DatetimeScalarOrArray):
# TODO: implement custom rollback/rollforward with array support


def _set_default_passday_map() -> dict[str, str]:
"""
Default passday mapping for MilitaryPassDay.
"""
return {
"Mon": "Fri", # [key] holiday day of observance to:
"Tue": "Mon", # value day of associated passday
"Wed": "Thu",
"Thu": "Fri",
"Fri": "Mon",
}


@dataclass(order=False, slots=False)
class MilitaryPassDay(CustomBusinessDay):
"""
Expand Down Expand Up @@ -931,20 +944,12 @@ class MilitaryPassDay(CustomBusinessDay):

_normalize: bool = field(default=True, init=False)

b_day = FedBusinessDay()
b_day = None

# mapping of holiday-day-of-the-week to passday-day-of-the-week
# default is Friday passday for Thursday or Monday holiday, Monday passday
# for Friday or Tuesday holiday, and Thursday passday for Wednesday holiday
passday_map: dict[str, str] = field(
default={
"Mon": "Fri", # [key] holiday day of observance to:
"Tue": "Mon", # value day of associated passday
"Wed": "Thu",
"Thu": "Fri",
"Fri": "Mon",
}
)
passday_map: dict[str, str] = field(default_factory=_set_default_passday_map)

_map: dict | None = None

Expand All @@ -959,7 +964,6 @@ def _post_init__(self) -> None:
values.
"""
if not hasattr(self.b_day, "calendar"):
FedHolidays()
self.b_day = FedBusinessDay()
super().__init__(
n=1,
Expand Down
Loading

0 comments on commit accb251

Please sign in to comment.