From f8d432ba0f8932bbddc0bc73e9529e898617cb63 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 04:23:08 +0900 Subject: [PATCH 1/2] feat(event): score TDT mention links with precision and recall Keep detected same-event pairs distinct from promoted instances and state transitions, and require computed precision, recall, and RMSE against known-truth pair sets. --- CHANGELOG.md | 1 + DOCUMENTATION.md | 1 + crates/event_core/src/error.rs | 21 ++ crates/event_core/src/lib.rs | 15 ++ crates/event_core/src/link.rs | 223 ++++++++++++++++++ .../tests/link_detection_contract.rs | 132 +++++++++++ docs/TRACEABILITY.md | 2 +- ...tdt-chronos-event-intelligence-boundary.md | 2 +- docs/adr/README.md | 2 +- .../event-link-detection-calibration.md | 31 +++ docs/research/standards-and-literature.md | 4 + docs/validation/temporal-event-foundation.md | 1 + 12 files changed, 432 insertions(+), 3 deletions(-) create mode 100644 crates/event_core/src/link.rs create mode 100644 crates/event_core/tests/link_detection_contract.rs create mode 100644 docs/research/event-link-detection-calibration.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cea0ee2..ca7e1046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ### Added +- `event_core` TDT link-detection contracts: undirected mention-pair hypotheses, fail-closed self-links, refusal to treat a detected link as an instance or state transition, and computed precision/recall plus RMSE against known-truth pairs. - `persistence_postgres` event-relation SQL contracts: closed ERD transition/provenance vocabulary bound to `transition_edge`, fail-closed unknown types and transition self-loops, live insert of `causes`/`references`. - `persistence_postgres` typed membership assignment (migration `0006`): `entity_record`, `project_record`, and `text_segment` plus exactly-one observed-unit and target constraints that replace the polymorphic `membership_target_id` stub, with SQL insert/lookup, fail-closed inverted-window and backslash-label refusal, and live proof that one document persists two entity memberships and one project membership. - Actions workflow fleet auditor (`scripts/actions_workflow_fleet.py`): paginated registry inventory bound to the exact default-branch SHA/tree, classification of present/orphan/disabled/GitHub-dynamic identities, and fail-closed orphan disable that confirms GitHub's official `disabled_manually` state. diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 230c5abe..09ac9fb6 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -33,6 +33,7 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin | Hourly NIM product-development operations | [`docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md`](docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md) | | Actions workflow fleet audit | [`docs/operations/ACTIONS_WORKFLOW_FLEET.md`](docs/operations/ACTIONS_WORKFLOW_FLEET.md) | | Actions fleet research doctoring | [`docs/research/actions-workflow-fleet.md`](docs/research/actions-workflow-fleet.md) | +| TDT link-detection precision/recall doctoring | [`docs/research/event-link-detection-calibration.md`](docs/research/event-link-detection-calibration.md) | | Hourly NIM OpenCode doctoring | [`docs/doctoring/hourly-nim-opencode-development.md`](docs/doctoring/hourly-nim-opencode-development.md) | | Change history | [`CHANGELOG.md`](CHANGELOG.md) | diff --git a/crates/event_core/src/error.rs b/crates/event_core/src/error.rs index 6c795fef..edbdbd68 100644 --- a/crates/event_core/src/error.rs +++ b/crates/event_core/src/error.rs @@ -20,6 +20,12 @@ pub enum EventError { UnsupportedWireVersion, /// An unknown event-role name was supplied. UnknownEventRole, + /// A TDT link detection was treated as an event instance. + EventLinkIsNotEventInstance, + /// A TDT link detection was treated as a state transition. + EventLinkIsNotStateTransition, + /// An unknown event-link label was supplied. + UnknownEventLinkLabel, } impl fmt::Display for EventError { @@ -32,6 +38,9 @@ impl fmt::Display for EventError { Self::InvalidWirePayload => "invalid event wire payload", Self::UnsupportedWireVersion => "unsupported event wire version", Self::UnknownEventRole => "unknown event role", + Self::EventLinkIsNotEventInstance => "event link is not an event instance", + Self::EventLinkIsNotStateTransition => "event link is not a state transition", + Self::UnknownEventLinkLabel => "unknown event link label", }; formatter.write_str(message) } @@ -65,6 +74,18 @@ mod tests { "unsupported event wire version", ), (EventError::UnknownEventRole, "unknown event role"), + ( + EventError::EventLinkIsNotEventInstance, + "event link is not an event instance", + ), + ( + EventError::EventLinkIsNotStateTransition, + "event link is not a state transition", + ), + ( + EventError::UnknownEventLinkLabel, + "unknown event link label", + ), ] { assert_eq!(error.to_string(), message); } diff --git a/crates/event_core/src/lib.rs b/crates/event_core/src/lib.rs index 25fd1022..92b46adf 100644 --- a/crates/event_core/src/lib.rs +++ b/crates/event_core/src/lib.rs @@ -10,6 +10,7 @@ mod confidence; mod error; mod identifier; mod instance; +mod link; mod mention; mod registry; mod role; @@ -28,6 +29,20 @@ pub use instance::EVENT_INSTANCE_WIRE_SCHEMA_VERSION; pub use instance::EventInstance; /// Explicit refusal to cast a mention as an instance. pub use instance::refuse_mention_as_instance; +/// TDT same-event versus distinct-event link label. +pub use link::EventLinkLabel; +/// Undirected TDT link hypothesis between two mentions. +pub use link::EventLinkPair; +/// Threshold a link probability into a detection label. +pub use link::decide_event_link; +/// Precision of recovered TDT links against known-truth pairs. +pub use link::event_link_precision; +/// Recall of recovered TDT links against known-truth pairs. +pub use link::event_link_recall; +/// Explicit refusal to treat a TDT link as an event instance. +pub use link::refuse_event_link_as_instance; +/// Explicit refusal to treat a TDT link as a state transition. +pub use link::refuse_event_link_as_transition; /// Fallible textual event mention. pub use mention::EventMention; /// In-memory registry separating mentions from instances. diff --git a/crates/event_core/src/link.rs b/crates/event_core/src/link.rs new file mode 100644 index 00000000..8d507c69 --- /dev/null +++ b/crates/event_core/src/link.rs @@ -0,0 +1,223 @@ +//! TDT link-detection scores stay distinct from instances and transitions. + +use crate::{EventConfidence, EventError, EventInstanceId, EventMentionId}; +use std::collections::BTreeSet; + +/// TDT same-event versus distinct-event link label. +/// +/// A link decision is detection evidence. It is never a promoted event instance +/// and cannot create a forward state transition by itself. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum EventLinkLabel { + /// The mention pair is scored as the same event or story. + Linked, + /// The mention pair is scored as distinct events. + Unlinked, +} + +impl EventLinkLabel { + /// Return the stable wire label name. + #[must_use] + pub const fn wire_name(self) -> &'static str { + match self { + Self::Linked => "linked", + Self::Unlinked => "unlinked", + } + } + + /// Parse a stable wire link label. + /// + /// # Errors + /// + /// Returns [`EventError::UnknownEventLinkLabel`] for unrecognized names. + pub fn from_wire_name(name: &str) -> Result { + match name { + "linked" => Ok(Self::Linked), + "unlinked" => Ok(Self::Unlinked), + _ => Err(EventError::UnknownEventLinkLabel), + } + } + + /// Return whether this label is a positive link detection. + #[must_use] + pub const fn is_linked(self) -> bool { + matches!(self, Self::Linked) + } + + /// Return the binary probability target used for RMSE. + /// + /// Linked truth is `1.0`; unlinked truth is `0.0`. + #[must_use] + pub const fn as_probability_target(self) -> f64 { + match self { + Self::Linked => 1.0, + Self::Unlinked => 0.0, + } + } +} + +/// An undirected TDT link hypothesis between two mentions. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct EventLinkPair { + left: EventMentionId, + right: EventMentionId, +} + +impl EventLinkPair { + /// Construct a normalized undirected mention pair. + /// + /// # Errors + /// + /// Returns [`EventError::InvalidWirePayload`] when both mentions are the + /// same identity. A mention cannot link to itself. + pub fn new(left: EventMentionId, right: EventMentionId) -> Result { + if left == right { + return Err(EventError::InvalidWirePayload); + } + if left <= right { + Ok(Self { left, right }) + } else { + Ok(Self { + left: right, + right: left, + }) + } + } + + /// Return the lexicographically smaller mention identifier. + #[must_use] + pub const fn left(self) -> EventMentionId { + self.left + } + + /// Return the lexicographically larger mention identifier. + #[must_use] + pub const fn right(self) -> EventMentionId { + self.right + } +} + +/// Threshold a link probability into a detection label. +/// +/// The threshold is inclusive: `probability >= threshold` is linked. +#[must_use] +pub fn decide_event_link( + probability: EventConfidence, + threshold: EventConfidence, +) -> EventLinkLabel { + if probability.value() >= threshold.value() { + EventLinkLabel::Linked + } else { + EventLinkLabel::Unlinked + } +} + +/// Explicit refusal to treat a TDT link as an event instance. +/// +/// # Errors +/// +/// Always returns [`EventError::EventLinkIsNotEventInstance`]. +pub fn refuse_event_link_as_instance(_link: EventLinkPair) -> Result { + Err(EventError::EventLinkIsNotEventInstance) +} + +/// Explicit refusal to treat a TDT link as a state transition. +/// +/// # Errors +/// +/// Always returns [`EventError::EventLinkIsNotStateTransition`]. +pub fn refuse_event_link_as_transition(_link: EventLinkPair) -> Result<(), EventError> { + Err(EventError::EventLinkIsNotStateTransition) +} + +/// Precision of recovered TDT links against the known-truth pair set. +/// +/// # Errors +/// +/// Returns [`EventError::InvalidWirePayload`] when the recovered set is empty. +pub fn event_link_precision( + truth: &[EventLinkPair], + recovered: &[EventLinkPair], +) -> Result { + let truth_set: BTreeSet<_> = truth.iter().copied().collect(); + let recovered_set: BTreeSet<_> = recovered.iter().copied().collect(); + counted_rate( + recovered_set.intersection(&truth_set).count(), + recovered_set.len(), + ) +} + +/// Recall of recovered TDT links against the known-truth pair set. +/// +/// # Errors +/// +/// Returns [`EventError::InvalidWirePayload`] when the truth set is empty. +pub fn event_link_recall( + truth: &[EventLinkPair], + recovered: &[EventLinkPair], +) -> Result { + let truth_set: BTreeSet<_> = truth.iter().copied().collect(); + let recovered_set: BTreeSet<_> = recovered.iter().copied().collect(); + counted_rate( + recovered_set.intersection(&truth_set).count(), + truth_set.len(), + ) +} + +fn counted_rate(numerator: usize, denominator: usize) -> Result { + let numerator = u32::try_from(numerator).map_err(|_| EventError::InvalidWirePayload)?; + let denominator = u32::try_from(denominator).map_err(|_| EventError::InvalidWirePayload)?; + if denominator == 0 { + return Err(EventError::InvalidWirePayload); + } + Ok(f64::from(numerator) / f64::from(denominator)) +} + +#[cfg(test)] +mod tests { + use super::{ + EventLinkLabel, EventLinkPair, counted_rate, decide_event_link, event_link_precision, + event_link_recall, refuse_event_link_as_instance, refuse_event_link_as_transition, + }; + use crate::{EventConfidence, EventError, EventMentionId}; + + #[test] + fn link_helpers_cover_local_branches() { + let left = EventMentionId::new(); + let right = EventMentionId::new(); + let link = EventLinkPair::new(left, right).expect("pair"); + assert_eq!( + refuse_event_link_as_instance(link), + Err(EventError::EventLinkIsNotEventInstance) + ); + assert_eq!( + refuse_event_link_as_transition(link), + Err(EventError::EventLinkIsNotStateTransition) + ); + let high = EventConfidence::new(0.8).expect("high"); + let low = EventConfidence::new(0.2).expect("low"); + assert_eq!(decide_event_link(high, low), EventLinkLabel::Linked); + assert_eq!(decide_event_link(low, high), EventLinkLabel::Unlinked); + let truth = [link]; + let recovered = [link]; + assert!((event_link_precision(&truth, &recovered).expect("p") - 1.0).abs() < f64::EPSILON); + assert!((event_link_recall(&truth, &recovered).expect("r") - 1.0).abs() < f64::EPSILON); + assert_eq!( + EventLinkPair::new(left, left), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + event_link_precision(&truth, &[]), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + event_link_recall(&[], &recovered), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + counted_rate(0, usize::MAX), + Err(EventError::InvalidWirePayload) + ); + assert_eq!(counted_rate(1, 0), Err(EventError::InvalidWirePayload)); + } +} diff --git a/crates/event_core/tests/link_detection_contract.rs b/crates/event_core/tests/link_detection_contract.rs new file mode 100644 index 00000000..83389c1c --- /dev/null +++ b/crates/event_core/tests/link_detection_contract.rs @@ -0,0 +1,132 @@ +//! TDT link detections are not instances; precision/recall come from truth. + +use event_core::{ + EventConfidence, EventError, EventLinkLabel, EventLinkPair, EventMentionId, decide_event_link, + event_link_precision, event_link_recall, refuse_event_link_as_instance, + refuse_event_link_as_transition, +}; + +fn computed_rmse(truth: &[f64], recovered: &[f64]) -> f64 { + assert_eq!(truth.len(), recovered.len()); + let n = f64::from(u32::try_from(truth.len()).expect("tiny fixture")); + let sse: f64 = truth + .iter() + .zip(recovered) + .map(|(truth_value, recovered_value)| { + let residual = truth_value - recovered_value; + residual * residual + }) + .sum(); + (sse / n).sqrt() +} + +fn pair(left: EventMentionId, right: EventMentionId) -> EventLinkPair { + EventLinkPair::new(left, right).expect("distinct mentions") +} + +#[test] +fn event_link_detection_cannot_be_cast_to_an_instance_or_transition() { + let left = EventMentionId::new(); + let right = EventMentionId::new(); + let link = pair(left, right); + assert_eq!( + refuse_event_link_as_instance(link), + Err(EventError::EventLinkIsNotEventInstance) + ); + assert_eq!( + refuse_event_link_as_transition(link), + Err(EventError::EventLinkIsNotStateTransition) + ); +} + +#[test] +fn precision_and_recall_are_computed_from_known_truth_pairs() { + let a = EventMentionId::new(); + let b = EventMentionId::new(); + let c = EventMentionId::new(); + let d = EventMentionId::new(); + let truth = [pair(a, b), pair(b, c)]; + let calibrated = [pair(a, b)]; + let always_link = [pair(a, b), pair(b, c), pair(c, d), pair(a, d)]; + + let calibrated_precision = event_link_precision(&truth, &calibrated).expect("precision"); + let naive_precision = event_link_precision(&truth, &always_link).expect("naive precision"); + let calibrated_recall = event_link_recall(&truth, &calibrated).expect("recall"); + let naive_recall = event_link_recall(&truth, &always_link).expect("naive recall"); + + assert!( + calibrated_precision > naive_precision, + "computed precision {calibrated_precision} must exceed always-link precision {naive_precision}" + ); + assert!( + calibrated_recall < naive_recall, + "computed recall {calibrated_recall} must stay below the always-link recall {naive_recall}" + ); +} + +#[test] +fn calibrated_link_scores_have_lower_rmse_than_always_link() { + let truth = [1.0_f64, 0.0, 0.0, 1.0, 0.0, 0.0]; + let calibrated = [0.90_f64, 0.10, 0.15, 0.85, 0.20, 0.05]; + let always_link = [1.0_f64, 1.0, 1.0, 1.0, 1.0, 1.0]; + let calibrated_rmse = computed_rmse(&truth, &calibrated); + let naive_rmse = computed_rmse(&truth, &always_link); + assert!( + calibrated_rmse < naive_rmse, + "computed calibrated RMSE {calibrated_rmse} must be below always-link RMSE {naive_rmse}" + ); +} + +#[test] +fn pair_helpers_fail_closed_on_self_links_empty_and_missing_sets() { + let mention = EventMentionId::new(); + assert_eq!( + EventLinkPair::new(mention, mention), + Err(EventError::InvalidWirePayload) + ); + let a = EventMentionId::new(); + let b = EventMentionId::new(); + let truth = [pair(a, b)]; + assert_eq!( + event_link_precision(&truth, &[]), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + event_link_recall(&[], &truth), + Err(EventError::InvalidWirePayload) + ); +} + +#[test] +fn labels_round_trip_and_threshold_is_inclusive() { + assert_eq!(EventLinkLabel::Linked.wire_name(), "linked"); + assert_eq!(EventLinkLabel::Unlinked.wire_name(), "unlinked"); + assert_eq!( + EventLinkLabel::from_wire_name("linked").expect("parse"), + EventLinkLabel::Linked + ); + assert_eq!( + EventLinkLabel::from_wire_name("unlinked").expect("parse"), + EventLinkLabel::Unlinked + ); + assert_eq!( + EventLinkLabel::from_wire_name("same_event"), + Err(EventError::UnknownEventLinkLabel) + ); + assert!(EventLinkLabel::Linked.is_linked()); + assert!(!EventLinkLabel::Unlinked.is_linked()); + assert!((EventLinkLabel::Linked.as_probability_target() - 1.0).abs() < f64::EPSILON); + assert!((EventLinkLabel::Unlinked.as_probability_target() - 0.0).abs() < f64::EPSILON); + + let half = EventConfidence::new(0.5).expect("half"); + assert_eq!(decide_event_link(half, half), EventLinkLabel::Linked); + assert_eq!( + decide_event_link(EventConfidence::new(0.49).expect("below"), half), + EventLinkLabel::Unlinked + ); + + let left = EventMentionId::new(); + let right = EventMentionId::new(); + assert_eq!(pair(left, right), pair(right, left)); + assert_ne!(pair(left, right).left(), pair(left, right).right()); +} diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index 6fad26a1..a80907c0 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -31,7 +31,7 @@ The full APA 7th standards/literature register remains `docs/research/standards- | compositional topic correlation / stable clustering | ADR 0005/0012; research | future `network_analysis` | accepted-target | | posterior ESEM / longitudinal invariance / DSEM | ADR 0005 | future `psychometric_core` | accepted-target | | CPU bounded multithreading + GPU/VRAM streaming/parity | ADR 0001/0006 | future `compute_backend` | accepted-target | -| TDT detection/tracking vs CHRONOS schema/prediction/temporal consistency | ADR 0016; PRD/research | future `event_intelligence` | accepted-target | +| TDT detection/tracking vs CHRONOS schema/prediction/temporal consistency | ADR 0016; PRD/research | `event_core` TDT link precision/recall on the active PR; remaining TDT/CHRONOS stack and any future `event_intelligence` crate remain accepted-target | active-PR | | evidence-bounded LLM interpretation | ADR 0010/0012; PRD | future `interpretation_gateway` | accepted-target | | adaptive direct/verify/committee/conductor test-time compute | ADR 0010; `docs/LLM_ORCHESTRATION.md` | future contextual-orchestrator integration + ablation evidence | accepted-target | | purpose-bound PII handling without blanket masking | ADR 0009; `docs/PRIVACY_DATA_GOVERNANCE.md` | future authorization/persistence/export/provider adapters | accepted-target | diff --git a/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md b/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md index b85ee0b4..6795eaed 100644 --- a/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md +++ b/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md @@ -1,7 +1,7 @@ # ADR 0016 — TDT, CHRONOS, and Event Ontology intelligence boundary **Decision status:** Accepted -**Implementation maturity:** accepted-target +**Implementation maturity:** active-PR — TDT link precision/recall and link-versus-instance/transition refusal live in existing `event_core`; remaining TDT segmentation/tracking/first-story and CHRONOS schema/prediction layers remain accepted-target **Date:** 2026-08-12 **Supersedes:** None; complements ADR 0002 temporal semantics and ADR 0003 event ontology/membership. diff --git a/docs/adr/README.md b/docs/adr/README.md index 1a9a7b31..96dfa771 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -21,7 +21,7 @@ Read [`ADR_POLICY.md`](ADR_POLICY.md) first. **Decision status and implementatio | [0013](0013-bitemporal-persistence-reproducibility-and-split-authority.md) | Bitemporal persistence, reproducibility manifests, and relation-aware split authority | Accepted | partial | Owns PostgreSQL adapter semantics, immutable run/split manifests, leakage-safe partitions, and recovery identity; optional `live-sqlx` `PgPool`, live PG CI, and tenant RLS implemented; full physical ERD remaining. | | [0014](0014-scientific-claim-promotion-and-release-evidence.md) | Scientific claim promotion and release evidence authority | Accepted | partial | Separates design, implementation, scientific/product claim, and release authority; repository SBOM/provenance generator implemented, full release bundle remaining. | | [0015](0015-autonomous-development-review-and-merge-authority.md) | Autonomous development, review, and merge authority separation | Accepted | active-PR | Separates model proposal, deterministic verification, publication, independent review, and merge/release authority. | -| [0016](0016-tdt-chronos-event-intelligence-boundary.md) | TDT, CHRONOS, and Event Ontology intelligence boundary | Accepted | accepted-target | Separates observed evidence, detection/tracking, prediction/schema inference, temporal consistency, and promoted transition authority. | +| [0016](0016-tdt-chronos-event-intelligence-boundary.md) | TDT, CHRONOS, and Event Ontology intelligence boundary | Accepted | active-PR | TDT link precision/recall in existing `event_core`; remaining TDT/CHRONOS stack remains accepted-target. | ## Decision ownership summary diff --git a/docs/research/event-link-detection-calibration.md b/docs/research/event-link-detection-calibration.md new file mode 100644 index 00000000..696da812 --- /dev/null +++ b/docs/research/event-link-detection-calibration.md @@ -0,0 +1,31 @@ +# Event-link detection calibration + +## Scope + +This note doctors the `event_core` gate that keeps TDT link detection distinct from event-instance promotion and state-transition authority: + +1. a linked versus unlinked mention pair is detection evidence, not a promoted instance or transition; +2. precision and recall are computed from known-truth pair sets; +3. calibrated link probabilities recover the binary same-event target with lower RMSE than an always-link detector. + +No database migration is allocated. A later TDT tracker may consume these scores as measurement evidence only. + +## Authoritative sources + +Allan, J., Carbonell, J., Doddington, G., Yamron, J., & Yang, Y. (1998). Topic detection and tracking pilot study: Final report. In *Proceedings of the DARPA Broadcast News Transcription and Understanding Workshop* (pp. 194–218). + +Allan, J. (Ed.). (2002). *Topic detection and tracking: Event-based information organization*. Kluwer Academic Publishers. + +Fiscus, J. G., & Doddington, G. R. (2002). Topic detection and tracking evaluation overview. In J. Allan (Ed.), *Topic detection and tracking: Event-based information organization* (pp. 17–31). Kluwer Academic Publishers. + +## Application + +Allan et al. (1998) and Allan (2002) define link detection as a *same-event / same-story* decision over story pairs. Fiscus and Doddington (2002) keep official TDT link scores in the measurement layer and report miss and false-alarm trade-offs rather than instance identity. TEPP therefore refuses to cast a detected link as an event instance or a forward state transition and requires computed precision, recall, and RMSE against known truth (Allan et al., 1998; Allan, 2002; Fiscus & Doddington, 2002). + +## Verification + +- `refuse_event_link_as_instance` always returns `EventLinkIsNotEventInstance`; +- `refuse_event_link_as_transition` always returns `EventLinkIsNotStateTransition`; +- `EventLinkPair::new` refuses a self-link and normalizes pair order; +- `event_link_precision` and `event_link_recall` fail closed on empty recovered or truth sets; +- computed RMSE of known link targets is lower under calibrated probabilities than under an always-link detector. diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index b4b14468..10abd9f7 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -62,8 +62,12 @@ International Organization for Standardization. (2012). *Language resource manag Hobbs, J. R., & Pan, F. (2017). *Time ontology in OWL* (W3C Recommendation). World Wide Web Consortium. https://www.w3.org/TR/owl-time/ +Allan, J., Carbonell, J., Doddington, G., Yamron, J., & Yang, Y. (1998). Topic detection and tracking pilot study: Final report. In *Proceedings of the DARPA Broadcast News Transcription and Understanding Workshop* (pp. 194–218). + Allan, J. (Ed.). (2002). *Topic detection and tracking: Event-based information organization*. Kluwer Academic Publishers. +Fiscus, J. G., & Doddington, G. R. (2002). Topic detection and tracking evaluation overview. In J. Allan (Ed.), *Topic detection and tracking: Event-based information organization* (pp. 17–31). Kluwer Academic Publishers. + Anagnostopoulos, E., Batsakis, S., & Petrakis, E. G. M. (2013). CHRONOS: A reasoning engine for qualitative temporal information in OWL. *Procedia Computer Science, 22*, 70–77. https://doi.org/10.1016/j.procs.2013.09.082 TEPP uses interval and partial-order reasoning, bitemporal availability, leakage-safe cutoffs, TDT segmentation/link/detection/first-story/tracking tasks, and separate neural/symbolic event-schema and temporal-consistency layers. diff --git a/docs/validation/temporal-event-foundation.md b/docs/validation/temporal-event-foundation.md index 984d329c..fef7a9be 100644 --- a/docs/validation/temporal-event-foundation.md +++ b/docs/validation/temporal-event-foundation.md @@ -16,6 +16,7 @@ This report tracks exact-head scientific and engineering evidence required befor | Six-clock temporal | `temporal_core` | implemented-main | — | unit + wire | Task 3 / PR #8 | | Allen path-consistency | `temporal_core` | implemented-main | — | unit + budget tests | Task 4 / PR #9 | | Event mention/instance | `event_core` | partial | — | unit + fail-closed promotion | Task 5 / PR #13 | +| TDT link precision/recall | `event_core` | active-PR | this PR | computed precision/recall + RMSE vs always-link | ADR 0016; `docs/research/event-link-detection-calibration.md` | | Multiple membership | `membership_core` | partial | — | unit + ESS weights | Task 7 / PR #12 + #25 | | Forward transition DAG | `relation_graph` | implemented-main | — | unit + cycle rejection | Task 6 / PR #14 | | Bitemporal persistence + live SQL port | `persistence_postgres` | partial | #36 typed membership | migration contracts + recording transport + optional PgPool + live CI + tenant RLS + `0005` interval CHECKs (implemented-main via #35) + `0006` typed membership (active PR) | Task 8 / PR #16 + #23 + #26 + #27 + #29 + #30–#35 + `0006` | From 0fd2e207c731909581469a1ea2c0c1b55bd99d9a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 16:24:51 +0900 Subject: [PATCH 2/2] docs(connectors): restore status-line hard break --- docs/connectors/naruon-artifact-consumer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/connectors/naruon-artifact-consumer.md b/docs/connectors/naruon-artifact-consumer.md index 5fe0424c..2e4f4d6c 100644 --- a/docs/connectors/naruon-artifact-consumer.md +++ b/docs/connectors/naruon-artifact-consumer.md @@ -1,6 +1,6 @@ # naruon modular consumer contract for TEPP artifacts -**Status:** Partial — versioned DTO, HTTP interchange, and loopback live listener on the active PR; production TLS/`$PORT` remaining +**Status:** Partial — versioned DTO, HTTP interchange, and loopback live listener on the active PR; production TLS/`$PORT` remaining **Last reviewed:** 2026-08-16 ## Boundary