grammargem/Sources/GrammaGem/Grammar/WritingScore.swift
BigMarc 143620e44e Add Grammarly-style value loop: score, buckets, dismiss, streaks, demo
Implements the highest-leverage items from docs/grammarly-value-plan.md:
- Live Check: on-device 0-100 writing score, Correctness/Clarity/Polish
  count chips, and per-suggestion Dismiss / Add-to-dictionary (a cheap,
  always-available 'no' is what makes 'Apply' feel safe).
- Value loop: day-streak, correction milestones and a 'subscription you
  didn't pay' figure on the dashboard + a streak line in the menu bar
  (all local, self-vs-self, no telemetry).
- First-run demo: a planted sample the real engine cleans in one click,
  before any permission ask.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 06:43:31 +02:00

21 lines
1013 B
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
/// A deterministic, on-device "cleanliness" score (0–100) for a chunk of text.
///
/// Honest by construction: it's defined relative to *this* text — weighted issues
/// per 100 words — never a hidden cloud cohort, and grammar/spelling are always
/// fully resolvable, so a clean document genuinely reaches 100. (Contrast with
/// Grammarly's percentile-vs-other-users score, which needs telemetry + an
/// account; see docs/grammarly-value-plan.md §5.)
enum WritingScore {
/// - Parameters:
/// - weightedIssues: sum of `GrammarKind.severity` across outstanding issues.
/// - words: total words in the scanned text.
static func score(weightedIssues: Int, words: Int) -> Int {
guard words > 0 else { return 100 }
let perHundredWords = Double(weightedIssues) / Double(words) * 100.0
// Each weighted issue per 100 words costs ~4 points; clamp to 0…100.
return max(0, min(100, Int((100.0 - perHundredWords * 4.0).rounded())))
}
}