Class: Agentilda::Linear::Survey

Inherits:
Object
  • Object
show all
Defined in:
lib/agentilda/linear/survey.rb

Overview

What a team already has in Linear, set against what .plans holds.

This exists because the import cannot see Linear at all — that is the property that makes its dry run trustworthy — and the cost of that property is a blind spot: a plan whose project someone already made by hand looks, from disk, exactly like a plan that has none. Proposing to create it would quietly produce a second project beside the first.

So the reconciliation is its own read-only command rather than something smuggled into the import. It answers one question — is anything here already in Linear under a different name? — and answers it by showing both lists rather than by guessing.

Defined Under Namespace

Classes: Match

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tree:, projects:) ⇒ Survey

Returns a new instance of Survey.

Parameters:



30
31
32
33
# File 'lib/agentilda/linear/survey.rb', line 30

def initialize(tree:, projects:)
  @tree = tree
  @projects = projects
end

Instance Attribute Details

#projectsArray<Hash> (readonly)

Returns every project, as Linear reports it.

Returns:

  • (Array<Hash>)

    every project, as Linear reports it



36
37
38
# File 'lib/agentilda/linear/survey.rb', line 36

def projects
  @projects
end

Instance Method Details

#described?(project) ⇒ Boolean

Parameters:

  • project (Hash)

Returns:

  • (Boolean)


89
# File 'lib/agentilda/linear/survey.rb', line 89

def described?(project) = !prose(project).strip.empty?

#guess(subject) ⇒ Array(Hash, Integer)?

The best guess at which project a plan belongs under, for --auto-assign, with the number of words it agreed on.

One shared word is not evidence — half these plans mention "tax" — so a guess needs two. Even then it is reported as a guess wherever it appears, because plaid-integration matching Plaid Connectivity is obvious to a person and, to this, indistinguishable from tax-engine-consolidation matching Tax Law Engine, which is wrong.

Parameters:

Returns:

  • (Array(Hash, Integer), nil)

    the project and its score



70
71
72
73
74
75
# File 'lib/agentilda/linear/survey.rb', line 70

def guess(subject)
  best, score = scored(subject).first
  return nil unless best && score >= 2

  [best, score]
end

#matchesArray<Agentilda::Linear::Survey::Match>



39
# File 'lib/agentilda/linear/survey.rb', line 39

def matches = @matches ||= projects.filter_map { |project| match_for(project) }

#near(subject) ⇒ Array<Hash>

Projects whose name shares the plan's words without matching either rule, best first.

Parameters:

Returns:

  • (Array<Hash>)


57
# File 'lib/agentilda/linear/survey.rb', line 57

def near(subject) = scored(subject).map(&:first)

#project(reference) ⇒ Hash

Find one project by whatever the user had to hand: its URL, its name, or its id. A URL is the thing you can actually copy out of Linear.

Parameters:

  • reference (String)

Returns:

  • (Hash)

Raises:



97
98
99
100
101
102
103
104
105
106
107
# File 'lib/agentilda/linear/survey.rb', line 97

def project(reference)
  wanted = reference.to_s.strip
  found = projects.find { |p|
    p["url"].to_s.casecmp?(wanted) || p["name"].to_s.casecmp?(wanted) ||
      p["id"] == wanted || p["url"].to_s.end_with?("/#{slugify(wanted)}")
  }
  return found if found

  raise Error, "no project matches #{reference.inspect}. This team has:\n" +
    projects.map { |p| "  #{p["name"]}\n    #{p["url"]}" }.join("\n")
end

#uncoveredArray<Agentilda::Subject>

Plans with no project. An import would create one for each.

Returns:



50
# File 'lib/agentilda/linear/survey.rb', line 50

def uncovered = tree.subjects - matches.map(&:subject)

#undescribedArray<Hash>

Projects with nothing written about them.

A name is three or four words and half of them are the company's. What makes a plan recognisably one project's business rather than another's is the sentence saying what the project is for, and where that sentence is missing there is nothing to match a specification against.

Returns:

  • (Array<Hash>)


85
# File 'lib/agentilda/linear/survey.rb', line 85

def undescribed = projects.reject { |p| described?(p) }

#unmatchedArray<Hash>

Projects that correspond to no plan. Usually the team's real, hand-curated projects — the ones an import has no business touching.

Returns:

  • (Array<Hash>)


45
# File 'lib/agentilda/linear/survey.rb', line 45

def unmatched = projects - matches.map(&:project)