"""Personal Dimension Benchmark: the subject, the facts, and the ground-truth engine.

One synthetic person. Every fact is authored once, here, with a validity interval, a
source and a natural-language note. Every representation used in the study is RENDERED
from this single table, so no representation can be missing a fact that another one has.
That is the design correction the review of the previous study forced on us: completeness
is guaranteed by construction and is verified in code, not by inspection.

TODAY is fixed so the study is reproducible.
"""
from datetime import date

TODAY = date(2026, 9, 6)

SUBJECT = "Mira Vantaa"

# ------------------------------------------------------------------ sources
# tier 1 is the most trusted. The conflict policy reads these tiers.
SOURCES = {
    "S-DOC-01": ("document_scan", 1, date(2019, 2, 24)),
    "S-DOC-02": ("document_scan", 1, date(2022, 7, 20)),
    "S-DOC-03": ("document_scan", 1, date(2025, 5, 12)),
    "S-DOC-04": ("document_scan", 1, date(2021, 11, 18)),
    "S-DOC-05": ("document_scan", 1, date(2025, 2, 21)),
    "S-DOC-06": ("document_scan", 1, date(2016, 4, 12)),
    "S-DOC-07": ("document_scan", 1, date(2017, 2, 3)),
    "S-DOC-08": ("document_scan", 1, date(2025, 9, 2)),
    "S-DOC-09": ("document_scan", 1, date(2026, 1, 31)),
    "S-DOC-10": ("document_scan", 1, date(2024, 11, 9)),
    "S-DEV-01": ("device_sync", 2, date(2024, 7, 1)),
    "S-DEV-02": ("device_sync", 2, date(2026, 8, 30)),
    "S-EML-01": ("email", 3, date(2018, 8, 20)),
    "S-EML-02": ("email", 3, date(2024, 2, 20)),
    "S-EML-03": ("email", 3, date(2024, 7, 1)),
    "S-EML-04": ("email", 3, date(2021, 4, 28)),
    "S-EML-05": ("email", 3, date(2022, 12, 27)),
    "S-EML-06": ("email", 3, date(2022, 8, 30)),
    "S-EML-07": ("email", 3, date(2024, 3, 15)),
    "S-EML-08": ("email", 3, date(2025, 2, 26)),
    "S-SLF-01": ("self_reported", 4, date(2019, 1, 10)),
    "S-SLF-02": ("self_reported", 4, date(2020, 1, 4)),
    "S-SLF-03": ("self_reported", 4, date(2023, 6, 2)),
    "S-SLF-04": ("self_reported", 4, date(2025, 6, 20)),
    "S-SLF-05": ("self_reported", 4, date(2022, 9, 5)),
    "S-SLF-06": ("self_reported", 4, date(2024, 5, 19)),
    "S-SLF-08": ("self_reported", 4, date(2025, 3, 30)),
    "S-SLF-07": ("self_reported", 4, date(2026, 2, 10)),
}

TIER_NAMES = {1: "document_scan", 2: "device_sync", 3: "email", 4: "self_reported"}

POLICY_ID = "CP-01"
POLICY_TEXT = (
    "CP-01: when two facts about the same attribute cover the same date and disagree, "
    "the fact from the lower source tier wins (document_scan 1, device_sync 2, email 3, "
    "self_reported 4). Within one tier, the fact captured later wins."
)

# ------------------------------------------------------------------ facts
# (fid, attr, value, valid_from, valid_to, source, note)
# valid_to None means open-ended.
F = []


def fact(fid, attr, value, vfrom, vto, source, note):
    F.append({"fid": fid, "attr": attr, "value": value,
              "valid_from": vfrom, "valid_to": vto, "source": source, "note": note})


fact("F-001", "home_address", "12 Harbour Lane, Limassol", date(2019, 3, 1),
     date(2022, 7, 31), "S-DOC-01",
     "Signed the lease for 12 Harbour Lane, Limassol and moved in on 2019-03-01; "
     "stayed until 2022-07-31. The lease scan is filed as S-DOC-01.")
fact("F-002", "home_address", "48 Aristotelous, Nicosia", date(2022, 8, 1),
     date(2025, 5, 14), "S-DOC-02",
     "Moved to 48 Aristotelous, Nicosia on 2022-08-01 and lived there until "
     "2025-05-14. Lease scan filed as S-DOC-02.")
fact("F-003", "home_address", "7 Kalymnou, Larnaca", date(2025, 5, 15), None, "S-DOC-03",
     "Moved to 7 Kalymnou, Larnaca on 2025-05-15 and still live there. Lease scan "
     "filed as S-DOC-03.")
fact("F-004", "home_address", "48 Aristotelous, Nicosia", date(2025, 5, 15), None,
     "S-SLF-04",
     "Wrote down on 2025-06-20 that my address is 48 Aristotelous, Nicosia, and that "
     "this holds from 2025-05-15 onwards. This is just my own note, S-SLF-04.")

fact("F-010", "employer", "Halvard Systems", date(2018, 9, 1), date(2021, 11, 30),
     "S-EML-01",
     "Started at Halvard Systems on 2018-09-01 and left on 2021-11-30. The offer email "
     "is S-EML-01.")
fact("F-011", "employer", "Tessera Analytics", date(2021, 12, 1), date(2025, 2, 28),
     "S-DOC-04",
     "Worked at Tessera Analytics from 2021-12-01 to 2025-02-28. Signed contract scan "
     "S-DOC-04.")
fact("F-012", "employer", "Kestrel Data Works", date(2025, 3, 1), None, "S-DOC-05",
     "Joined Kestrel Data Works on 2025-03-01 and still work there. Contract scan "
     "S-DOC-05.")

fact("F-020", "mobile_phone", "+357 99 100 224", date(2019, 1, 10), date(2024, 6, 30),
     "S-SLF-01",
     "My mobile number was +357 99 100 224 from 2019-01-10 until 2024-06-30. I noted "
     "it myself, S-SLF-01.")
fact("F-021", "mobile_phone", "+357 96 442 018", date(2024, 7, 1), None, "S-DEV-01",
     "New mobile number +357 96 442 018 from 2024-07-01, synced from the handset, "
     "S-DEV-01.")
fact("F-022", "mobile_phone", "+357 99 100 224", date(2024, 7, 1), None, "S-SLF-06",
     "Wrote on 2024-05-19 that my number stays +357 99 100 224 from 2024-07-01. Own "
     "note, S-SLF-06.")

fact("F-030", "monthly_salary_eur", "4200", date(2025, 3, 1), date(2025, 12, 31),
     "S-EML-08",
     "The offer email of 2025-02-26 put my monthly salary at EUR 4200 from 2025-03-01 "
     "until 2025-12-31. Email S-EML-08.")
fact("F-031", "monthly_salary_eur", "4410", date(2026, 1, 1), None, "S-DOC-09",
     "The January 2026 payslip scan shows a monthly salary of EUR 4410 from "
     "2026-01-01. Scan S-DOC-09.")
fact("F-032", "monthly_salary_eur", "4200", date(2026, 1, 1), None, "S-SLF-07",
     "On 2026-02-10 I wrote down that my salary is EUR 4200 from 2026-01-01. Own note "
     "S-SLF-07.")

fact("F-040", "blood_type", "A+", date(2015, 1, 1), None, "S-SLF-03",
     "I have always said my blood type is A+, and that it has held since 2015-01-01. "
     "Own note from 2023-06-02, S-SLF-03.")
fact("F-041", "blood_type", "0+", date(2015, 1, 1), None, "S-DOC-10",
     "The laboratory report of 2024-11-09 gives my blood type as 0+, holding since "
     "2015-01-01. Scan S-DOC-10.")

fact("F-050", "diet", "vegetarian", date(2023, 6, 2), date(2025, 3, 29), "S-SLF-03",
     "From 2023-06-02 until 2025-03-29 I ate vegetarian. Own note S-SLF-03.")
fact("F-051", "diet", "pescatarian", date(2025, 3, 30), None, "S-SLF-08",
     "From 2025-03-30 I eat pescatarian. Own note S-SLF-08.")

fact("F-060", "passport_number", "P4417832", date(2016, 4, 12), date(2026, 4, 11),
     "S-DOC-06",
     "Passport P4417832 was issued on 2016-04-12 and expired on 2026-04-11. Scan "
     "S-DOC-06.")
fact("F-061", "driving_licence", "DL-772104", date(2017, 2, 3), date(2027, 1, 31),
     "S-DOC-07",
     "Driving licence DL-772104 runs from 2017-02-03 to 2027-01-31. Scan S-DOC-07.")
fact("F-062", "health_insurance_policy", "HI-88231", date(2025, 9, 1),
     date(2026, 8, 31), "S-DOC-08",
     "Health insurance policy HI-88231 covered 2025-09-01 to 2026-08-31. Scan "
     "S-DOC-08.")
fact("F-063", "bank_iban", "CY17 0020 0128 0000 0012 0052 7600", date(2021, 12, 1),
     None, "S-DOC-04",
     "Salary goes to IBAN CY17 0020 0128 0000 0012 0052 7600, on file since "
     "2021-12-01 with the contract scan S-DOC-04.")
fact("F-064", "drug_allergy", "penicillin", date(2015, 1, 1), None, "S-DOC-10",
     "The laboratory file of 2024-11-09 records a penicillin allergy, known since "
     "2015-01-01. Scan S-DOC-10.")

fact("F-070", "device_laptop", "Aurora 14", date(2024, 2, 20), None, "S-EML-02",
     "Bought an Aurora 14 laptop on 2024-02-20; the receipt email is S-EML-02.")
fact("F-071", "laptop_warranty_until", "2027-02-20", date(2024, 2, 20),
     date(2027, 2, 20), "S-EML-02",
     "The Aurora 14 laptop warranty runs to 2027-02-20 according to the receipt "
     "S-EML-02.")
fact("F-072", "device_phone", "Corvus 8", date(2024, 7, 1), None, "S-EML-03",
     "Bought a Corvus 8 handset on 2024-07-01; receipt email S-EML-03.")
fact("F-073", "phone_warranty_until", "2026-07-01", date(2024, 7, 1), date(2026, 7, 1),
     "S-EML-03",
     "The Corvus 8 handset warranty ran to 2026-07-01, per receipt S-EML-03.")

fact("F-080", "subscription_cloudline", "9", date(2021, 5, 1), None, "S-EML-04",
     "Cloudline storage costs EUR 9 a month, running since 2021-05-01. Billing email "
     "S-EML-04.")
fact("F-081", "subscription_cloudline", "12", date(2021, 5, 1), None, "S-SLF-05",
     "I noted on 2022-09-05 that Cloudline storage costs EUR 12 a month from "
     "2021-05-01. Own note S-SLF-05.")
fact("F-082", "subscription_trainline", "19", date(2023, 1, 1), date(2024, 12, 31),
     "S-EML-05",
     "The Trainline pass cost EUR 19 a month from 2023-01-01 and was cancelled on "
     "2024-12-31. Billing email S-EML-05.")
fact("F-083", "subscription_gym", "35", date(2022, 9, 1), date(2025, 5, 31),
     "S-EML-06",
     "The Nicosia gym cost EUR 35 a month from 2022-09-01 until I cancelled it on "
     "2025-05-31 after the move. Billing email S-EML-06.")
fact("F-084", "subscription_journal", "22", date(2024, 3, 15), None, "S-EML-07",
     "Journal access costs EUR 22 a month since 2024-03-15. Billing email S-EML-07.")
fact("F-085", "subscription_music", "11", date(2020, 1, 1), None, "S-SLF-02",
     "The music service costs EUR 11 a month since 2020-01-01. Own note S-SLF-02.")

fact("F-090", "review_due_home_address", "2027-05-15", date(2025, 5, 15), None,
     "S-DOC-03",
     "The lease at 7 Kalymnou is up for review by 2027-05-15, per the scan S-DOC-03.")
fact("F-091", "review_due_health_insurance", "2026-08-31", date(2025, 9, 1), None,
     "S-DOC-08",
     "The health insurance needs renewing by 2026-08-31, per the scan S-DOC-08.")

# ------------------------------------------------------------------ people
# (name, role, city, phone, note)
PEOPLE = [
    ("Ilona Vantaa", "sister", "Nicosia", "+357 99 774 310",
     "My sister Ilona Vantaa lives in Nicosia; her number is +357 99 774 310."),
    ("Alex Reiner", "colleague at Kestrel Data Works", "Larnaca", "+357 96 220 145",
     "Alex Reiner is a colleague at Kestrel Data Works, based in Larnaca, "
     "+357 96 220 145."),
    ("Alex Doran", "general practitioner", "Larnaca", "+357 24 812 660",
     "Alex Doran is my GP in Larnaca, +357 24 812 660."),
    ("Alex Kovacs", "landlord of 7 Kalymnou", "Larnaca", "+357 99 018 442",
     "Alex Kovacs is the landlord of 7 Kalymnou in Larnaca, +357 99 018 442."),
    ("Nadja Lind", "former colleague at Tessera Analytics", "Nicosia",
     "+357 97 553 208",
     "Nadja Lind is a former colleague from Tessera Analytics, now in Nicosia, "
     "+357 97 553 208."),
    ("Petros Sarris", "dentist", "Larnaca", "+357 24 330 917",
     "Petros Sarris is my dentist in Larnaca, +357 24 330 917."),
    ("Georgia Mensah", "accountant", "Limassol", "+357 25 447 002",
     "Georgia Mensah is my accountant in Limassol, +357 25 447 002."),
]

# (date, destination, companion or None, note)
TRIPS = [
    (date(2023, 4, 10), "Athens", "Ilona Vantaa",
     "Travelled to Athens on 2023-04-10 with Ilona Vantaa."),
    (date(2024, 2, 3), "Nicosia", "Nadja Lind",
     "Went to Nicosia on 2024-02-03 with Nadja Lind."),
    (date(2024, 9, 17), "Vienna", None, "Flew to Vienna alone on 2024-09-17."),
    (date(2025, 6, 21), "Athens", "Alex Reiner",
     "Travelled to Athens on 2025-06-21 with Alex Reiner."),
    (date(2025, 11, 8), "Nicosia", "Ilona Vantaa",
     "Went to Nicosia on 2025-11-08 with Ilona Vantaa."),
    (date(2026, 1, 22), "Nicosia", "Nadja Lind",
     "Went to Nicosia on 2026-01-22 with Nadja Lind."),
    (date(2026, 3, 14), "Berlin", "Alex Reiner",
     "Travelled to Berlin on 2026-03-14 with Alex Reiner."),
    (date(2026, 7, 2), "Athens", None, "Flew to Athens alone on 2026-07-02."),
]


# ------------------------------------------------------------------ engine

def tier(fid_or_source):
    src = fid_or_source if fid_or_source in SOURCES else None
    return SOURCES[src][1] if src else 99


def captured(source):
    return SOURCES[source][2]


def covers(f, when):
    if f["valid_from"] > when:
        return False
    return f["valid_to"] is None or f["valid_to"] >= when


def candidates(attr, when):
    return [f for f in F if f["attr"] == attr and covers(f, when)]


def resolve(attr, when=TODAY):
    """Apply CP-01: lowest tier wins, then latest captured. Returns the winning fact."""
    cand = candidates(attr, when)
    if not cand:
        return None
    cand.sort(key=lambda f: (tier(f["source"]), -captured(f["source"]).toordinal()))
    return cand[0]


def value_at(attr, when=TODAY):
    f = resolve(attr, when)
    return f["value"] if f else None


def conflicted(attr, when=TODAY):
    """True when more than one distinct value covers `when`."""
    return len({f["value"] for f in candidates(attr, when)}) > 1


def expired(attr, when=TODAY):
    """The attribute has a fact whose validity ended before `when` and no later one."""
    same = [f for f in F if f["attr"] == attr]
    if not same:
        return False
    if candidates(attr, when):
        return False
    return any(f["valid_to"] is not None and f["valid_to"] < when for f in same)


CONFLICT_ATTRS = ["home_address", "mobile_phone", "monthly_salary_eur", "blood_type",
                  "subscription_cloudline"]


def naive_value(attr, when=TODAY):
    """What a summary without provenance produces: the most recently captured fact
    that covers the date. This is the rule a plain assistant memory actually uses."""
    cand = candidates(attr, when)
    if not cand:
        return None
    cand.sort(key=lambda f: -captured(f["source"]).toordinal())
    return cand[0]["value"]


def person(name):
    for p in PEOPLE:
        if p[0] == name:
            return p
    return None


def people_named(first):
    return [p for p in PEOPLE if p[0].split()[0] == first]


def trips_to(city):
    return sorted([t for t in TRIPS if t[1] == city], key=lambda t: t[0])


def all_values():
    """Every value that appears anywhere, for the fabrication check."""
    out = {str(f["value"]) for f in F}
    for p in PEOPLE:
        out.update({p[0], p[2], p[3]})
    for t in TRIPS:
        out.add(t[1])
        if t[2]:
            out.add(t[2])
    return out


def self_check():
    """Fail loudly rather than publish a study built on an inconsistent world."""
    problems = []
    for f in F:
        if f["source"] not in SOURCES:
            problems.append(f"{f['fid']}: unknown source {f['source']}")
        if f["valid_to"] is not None and f["valid_to"] < f["valid_from"]:
            problems.append(f"{f['fid']}: valid_to before valid_from")
    # the six planted conflicts must actually be conflicts today
    for attr in ("home_address", "mobile_phone", "monthly_salary_eur", "blood_type",
                 "subscription_cloudline"):
        if not conflicted(attr):
            problems.append(f"{attr}: expected a live conflict, found none")
    if conflicted("diet"):
        problems.append("diet: expected the later self-report to stand alone")
    # expiries the study depends on
    for attr in ("passport_number", "health_insurance_policy", "phone_warranty_until"):
        if not expired(attr):
            problems.append(f"{attr}: expected to be expired as of TODAY")
    if expired("driving_licence"):
        problems.append("driving_licence: expected to still be valid")
    if len(people_named("Alex")) != 3:
        problems.append("expected exactly three people called Alex")
    disagree = {a for a in CONFLICT_ATTRS if naive_value(a) != value_at(a)}
    if disagree != {"home_address", "monthly_salary_eur", "subscription_cloudline"}:
        problems.append(f"conflict balance drifted: naive differs on {sorted(disagree)}")
    return problems


if __name__ == "__main__":
    bad = self_check()
    print("facts:", len(F), "people:", len(PEOPLE), "trips:", len(TRIPS),
          "sources:", len(SOURCES))
    print("current address:", value_at("home_address"))
    print("current salary:", value_at("monthly_salary_eur"))
    print("blood type:", value_at("blood_type"))
    print("address on 2023-01-01:", value_at("home_address", date(2023, 1, 1)))
    print("passport expired:", expired("passport_number"))
    print("self check:", "OK" if not bad else bad)
