import Foundation /// One topic section: a contiguous run of transcript entries `[startIndex...endIndex]` /// (inclusive, indices into the canonical entries array) with an LLM title + summary. struct TopicSection: Equatable, Codable { var title: String var summary: String var startIndex: Int var endIndex: Int } /// Structured "meeting extras" extracted from the named transcript. Mirrors /// recap-relay's schema; speakers are real names (we already have them from /// label-merge), not anonymous cluster ids. struct MeetingExtras: Equatable, Codable { struct TLDR: Equatable, Codable { var summary: String; var primarySpeakers: [String] } struct Decision: Equatable, Codable { var statement: String; var agreedBy: [String]; var supportingOffset: Int? } struct ActionItem: Equatable, Codable { var description: String; var owner: String?; var dueHint: String?; var supportingOffset: Int? } struct OpenQuestion: Equatable, Codable { var question: String; var raisedBy: String? } struct KeyQuote: Equatable, Codable { var speaker: String?; var offset: Int?; var quote: String; var whyNotable: String } var tldr: TLDR var decisions: [Decision] var actionItems: [ActionItem] var openQuestions: [OpenQuestion] var keyQuotes: [KeyQuote] var isEmptyBeyondTLDR: Bool { decisions.isEmpty && actionItems.isEmpty && openQuestions.isEmpty && keyQuotes.isEmpty } } /// The assembled recap for one session: the topic sections + (optional) extras, /// over the session's transcript. Rendered to `transcript.md` / `recap.html`. struct RecapResult: Equatable, Codable { var sections: [TopicSection] var extras: MeetingExtras? } /// Persisted `recap.json` — the recap result plus its title, so the speaker editor /// can re-render `recap.html` / `transcript.md` after corrections without re-calling /// the LLM (a "Regenerate" action re-runs analysis when the user wants fresh summaries). struct RecapFile: Equatable, Codable { var title: String var result: RecapResult func write(to url: URL) throws { let enc = JSONEncoder(); enc.outputFormatting = [.prettyPrinted, .sortedKeys] try enc.encode(self).write(to: url) } static func read(from url: URL) -> RecapFile? { guard let data = try? Data(contentsOf: url) else { return nil } return try? JSONDecoder().decode(RecapFile.self, from: data) } }