mirror of
https://github.com/PlaneQuery/OpenAirframes.git
synced 2026-09-18 11:52:15 +02:00
feat: redistribute Canadian owner addresses to match the FAA asset
- publish street, city, postal code and care-of, which the FAA asset already carries as registrant_* for 99.7% of US registrants; dropping them here left one repository with two different postures on the same class of data - take the address from the single MAIL_RECIPIENT row rather than merging across parties, since a co-owned mark lists several people in different cities Generated-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -66,8 +66,10 @@ CCARCS `ACTIVE_FLAG` does **not** mean "current owner": 1,932 currently-Register
|
|||||||
`I` parties, and those rows are the `MAIL_RECIPIENT`. Prefer `A` parties where a mark has any, fall
|
`I` parties, and those rows are the `MAIL_RECIPIENT`. Prefer `A` parties where a mark has any, fall
|
||||||
back to all of them otherwise. Filtering on `A` alone publishes registered aircraft with no owner.
|
back to all of them otherwise. Filtering on `A` alone publishes registered aircraft with no owner.
|
||||||
|
|
||||||
Owner mailing addresses (street, city, postal code, care-of) are dropped; name, type, province and
|
Owner mailing addresses **are** published, matching the `registrant_*` address the FAA asset already
|
||||||
country are published.
|
carries — the two sources must not diverge on this. Addresses are per-party, so they come from the
|
||||||
|
single `MAIL_RECIPIENT == "Y"` row (exactly one per mark) and are never merged across co-owners; only
|
||||||
|
name, type, province and country are merged lists.
|
||||||
|
|
||||||
## Fork and upstream
|
## Fork and upstream
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ CCARCS download page, so no licence name or URL is asserted here.
|
|||||||
construed as constituting an endorsement by the Government of Canada of our
|
construed as constituting an endorsement by the Government of Canada of our
|
||||||
product.
|
product.
|
||||||
|
|
||||||
Owner mailing addresses published in the CCARCS export (street, city, postal
|
Registered-owner mailing addresses are redistributed, matching the registrant
|
||||||
code, care-of) are dropped during ingestion and are not redistributed.
|
addresses the FAA asset already carries. Because CCARCS lists one row per party,
|
||||||
|
the published address is that of the single designated mail recipient rather than
|
||||||
|
a merge across co-owners.
|
||||||
|
|
||||||
|
|
||||||
FAA — Releasable Aircraft Database
|
FAA — Releasable Aircraft Database
|
||||||
|
|||||||
@@ -34,8 +34,16 @@ CARSOWNR_COLUMNS = [
|
|||||||
"OWNER_NAME_OLD_FORMAT", "MAIL_RECIPIENT", "TRIMMED_MARK",
|
"OWNER_NAME_OLD_FORMAT", "MAIL_RECIPIENT", "TRIMMED_MARK",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Owner mailing addresses are dropped rather than republished; see NOTICE.
|
# Mailing address of the single designated recipient, matching the registrant_* address
|
||||||
OWNER_PII_COLUMNS = ["STREET_NAME", "STREET_NAME2", "CITY", "POSTAL_CODE", "CARE_OF"]
|
# the FAA build already publishes. Addresses are per-party, so they are taken from the one
|
||||||
|
# MAIL_RECIPIENT row rather than merged across co-owners.
|
||||||
|
OWNER_ADDRESS_COLUMNS = {
|
||||||
|
"STREET_NAME": "owner_street_1",
|
||||||
|
"STREET_NAME2": "owner_street_2",
|
||||||
|
"CITY": "owner_city",
|
||||||
|
"POSTAL_CODE": "owner_postal_code",
|
||||||
|
"CARE_OF": "owner_care_of",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
FOOTER_RE = re.compile(r"\s*(\d+) rows selected\.\s*")
|
FOOTER_RE = re.compile(r"\s*(\d+) rows selected\.\s*")
|
||||||
@@ -125,6 +133,7 @@ def _merge_owners(df_ownr: pd.DataFrame) -> pd.DataFrame:
|
|||||||
# Registered marks carry only "I" parties, and those rows are the MAIL_RECIPIENT.
|
# Registered marks carry only "I" parties, and those rows are the MAIL_RECIPIENT.
|
||||||
# So prefer active parties where a mark has any, and fall back to all of them
|
# So prefer active parties where a mark has any, and fall back to all of them
|
||||||
# rather than publishing a registered aircraft with no owner at all.
|
# rather than publishing a registered aircraft with no owner at all.
|
||||||
|
all_parties = df_ownr
|
||||||
active = df_ownr[df_ownr["ACTIVE_FLAG"].str.upper() == "A"]
|
active = df_ownr[df_ownr["ACTIVE_FLAG"].str.upper() == "A"]
|
||||||
marks_with_active = set(active["TRIMMED_MARK"])
|
marks_with_active = set(active["TRIMMED_MARK"])
|
||||||
df_ownr = pd.concat([
|
df_ownr = pd.concat([
|
||||||
@@ -154,14 +163,25 @@ def _merge_owners(df_ownr: pd.DataFrame) -> pd.DataFrame:
|
|||||||
# once several parties share it. Counting distinct names rather than rows keeps this
|
# once several parties share it. Counting distinct names rather than rows keeps this
|
||||||
# consistent with owner_name, which is also deduplicated.
|
# consistent with owner_name, which is also deduplicated.
|
||||||
grouped.loc[grouped["owner_party_count"] > 1, "owner_type"] = "Co-owner"
|
grouped.loc[grouped["owner_party_count"] > 1, "owner_type"] = "Co-owner"
|
||||||
return grouped
|
|
||||||
|
# Taken from the unfiltered frame: the designated recipient is the designated
|
||||||
|
# recipient even when its own party row is flagged inactive.
|
||||||
|
recipient = (
|
||||||
|
all_parties[all_parties["MAIL_RECIPIENT"].str.upper() == "Y"]
|
||||||
|
.drop_duplicates(subset="TRIMMED_MARK", keep="first")
|
||||||
|
.rename(columns=OWNER_ADDRESS_COLUMNS)
|
||||||
|
)
|
||||||
|
return grouped.merge(
|
||||||
|
recipient[["TRIMMED_MARK", *OWNER_ADDRESS_COLUMNS.values()]],
|
||||||
|
on="TRIMMED_MARK",
|
||||||
|
how="left",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def convert_tc_ccarcs_to_df(zip_path: Path, date: str) -> pd.DataFrame:
|
def convert_tc_ccarcs_to_df(zip_path: Path, date: str) -> pd.DataFrame:
|
||||||
"""Build the OpenAirframes Transport Canada frame from a CCARCS zip."""
|
"""Build the OpenAirframes Transport Canada frame from a CCARCS zip."""
|
||||||
df = _read_ccarcs_entry(zip_path, "carscurr.txt", CARSCURR_COLUMNS)
|
df = _read_ccarcs_entry(zip_path, "carscurr.txt", CARSCURR_COLUMNS)
|
||||||
df_ownr = _read_ccarcs_entry(zip_path, "carsownr.txt", CARSOWNR_COLUMNS)
|
df_ownr = _read_ccarcs_entry(zip_path, "carsownr.txt", CARSOWNR_COLUMNS)
|
||||||
df_ownr = df_ownr.drop(columns=OWNER_PII_COLUMNS)
|
|
||||||
|
|
||||||
df = df.merge(_merge_owners(df_ownr), on="TRIMMED_MARK", how="left")
|
df = df.merge(_merge_owners(df_ownr), on="TRIMMED_MARK", how="left")
|
||||||
|
|
||||||
@@ -196,6 +216,11 @@ def convert_tc_ccarcs_to_df(zip_path: Path, date: str) -> pd.DataFrame:
|
|||||||
"owner_type": df["owner_type"],
|
"owner_type": df["owner_type"],
|
||||||
"owner_province_or_state": df["owner_province_or_state"],
|
"owner_province_or_state": df["owner_province_or_state"],
|
||||||
"owner_country": df["owner_country"],
|
"owner_country": df["owner_country"],
|
||||||
|
"owner_care_of": df["owner_care_of"],
|
||||||
|
"owner_street_1": df["owner_street_1"],
|
||||||
|
"owner_street_2": df["owner_street_2"],
|
||||||
|
"owner_city": df["owner_city"],
|
||||||
|
"owner_postal_code": df["owner_postal_code"],
|
||||||
"issue_date": df["ISSUE_DATE"],
|
"issue_date": df["ISSUE_DATE"],
|
||||||
"effective_date": df["EFFECTIVE_DATE"],
|
"effective_date": df["EFFECTIVE_DATE"],
|
||||||
"ineffective_date": df["INEFFECTIVE_DATE"],
|
"ineffective_date": df["INEFFECTIVE_DATE"],
|
||||||
|
|||||||
Reference in New Issue
Block a user