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 } /// Generic, template-driven takeaways extracted from the named transcript: a TLDR /// plus an ordered list of sections whose categories come from the active /// `RecapTemplate` (Settings), not from code. Speakers are real names (label-merge). struct RecapExtras: Equatable, Codable { var tldr: String var primarySpeakers: [String] var sections: [RenderedSection] var isEmpty: Bool { tldr.isEmpty && sections.allSatisfy { $0.isEmpty } } } /// One rendered takeaways section. Only the field matching `kind` is populated. struct RenderedSection: Equatable, Codable { var title: String var kind: SectionKind var bullets: [String] var items: [RecapItem] var paragraph: String var isEmpty: Bool { bullets.isEmpty && items.isEmpty && paragraph.isEmpty } init(title: String, kind: SectionKind, bullets: [String] = [], items: [RecapItem] = [], paragraph: String = "") { self.title = title; self.kind = kind; self.bullets = bullets; self.items = items; self.paragraph = paragraph } } /// A single attributed point: the statement plus optional speaker / timestamp / note. /// Subsumes decisions, action items, questions, quotes, etc. struct RecapItem: Equatable, Codable { var text: String var who: String? var when: Int? // seconds var note: String? } /// 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: RecapExtras? } /// 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) } }