from ehrql import days, maximum_of, minimum_of, weeks
from ehrql.tables import core, emis, tpp
# A file to keep track of things where we get good autocomplete behaviour.
#
# Anything in this file is automatically checked by the test file test_autocomplete.py,
# though it must be formatted correctly or the tests will fail. Currently you can:
#
# - write a single command and confirm the type e.g.:
# core.patients.date_of_birth ## type:DatePatientSeries
#
# - assign a single command to a variable and confirm the type e.g.:
# bool_invert = ~core.patients.exists_for_patient() ## type:BoolPatientSeries
#
# - either of the above but spanning multiple lines e.g.
# bool_and = (
# core.patients.exists_for_patient() & core.patients.exists_for_patient()
# ) ## type:BoolPatientSeries
# Currently all columns on all tables have type of "Series | Any",
# so we don't get autocomplete. Providing type hints on the Series
# class, and on the @table decorator means the column types are
# correct e.g. IntPatientSeries, DateEventSeries etc. and therefore
# we get autocomplete for all the properties and methods on each series.
core.patients.date_of_birth ## type:DatePatientSeries
core.patients.sex ## type:StrPatientSeries
core.ons_deaths.underlying_cause_of_death ## type:CodePatientSeries
core.clinical_events.date ## type:DateEventSeries
core.clinical_events.numeric_value ## type:FloatEventSeries
core.clinical_events.snomedct_code ## type:CodeEventSeries
tpp.apcs.all_diagnoses ## type:MultiCodeStringEventSeries
tpp.addresses.has_postcode ## type:BoolEventSeries
tpp.addresses.address_id ## type:IntEventSeries
# There are some methods that always return the same type
core.clinical_events.snomedct_code.count_distinct_for_patient() ## type:IntPatientSeries
core.clinical_events.numeric_value.mean_for_patient() ## type:FloatPatientSeries
core.clinical_events.date.count_episodes_for_patient(weeks(1)) ## type:IntPatientSeries
core.patients.exists_for_patient() ## type:BoolPatientSeries
core.patients.count_for_patient() ## type:IntPatientSeries
bool_eq = days(100) == days(100) ## type:bool
bool_neq = days(100) != days(100) ## type:bool
# There are some things that return the same type as the calling
# object or one of the arguments
bool_and = (
core.patients.exists_for_patient() & core.patients.exists_for_patient()
) ## type:BoolPatientSeries
bool_or = (
core.patients.exists_for_patient() | core.patients.exists_for_patient()
) ## type:BoolPatientSeries
bool_invert = ~core.patients.exists_for_patient() ## type:BoolPatientSeries
numeric_neg = -core.clinical_events.numeric_value ## type:FloatEventSeries
core.clinical_events.date.to_first_of_year() ## type:DateEventSeries
core.patients.date_of_birth.to_first_of_month() ## type:DatePatientSeries
duration_negation = -days(100) ## type:days
core.patients.sex.when_null_then(core.patients.sex) ## type:StrPatientSeries
duration_add = days(100) + core.patients.date_of_birth ## type:DatePatientSeries
duration_radd = core.clinical_events.date + days(100) ## type:DateEventSeries
duration_add_duration = days(100) + days(100) ## type:days
# duration_rsub = core.clinical_events.date - days(100) ## type: DateEventSeries
# !!! the above doesn't work and thinks its a DateDifference. I assume
# !!! because the NotImplemented on the DateFunctions __sub__ method
# !!! is only known at runtime
# !!! commenting out until we fix it
duration_sub_duration = days(100) - days(100) ## type: days
# There are loads of things that return a series where the typ
# (int, str, float etc.) is fixed, but it can be a PatientSeries
# or an EventSeries depending on whether the calling object is a
# PatientSeries or an EventSeries. This can be achieved with two
# overloaded methods and type hints
#
# BaseSeries
#
base_eq = core.patients.sex == core.patients.sex ## type:BoolPatientSeries
base_ne = (
core.clinical_events.date != core.clinical_events.date
) ## type:BoolEventSeries
core.patients.sex.is_null() ## type:BoolPatientSeries
core.clinical_events.date.is_not_null() ## type:BoolEventSeries
core.patients.sex.is_in([]) ## type:BoolPatientSeries
core.clinical_events.snomedct_code.is_not_in([]) ## type:BoolEventSeries
#
# ComparableFunctions
#
comparable_lt = (
core.clinical_events.numeric_value < core.clinical_events.numeric_value
) ## type:BoolEventSeries
comparable_le = (
core.clinical_events.numeric_value <= core.clinical_events.numeric_value
) ## type:BoolEventSeries
comparable_gt = (
core.clinical_events.numeric_value > core.clinical_events.numeric_value
) ## type:BoolEventSeries
comparable_ge = (
core.clinical_events.numeric_value >= core.clinical_events.numeric_value
) ## type:BoolEventSeries
#
# StrFunctions
#
core.patients.sex.contains("m") ## type:BoolPatientSeries
#
# NumericFunctions
#
numeric_truediv = core.clinical_events.numeric_value / 10 ## type:FloatEventSeries
numeric_rtruediv = 10 / core.clinical_events.numeric_value ## type:FloatEventSeries
numeric_floordiv = core.clinical_events.numeric_value // 10 ## type:IntEventSeries
numeric_rfloordiv = 10 // core.clinical_events.numeric_value ## type:IntEventSeries
core.clinical_events.numeric_value.as_int() ## type:IntEventSeries
core.clinical_events.numeric_value.as_float() ## type:FloatEventSeries
#
# DateFunctions
#
date_str = "2024-01-01"
core.patients.date_of_birth.is_before(date_str) ## type:BoolPatientSeries
core.patients.date_of_birth.is_on_or_before(date_str) ## type:BoolPatientSeries
core.patients.date_of_birth.is_after(date_str) ## type:BoolPatientSeries
core.patients.date_of_birth.is_on_or_after(date_str) ## type:BoolPatientSeries
core.clinical_events.date.is_between_but_not_on(
date_str, date_str
) ## type:BoolEventSeries
core.clinical_events.date.is_on_or_between(date_str, date_str) ## type:BoolEventSeries
core.clinical_events.date.is_during((date_str, date_str)) ## type:BoolEventSeries
#
# MultiCodeStringFunctions
#
tpp.apcs.all_diagnoses.contains("N13") ## type:BoolEventSeries
tpp.apcs.all_diagnoses.contains_any_of(["N13"]) ## type:BoolEventSeries
#
# Couple of random list[tuple] types
starting_on = weeks(3).starting_on("2000-01-01")[0][0] ## type:date
ending_on = weeks(3).ending_on("2000-01-01")[0][0] ## type:date
#
# Things that aggregate from EventSeries to PatientSeries
# but that need to maintain the type (int, float, bool etc)
core.clinical_events.numeric_value.sum_for_patient() ## type:FloatPatientSeries
core.clinical_events.numeric_value.as_int().sum_for_patient() ## type:IntPatientSeries
core.clinical_events.numeric_value.minimum_for_patient() ## type:FloatPatientSeries
core.clinical_events.numeric_value.as_int().minimum_for_patient() ## type:IntPatientSeries
tpp.addresses.msoa_code.minimum_for_patient() ## type:StrPatientSeries
core.clinical_events.date.minimum_for_patient() ## type:DatePatientSeries
core.clinical_events.numeric_value.maximum_for_patient() ## type:FloatPatientSeries
core.clinical_events.numeric_value.as_int().maximum_for_patient() ## type:IntPatientSeries
tpp.addresses.msoa_code.maximum_for_patient() ## type:StrPatientSeries
core.clinical_events.date.maximum_for_patient() ## type:DatePatientSeries
#
# NumericFunctions which maintain the series (Event or Patient)
# and the type (int or float)
numeric_add = core.clinical_events.numeric_value + 10 ## type:FloatEventSeries
numeric_radd = 10 + core.clinical_events.numeric_value.as_int() ## type:IntEventSeries
numeric_add_patient = (
core.clinical_events.numeric_value.maximum_for_patient() + 10
) ## type:FloatPatientSeries
numeric_radd_patient = (
10 + core.clinical_events.numeric_value.as_int().maximum_for_patient()
) ## type:IntPatientSeries
numeric_add_series = (
core.clinical_events.numeric_value + core.clinical_events.numeric_value
) ## type:FloatEventSeries
numeric_sub = core.clinical_events.numeric_value - 10 ## type:FloatEventSeries
numeric_rsub = 10 - core.clinical_events.numeric_value.as_int() ## type:IntEventSeries
numeric_sub_patient = (
core.clinical_events.numeric_value.maximum_for_patient() - 10
) ## type:FloatPatientSeries
numeric_rsub_patient = (
10 - core.clinical_events.numeric_value.as_int().maximum_for_patient()
) ## type:IntPatientSeries
numeric_sub_series = (
core.clinical_events.numeric_value - core.clinical_events.numeric_value
) ## type:FloatEventSeries
numeric_mul = core.clinical_events.numeric_value * 10 ## type:FloatEventSeries
numeric_rmul = 10 * core.clinical_events.numeric_value.as_int() ## type:IntEventSeries
numeric_mul_patient = (
core.clinical_events.numeric_value.maximum_for_patient() * 10
) ## type:FloatPatientSeries
numeric_rmul_patient = (
10 * core.clinical_events.numeric_value.as_int().maximum_for_patient()
) ## type:IntPatientSeries
numeric_mul_series = (
core.clinical_events.numeric_value * core.clinical_events.numeric_value
) ## type:FloatEventSeries
#
# Horizontal aggregations
# The type checker casts eveything to the first series. But the only
# type we can easily get is the first arg. So if the first thing is
# a series then that's fine. Otherwise we ignore
#
max_of_float = maximum_of(
core.clinical_events.numeric_value, 10
) ## type:FloatEventSeries
max_of_int = maximum_of(
core.clinical_events.numeric_value.as_int(), 10
) ## type:IntEventSeries
max_of_date = maximum_of(
core.clinical_events.date, "2024-01-01"
) ## type:DateEventSeries
max_of_float_patient = maximum_of(
core.clinical_events.numeric_value.maximum_for_patient(), 10
) ## type:FloatPatientSeries
max_of_int_patient = maximum_of(
core.clinical_events.numeric_value.maximum_for_patient().as_int(), 10
) ## type:IntPatientSeries
max_of_date_patient = maximum_of(
core.patients.date_of_birth, "2024-01-01"
) ## type:DatePatientSeries
min_of_float = minimum_of(
core.clinical_events.numeric_value, 10
) ## type:FloatEventSeries
min_of_int = minimum_of(
core.clinical_events.numeric_value.as_int(), 10
) ## type:IntEventSeries
min_of_date = minimum_of(
core.clinical_events.date, "2024-01-01"
) ## type:DateEventSeries
min_of_float_patient = minimum_of(
core.clinical_events.numeric_value.minimum_for_patient(), 10
) ## type:FloatPatientSeries
min_of_int_patient = minimum_of(
core.clinical_events.numeric_value.minimum_for_patient().as_int(), 10
) ## type:IntPatientSeries
min_of_date_patient = minimum_of(
core.patients.date_of_birth, "2024-01-01"
) ## type:DatePatientSeries
# properties
core.patients.date_of_birth.day ## type:IntPatientSeries
core.patients.date_of_birth.month ## type:IntPatientSeries
core.patients.date_of_birth.year ## type:IntPatientSeries
core.clinical_events.date.day ## type:IntEventSeries
core.clinical_events.date.month ## type:IntEventSeries
core.clinical_events.date.year ## type:IntEventSeries
# Table methods not yet tested
tpp.patients.is_alive_on(date_str) ## type:BoolPatientSeries
tpp.patients.is_dead_on(date_str) ## type:BoolPatientSeries
tpp.decision_support_values.electronic_frailty_index() ## type:decision_support_values
tpp.practice_registrations.spanning_with_systmone(
date_str, date_str
) ## type:practice_registrations
emis.patients.has_practice_registration_spanning(
date_str, date_str
) ## type:BoolPatientSeries
core.patients.is_alive_on(date_str) ## type:BoolPatientSeries
core.patients.is_dead_on(date_str) ## type:BoolPatientSeries
core.practice_registrations.exists_for_patient_on(date_str) ## type:BoolPatientSeries
core.practice_registrations.spanning(date_str, date_str) ## type:practice_registrations
# query_language series methods not yet tested
core.clinical_events.date.is_after(date_str) ## type: BoolEventSeries
core.clinical_events.date.is_before(date_str) ## type: BoolEventSeries
core.clinical_events.date.is_on_or_after(date_str) ## type: BoolEventSeries
core.clinical_events.date.is_on_or_before(date_str) ## type: BoolEventSeries
core.clinical_events.date.to_first_of_month() ## type: DateEventSeries
core.clinical_events.snomedct_code.is_in([]) ## type: BoolEventSeries
core.clinical_events.snomedct_code.is_null() ## type: BoolEventSeries
core.clinical_events.snomedct_code.is_null().as_int() ## type: IntEventSeries
core.clinical_events.snomedct_code.when_null_then(3) ## type: CodeEventSeries
core.patients.date_of_birth.day.as_float() ## type: FloatPatientSeries
core.patients.date_of_birth.day.as_int() ## type: IntPatientSeries
core.patients.date_of_birth.is_between_but_not_on(
date_str, date_str
) ## type: BoolPatientSeries
core.patients.date_of_birth.is_during((date_str, date_str)) ## type: BoolPatientSeries
core.patients.date_of_birth.is_on_or_between(
date_str, date_str
) ## type: BoolPatientSeries
core.patients.date_of_birth.to_first_of_year() ## type: DatePatientSeries
core.patients.sex.is_not_in(["male"]) ## type: BoolPatientSeries
core.patients.sex.is_not_null() ## type: BoolPatientSeries
core.patients.sex.is_null().as_int() ## type: IntPatientSeries
tpp.apcs.all_diagnoses.is_in([]) ## type:NoReturn
tpp.apcs.all_diagnoses.is_not_in([]) ## type:NoReturn
tpp.addresses.msoa_code.contains([]) ## type: BoolEventSeries
# query_language non-series methods
core.clinical_events.where(
core.clinical_events.snomedct_code.is_in([])
) ## type:clinical_events
core.clinical_events.except_where(
core.clinical_events.snomedct_code.is_in([])
) ## type:clinical_events
# Duration methods
days(100).starting_on("2045-01-01") ## type: list[tuple[date, date]]
days(100).ending_on("2045-01-01") ## type: list[tuple[date, date]]