Documentation
¶
Overview ¶
Package flightrules is the single source of truth for "who/what counts as PIC, Dual, FI, Night, IFR, MP/SP …" across the codebase.
flightcalc/ owns the *write* path (mutates Flight on save). flightrules owns the *read* path (pure helpers consumed by handlers, exporters, PDF renderers, stats and tests). The two share the role + name primitives below.
IMPORTANT: do NOT inline any of these rules in handlers/, repository/ or exporter code. A grep-guard in scripts/run-all-tests.sh enforces this.
Index ¶
- func CombinedRemarks(f *models.Flight, flags ...RemarkFlag) string
- func DisplayPICName(f *models.Flight, userName string) string
- func EffectiveIFRTime(f *models.Flight) int
- func ExaminerNameFromCrew(f *models.Flight, userName string) string
- func FSTDFields(f *models.Flight, dateLayout string, timeFmt func(int) string) (date, kind, dur string)
- func InstructorNameFromCrew(f *models.Flight, userName string) string
- func IsFSTDRow(f *models.Flight) bool
- func IsMultiPilotOperation(flight *models.Flight, role Role) bool
- func IsNightAt(t time.Time, lat, lon float64) bool
- func MatchesUser(candidate, userName string) bool
- func NormalizeLegacyRole(s string) string
- func NormalizeName(s string) string
- func PICNameFromCrew(f *models.Flight, userName string) string
- func ResolvePICNameForSave(f *models.Flight, userName string) *string
- func RowTimes(f *models.Flight, acClass string) (spSE, spME, mp int)
- type LegacyCrewInput
- type LegacyCrewMember
- type PilotingCategory
- type RemarkFlag
- type Role
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CombinedRemarks ¶
func CombinedRemarks(f *models.Flight, flags ...RemarkFlag) string
CombinedRemarks returns the user-facing combined Remarks + Endorsements string, optionally suffixed with FAA-style inline flags ([IPC] / [FR] / [PC]). Empty endorsements/remarks are skipped; the separator between a non-empty remark and a non-empty endorsement is " | ".
Flag suffixes are only added when the corresponding boolean on `flight` is true AND the flag was requested by the caller. Most callers pass all three flags and let the model state decide.
func DisplayPICName ¶
DisplayPICName returns the value to render in the "PIC Name" column of a logbook row, applying the canonical fallback chain:
- flight.PICName, if set (a stale "Self" with a known instructor is treated as unset)
- flight.InstructorName, if set (legacy column)
- first non-self Instructor in flight.CrewMembers
- first non-self Examiner in flight.CrewMembers
- original PICName if present
- "SELF"
userName is the authenticated user's display name; pass "" if unknown. All exporters (CSV, PDF, future formats) MUST use this helper instead of re-implementing the fallback locally.
func EffectiveIFRTime ¶
EffectiveIFRTime returns the IFR/Instrument time minutes that should be reported for a flight. If the user explicitly set IFRTime it is returned as-is. Otherwise the sum of ActualInstrumentTime + SimulatedInstrumentTime is used, capped at TotalTime.
This is the single source of truth for IFR derivation: imports, the auto-calc pipeline and exporters all flow through it.
func ExaminerNameFromCrew ¶ added in v1.3.0
ExaminerNameFromCrew is the Examiner-role counterpart of InstructorNameFromCrew: the first third-party Examiner in the crew, or "".
func FSTDFields ¶
func FSTDFields(f *models.Flight, dateLayout string, timeFmt func(int) string) (date, kind, dur string)
FSTDFields returns the (date, type, time) triple to fill into the FSTD columns. dateLayout is the Go time layout used to format the date (e.g. "02.01.2006" for full EASA CSV, "02.01" for the compact PDF column). timeFmt formats the minutes value the way the caller wants (HH:MM, decimal hours, etc.).
Returns ("", "", "") when IsFSTDRow is false.
func InstructorNameFromCrew ¶
InstructorNameFromCrew returns the name of the first CrewMember with the Instructor role whose name does NOT match userName (i.e. a third-party instructor). Returns "" if no such member exists or if `f` is nil.
userName is the authenticated user's display name and may be empty; in that case ALL Instructor crew members are treated as third parties (matches the conservative behaviour of DetermineRole).
func IsFSTDRow ¶
IsFSTDRow reports whether a flight row should populate the FSTD-block columns (EASA AMC1 FCL.050 Cols 20-22, FAA §61.51(b)(1)(iv)).
Both: a non-empty FSTDType identifier AND non-zero simulated flight time.
func IsMultiPilotOperation ¶ added in v1.3.0
IsMultiPilotOperation reports whether the crew composition indicates an operation flown with a two-pilot crew (multi-crew cooperation): the user is the co-pilot (RoleSIC), or a crew member holds the SIC role (the user is PIC with a co-pilot). Per AMC1 FCL.050 both pilots then log the full flight time in the multi-pilot column.
func IsNightAt ¶
IsNightAt is the single point of truth for "is this instant night?" at a given latitude/longitude; it delegates to pkg/solar and every caller goes through here.
func MatchesUser ¶
MatchesUser reports whether `candidate` refers to the same person as `userName`. Comparison is case- and whitespace-insensitive, and also recognises the "Last, First" contact-picker form against a stored "First Last" profile (and vice versa).
Examples (all match "Amelia Earhart"):
- "Amelia Earhart", "amelia earhart", "AMELIA EARHART"
- " Amelia Earhart ", "Amelia\u00a0Earhart"
- "Earhart, Amelia"
Examples (do NOT match): "Amelia M. Earhart", "A. Earhart".
func NormalizeLegacyRole ¶ added in v1.2.3
NormalizeLegacyRole maps a free-form role string (case-insensitive) to one of our canonical CrewRole values. Unknown / empty strings return "" to signal "fall back to positional inference".
ForeFlight's role vocabulary in the structured Person cell includes PIC, SIC, Student, Instructor, FlightAttendant, Observer, Engineer and Other; the latter four are flattened to Passenger.
func NormalizeName ¶
NormalizeName returns a comparable form of a person name: lower-case, trimmed of leading/trailing whitespace, with all runs of internal whitespace collapsed to a single ASCII space, and Unicode whitespace (e.g. U+00A0 non-breaking space, tabs, newlines) folded to ASCII space.
This is the only place name normalization should happen. Empty input yields empty output.
func PICNameFromCrew ¶ added in v1.3.0
PICNameFromCrew is the PIC-role counterpart of InstructorNameFromCrew: the first third-party PIC in the crew, or "". Used when the user flies as co-pilot (SIC) — the other pilot is PIC of record.
func ResolvePICNameForSave ¶
ResolvePICNameForSave returns the value that should be persisted into `flight.PICName` when a flight is saved. The write-path equivalent of DisplayPICName:
- existing PICName wins (user explicitly set it),
- if the user is PIC and no instructor is involved → "Self",
- if the user is Dual and an InstructorName is set → that instructor,
- if the user is Dual and a non-self Instructor exists in CrewMembers → that instructor's name,
- if the user is Dual and a non-self Examiner exists in CrewMembers → that examiner's name,
- otherwise nil (column stays empty; DisplayPICName renders "SELF").
The CRUD handler must call this once at save time.
Types ¶
type LegacyCrewInput ¶
type LegacyCrewInput struct {
Person1, Person2, Person3, Person4, Person5, Person6 string
Person1Role, Person2Role, Person3Role, Person4Role, Person5Role, Person6Role string
InstructorName string
HasDualReceived bool // any positive DualReceived value
HasDualGiven bool // any positive DualGiven value
}
LegacyCrewInput describes the flat set of legacy/spreadsheet crew columns: up to six "personN" names, an InstructorName, and the user-declared DualReceived / DualGiven totals.
ForeFlight optionally encodes a role tag inside each Person cell ("Name;Role;email"). When that explicit role is available the caller SHOULD pass it via the matching PersonNRole field; it wins over positional inference.
type LegacyCrewMember ¶
LegacyCrewMember is a name+role pair returned by InferLegacyCrew. Callers map it to their own crew type (generated.FlightCrewMemberInput, models.FlightCrewMember, …). Role values match the strings in models.CrewRole ("PIC", "SIC", "Instructor", "Student", "Passenger").
func InferLegacyCrew ¶
func InferLegacyCrew(in LegacyCrewInput) []LegacyCrewMember
InferLegacyCrew is the single source of truth that converts the legacy flat crew columns (used by ForeFlight / Garmin / older spreadsheet CSVs) into a CrewMembers array. Both the CSV importer and any future legacy re-importer MUST use this — do not re-derive crew roles by hand.
Rules:
- Training flight ≡ InstructorName set OR HasDualReceived.
- Instructor-giving ≡ HasDualGiven (and not a training flight).
- Person1 is treated as Instructor on a training flight, Student when the user is giving instruction, else PIC. If InstructorName is set and differs from Person1, both are emitted (Person1 keeps a PIC role, InstructorName is added as Instructor).
- Person2 is Student on a training flight, Passenger otherwise.
- Persons 3-6 are always Passenger.
- InstructorName with no Person1 is emitted as Instructor on its own.
When the caller supplies an explicit role for a person via the matching PersonNRole field (see NormalizeLegacyRole for the accepted vocabulary), that role wins over the positional rules for that person; other persons still follow the positional rules.
type PilotingCategory ¶
type PilotingCategory int
PilotingCategory is the per-row bucket used for the EASA AMC1 FCL.050 columns "SP-SE" / "SP-ME" / "Multi-Pilot".
const ( // CategorySPSE: single-pilot single-engine. CategorySPSE PilotingCategory = iota // CategorySPME: single-pilot multi-engine. CategorySPME // CategoryMP: multi-pilot. CategoryMP )
func PilotingCategoryFor ¶
func PilotingCategoryFor(f *models.Flight, acClass string) PilotingCategory
PilotingCategoryFor returns the bucket for `flight`. acClass is the user's stored AircraftClass for this registration (e.g. "SEP", "MEP", "SET", "MET", "SES"), passed in by callers that have aircraft data. When acClass is empty (no fleet entry available) the rule defaults to SP-SE.
Rule:
- MultiPilotTime > 0 ⇒ MP (user-declared)
- acClass starts with "ME" or "MET" or "SET" ⇒ SP-ME
- otherwise ⇒ SP-SE
type RemarkFlag ¶
type RemarkFlag int
RemarkFlag is an optional inline marker appended to a flight's remarks when present (FAA logbook convention). Order matches FAA convention: [IPC], then [FR] (Flight Review), then [PC] (Proficiency Check).
const ( FlagIPC RemarkFlag = iota FlagFlightReview FlagProficiencyCheck )
type Role ¶
type Role int
Role classifies the user's pilot role on a flight, derived from the crew composition relative to the authenticated user's display name.
const ( // RolePIC: user is sole/lead pilot, no instruction context. RolePIC Role = iota // RoleDualReceiving: a third-party Instructor on board is giving the // user instruction, or a third-party Examiner is conducting a check // ride (Dual received). RoleDualReceiving // RoleDualGiving: the user is acting as instructor — either a Student // is on board, or the user themselves is listed with the Instructor // role (Dual given / FI). RoleDualGiving // RoleSIC: the user is co-pilot on a multi-pilot operation — another // person is listed with the PIC role, or the user themselves is listed // with the SIC role. Per AMC1 FCL.050 only the designated PIC logs PIC // time; the co-pilot logs co-pilot (SIC) time, even if both pilots are // qualified PICs (FOCA GM/INFO "Logging of Flight Time" §2.3.3). RoleSIC )
func DetermineRole ¶
DetermineRole inspects the crew list to classify the user's pilot role.
Precedence: a third-party Instructor or Examiner (name ≠ user) makes the user a Dual receiver, regardless of any Student also being present (NfL 2021-2-602 §4.2.2 no. 4; EASA AMC1 FCL.050). A Student or self-listed Instructor makes the user a Dual giver. A third-party PIC or a self-listed SIC makes the user the co-pilot (SIC) of a multi-pilot operation (AMC1 FCL.050; FOCA GM/INFO §2.3.3). A self-listed Examiner leaves the user as PIC; a self-listed PIC crew entry keeps the user as PIC and wins over a simultaneous third-party PIC entry. Otherwise the user is PIC.
When userName is empty, any Instructor, Examiner or PIC crew member is treated as a third party.