Calendar, holidays, leave and schedules¶
Weekends, public holidays, leave and shifts are yours to define. The processor only talks to provider interfaces, each set by a dotted-path setting.
| Setting | Interface | Default |
|---|---|---|
CALENDAR_PROVIDER |
BaseCalendarProvider (get_day_info, get_day_type, get_range) |
DefaultCalendarProvider: public holiday > weekend > leave > working day |
HOLIDAY_PROVIDER |
BaseHolidayProvider (get_holidays(start, end, enrollee=)) |
chained over HOLIDAY_PROVIDER_CHAIN = ["database", "settings"] |
LEAVE_PROVIDER |
BaseLeaveProvider (get_leaves(enrollee, start, end)) |
NullLeaveProvider (connect your HR system) |
SCHEDULE_PROVIDER |
BaseScheduleProvider (get_expected_shift(enrollee, date) -> ShiftInfo) |
settings (DEFAULT_SCHEDULE); database uses WorkSchedule / ShiftAssignment |
Holidays¶
SettingsHolidayProvider:HOLIDAYS = [{"date": "2026-10-01", "name": "Independence Day", "recurring": true}]DatabaseHolidayProvider:Holidayrows (admin and/holidays/API). Scope isall,device_grouporenrollee(most specific wins). Rows can recur yearly, and anis_cancelledrow cancels a holiday from lower-priority providers.PythonHolidaysProvider([holidays]extra):HOLIDAYS_COUNTRY="NG", optionalHOLIDAYS_SUBDIVISION.ChainedHolidayProvider: the first provider with any entry for a date decides that date.
Some governments declare or move holidays at short notice (Nigeria often does). Put
database first in the chain. To move a holiday, add a cancelled row on the old date and a
new row on the new date. To add a surprise holiday, add one row. Days already computed are
recomputed automatically.
FINGERPRINT_ATTENDANCE = {
"HOLIDAY_PROVIDER_CHAIN": ["database", "holidays"],
"HOLIDAYS_COUNTRY": "NG",
"WEEKEND_DAYS": [5, 6],
"WEEKEND_DAYS_BY_GROUP": {"Gulf office": [4, 5]},
}
Day types and statuses¶
Day types (workday, weekend, public_holiday, leave, off) and statuses (present,
absent, late, early_leave, incomplete, worked_on_off_day, worked_on_holiday,
on_leave, off) are open registries:
from fingerprint_attendance.registry import day_types, attendance_statuses
day_types.register("company_event", "Company event", is_working=True)
attendance_statuses.register("attended_event", "Attended event")
STATUS_RULES is an ordered list of callables rule(ctx) -> str | list[str] | None. Returned
statuses are appended, the first becomes AttendanceDay.status, and ctx.stop = True ends
evaluation. Add or replace one rule without touching the rest:
from fingerprint_attendance.conf import SPEC_BY_NAME
FINGERPRINT_ATTENDANCE["STATUS_RULES"] = ["myapp.rules.company_event",
*SPEC_BY_NAME["STATUS_RULES"].default]
Schedules and overnight shifts¶
DEFAULT_SCHEDULE = {"start": "09:00", "end": "17:00", "grace_in_minutes": 10,
"grace_out_minutes": 5, "break_minutes": 60, "weekdays": [0,1,2,3,4]}. Without a schedule
there are no late or early statuses.
With SCHEDULE_MODELS_ENABLED and SCHEDULE_PROVIDER="database", ShiftAssignment links a
WorkSchedule to an enrollee or device group over a date range. The highest priority wins,
and enrollee-specific beats group-level.
For night shifts use DAY_BOUNDARY_RESOLVER="shift_aware". Punches up to
OVERNIGHT_SHIFT_MARGIN after an overnight shift ends belong to the date the shift started.
offset (with DAY_START_TIME) is a simpler alternative.
Absences¶
fpa_generate_absences --date (or the periodic job) creates day rows for every active
enrollee (created before that date) after ABSENCE_CUTOFF_TIME: absent on working days
without punches, on_leave, off on non-working days. It is switched with
ABSENCE_GENERATION_ENABLED.
Recompute on change¶
Holiday, schedule and assignment changes recompute affected days automatically. Call
services.calendar_changed(start, end, enrollee_ids=[...]) when leave or rosters change in
your own systems. Punches on non-working days are always stored; only their classification
changes.
API¶
GET /api/fingerprint/v1/calendar/?enrollee=<id>&from=2026-01-01&to=2026-01-31 returns
resolved days and expected shifts, using the same logic as the processor, so front ends and
reports agree with AttendanceDay.