studio-engine 0.68.0 → 0.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/app/controllers/studio/knowledge_docs_controller.rb +49 -1
- data/app/models/studio/knowledge_doc.rb +6 -0
- data/app/models/studio/knowledge_expectation.rb +91 -0
- data/app/views/studio/knowledge_docs/_browser.html.erb +2 -0
- data/app/views/studio/knowledge_docs/_coverage_table.html.erb +109 -0
- data/app/views/studio/knowledge_docs/coverage.html.erb +4 -0
- data/app/views/studio/knowledge_docs/show.html.erb +9 -0
- data/db/migrate/20260902000001_create_studio_knowledge_expectations.rb +32 -0
- data/db/migrate/20260902000002_add_expectation_to_studio_knowledge_docs.rb +9 -0
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +8 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 528dc35d5f8eb65f9c9be8b69d5541d9eaba24bb6e18ec1b2d144274c39f3b75
|
|
4
|
+
data.tar.gz: 92b56fbeda78e7404eaf3ea3832aaa4cc1faa43d88915be60bc4dc7cdc694a3e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d95ee9d3c5cf24d466feb69d52e90e763bc7985543740df1458d46dedca41def112f35ce2e3a6642b02ac37a195ac5efe2adec043ecad9516d3e03bd650c156
|
|
7
|
+
data.tar.gz: 4d2d7a231e57e84fb746d0b5db9c77f2780c03c1a7e817273a898bc4484be68d55765bcbc51ccd7e4fc784591bed4fadce17f6e28637ccdbbb713cb0ccf8070c
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Knowledge coverage view** — `/admin/knowledge/coverage` +
|
|
10
|
+
`Studio::KnowledgeExpectation`: the "what SHOULD exist" half of the
|
|
11
|
+
knowledge layer. Expectations carry an entity, folder, provenance
|
|
12
|
+
(`source_note`, e.g. "diligence tracker item 14"), and a cadence — `once`
|
|
13
|
+
(an LOI) or `monthly` (aging inventory), where each calendar month from
|
|
14
|
+
`start_on` is a slot and the view names missing months outright
|
|
15
|
+
(`2026-09 MISSING`). Documents fulfill expectations by an EXPLICIT
|
|
16
|
+
`expectation_id` set at triage (the show page grows a "fulfills expectation"
|
|
17
|
+
select; a fuzzy name-match would silently merge lookalikes, an id never
|
|
18
|
+
does), and superseded documents never fill a slot. Ships two reference
|
|
19
|
+
migrations (`create_studio_knowledge_expectations`,
|
|
20
|
+
`add_expectation_to_studio_knowledge_docs`); routes ride the existing
|
|
21
|
+
`Studio.draw_knowledge_routes` opt-in. Built for the Commercial Welding
|
|
22
|
+
65-item diligence tracker; app-agnostic like the rest of the layer.
|
|
23
|
+
|
|
7
24
|
### Changed
|
|
8
25
|
|
|
9
26
|
- **The auth modal's Solana button moved to solana-studio, behind a CREDENTIAL
|
|
@@ -43,6 +43,47 @@ module Studio
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def show
|
|
46
|
+
# Guarded like intake!'s MissingTable posture: a consumer that bumped the
|
|
47
|
+
# gem without installing the expectations migrations keeps its working
|
|
48
|
+
# doc pages — the link select simply doesn't render.
|
|
49
|
+
@linkable_expectations =
|
|
50
|
+
if Studio::KnowledgeExpectation.table_exists?
|
|
51
|
+
Studio::KnowledgeExpectation.active.for_entity(@doc.entity).order(:path, :title).to_a
|
|
52
|
+
else
|
|
53
|
+
[]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# GET /admin/knowledge/coverage — expected vs uploaded, the gap named.
|
|
58
|
+
def coverage
|
|
59
|
+
unless Studio::KnowledgeExpectation.table_exists?
|
|
60
|
+
return redirect_to admin_knowledge_path,
|
|
61
|
+
alert: "The coverage view needs the studio_knowledge_expectations table — " \
|
|
62
|
+
"run `bin/rails studio_engine:install:migrations && bin/rails db:migrate`."
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
@entity = params[:entity].presence
|
|
66
|
+
scope = Studio::KnowledgeExpectation.active.order(:path, :title)
|
|
67
|
+
scope = scope.for_entity(@entity) if @entity
|
|
68
|
+
@expectations = scope.to_a
|
|
69
|
+
@entities = Studio::KnowledgeExpectation.distinct.pluck(:entity).sort
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def create_expectation
|
|
73
|
+
expectation = Studio::KnowledgeExpectation.create!(expectation_params)
|
|
74
|
+
redirect_to admin_knowledge_coverage_path(entity: expectation.entity),
|
|
75
|
+
notice: "Expecting: #{expectation.title}."
|
|
76
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
77
|
+
redirect_to admin_knowledge_coverage_path, alert: e.message
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def update_expectation
|
|
81
|
+
expectation = Studio::KnowledgeExpectation.find(params[:id])
|
|
82
|
+
expectation.update!(expectation_params)
|
|
83
|
+
redirect_to admin_knowledge_coverage_path(entity: expectation.entity),
|
|
84
|
+
notice: "#{expectation.title} updated."
|
|
85
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
86
|
+
redirect_to admin_knowledge_coverage_path, alert: e.message
|
|
46
87
|
end
|
|
47
88
|
|
|
48
89
|
def create
|
|
@@ -73,6 +114,12 @@ module Studio
|
|
|
73
114
|
|
|
74
115
|
private
|
|
75
116
|
|
|
117
|
+
def expectation_params
|
|
118
|
+
params.require(:knowledge_expectation)
|
|
119
|
+
.permit(:entity, :title, :path, :category, :cadence, :start_on,
|
|
120
|
+
:source_note, :active)
|
|
121
|
+
end
|
|
122
|
+
|
|
76
123
|
def set_doc
|
|
77
124
|
@doc = Studio::KnowledgeDoc.find(params[:id])
|
|
78
125
|
end
|
|
@@ -80,7 +127,8 @@ module Studio
|
|
|
80
127
|
def doc_params
|
|
81
128
|
permitted = params.require(:knowledge_doc)
|
|
82
129
|
.permit(:title, :entity, :path, :category, :summary,
|
|
83
|
-
:document_date, :source_note, :status,
|
|
130
|
+
:document_date, :source_note, :status,
|
|
131
|
+
:expectation_id, access: {})
|
|
84
132
|
# The access map arrives as {"samson" => "full", ...}; drop blanks so an
|
|
85
133
|
# untouched select doesn't write a "none" the map means by absence anyway.
|
|
86
134
|
if permitted[:access]
|
|
@@ -35,6 +35,12 @@ module Studio
|
|
|
35
35
|
ACCESS_LEVELS = %w[full aware none].freeze
|
|
36
36
|
STATUSES = %w[inbox filed superseded].freeze
|
|
37
37
|
|
|
38
|
+
# The expectation this document fulfills (coverage view) — optional, set at
|
|
39
|
+
# triage. Guarded: consumers that installed the docs table before the
|
|
40
|
+
# expectations migration existed simply have no column yet.
|
|
41
|
+
belongs_to :expectation, class_name: "Studio::KnowledgeExpectation",
|
|
42
|
+
optional: true, inverse_of: :docs
|
|
43
|
+
|
|
38
44
|
validates :title, presence: true
|
|
39
45
|
validates :entity, presence: true
|
|
40
46
|
validates :status, inclusion: { in: STATUSES }
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module Studio
|
|
2
|
+
# One row per document the knowledge layer EXPECTS — the other half of the
|
|
3
|
+
# coverage question. Documents record what exists; expectations record what
|
|
4
|
+
# should, so the gap between them is computable instead of remembered.
|
|
5
|
+
#
|
|
6
|
+
# Two cadences:
|
|
7
|
+
#
|
|
8
|
+
# once — a single fulfilling document satisfies it forever (an LOI, a
|
|
9
|
+
# lease, tracker item 14).
|
|
10
|
+
# monthly — one document per calendar month from start_on onward (aging
|
|
11
|
+
# inventory, bank statements). Each month is a SLOT; a slot is
|
|
12
|
+
# filled when a linked doc's as-of date falls inside it, and the
|
|
13
|
+
# coverage view names the missing months outright.
|
|
14
|
+
#
|
|
15
|
+
# Fulfillment is an EXPLICIT link — Studio::KnowledgeDoc#expectation_id, set
|
|
16
|
+
# at triage — never a name match: a fuzzy matcher silently merges lookalike
|
|
17
|
+
# documents, an id never does.
|
|
18
|
+
#
|
|
19
|
+
# Installed per consumer like the other studio_* tables.
|
|
20
|
+
class KnowledgeExpectation < ApplicationRecord
|
|
21
|
+
self.table_name = "studio_knowledge_expectations"
|
|
22
|
+
|
|
23
|
+
CADENCES = %w[once monthly].freeze
|
|
24
|
+
|
|
25
|
+
has_many :docs, class_name: "Studio::KnowledgeDoc",
|
|
26
|
+
foreign_key: :expectation_id, inverse_of: :expectation,
|
|
27
|
+
dependent: :nullify
|
|
28
|
+
|
|
29
|
+
validates :entity, presence: true
|
|
30
|
+
validates :title, presence: true
|
|
31
|
+
validates :cadence, inclusion: { in: CADENCES }
|
|
32
|
+
|
|
33
|
+
before_validation :normalize_fields
|
|
34
|
+
|
|
35
|
+
scope :for_entity, ->(entity) { where(entity: entity) }
|
|
36
|
+
scope :active, -> { where(active: true) }
|
|
37
|
+
|
|
38
|
+
def monthly?
|
|
39
|
+
cadence == "monthly"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The linked documents that can fill slots — superseded rows never count.
|
|
43
|
+
def fulfilling_docs
|
|
44
|
+
docs.active.to_a
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Monthly slot months, oldest first: start_on's month (or this row's
|
|
48
|
+
# creation month) through as_of's month. Nil for once-cadence.
|
|
49
|
+
def slot_months(as_of: Date.current)
|
|
50
|
+
return nil unless monthly?
|
|
51
|
+
|
|
52
|
+
first = (start_on || created_at&.to_date || as_of).beginning_of_month
|
|
53
|
+
last = as_of.beginning_of_month
|
|
54
|
+
return [] if first > last
|
|
55
|
+
|
|
56
|
+
months = []
|
|
57
|
+
cursor = first
|
|
58
|
+
while cursor <= last
|
|
59
|
+
months << cursor
|
|
60
|
+
cursor = cursor.next_month
|
|
61
|
+
end
|
|
62
|
+
months
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Coverage for the view, one shape per cadence:
|
|
66
|
+
# once: { filled: bool, docs: [...] }
|
|
67
|
+
# monthly: { slots: [{ month:, docs: [...] }], missing_months: [...] }
|
|
68
|
+
def coverage(as_of: Date.current)
|
|
69
|
+
linked = fulfilling_docs
|
|
70
|
+
return { filled: linked.any?, docs: linked } unless monthly?
|
|
71
|
+
|
|
72
|
+
slots = slot_months(as_of: as_of).map do |month|
|
|
73
|
+
{ month: month,
|
|
74
|
+
docs: linked.select { |doc| doc.display_date&.beginning_of_month == month } }
|
|
75
|
+
end
|
|
76
|
+
{ slots: slots, missing_months: slots.select { |slot| slot[:docs].empty? }.map { |slot| slot[:month] } }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def filled?(as_of: Date.current)
|
|
80
|
+
result = coverage(as_of: as_of)
|
|
81
|
+
monthly? ? result[:missing_months].empty? : result[:filled]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def normalize_fields
|
|
87
|
+
self.entity = entity.to_s.strip.downcase if entity
|
|
88
|
+
self.path = Studio::KnowledgeDoc.normalize_path(path)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
<span aria-hidden="true">·</span>
|
|
44
44
|
<%= link_to "Show all", admin_knowledge_path(view: "flat", entity: entity, status: status),
|
|
45
45
|
class: view == "flat" ? "font-semibold underline" : "" %>
|
|
46
|
+
<span aria-hidden="true">·</span>
|
|
47
|
+
<%= link_to "Coverage", admin_knowledge_coverage_path(entity: entity), id: "knowledge-coverage-link" %>
|
|
46
48
|
</div>
|
|
47
49
|
</div>
|
|
48
50
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<%# The coverage table — expected vs uploaded, gaps named. Locals:
|
|
2
|
+
expectations: active Studio::KnowledgeExpectation rows to render
|
|
3
|
+
entity: entity filter or nil
|
|
4
|
+
entities: all entity slugs (filter row) %>
|
|
5
|
+
<style>
|
|
6
|
+
.coverage-mark { font-size: .8rem; padding: 0 .45rem; border-radius: .25rem; white-space: nowrap; }
|
|
7
|
+
.coverage-filled { color: rgb(5 150 105); }
|
|
8
|
+
.coverage-missing { color: rgb(220 38 38); border: 1px solid rgb(220 38 38 / .5); font-weight: 600; }
|
|
9
|
+
.coverage-slot { display: inline-block; margin-right: .4rem; }
|
|
10
|
+
</style>
|
|
11
|
+
|
|
12
|
+
<div class="flex flex-wrap items-center justify-between gap-3 mb-4">
|
|
13
|
+
<h1 class="text-xl font-semibold">Coverage</h1>
|
|
14
|
+
<div class="text-sm"><%= link_to "← Knowledge", admin_knowledge_path(entity: entity) %></div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<% if entities.length > 1 || entity %>
|
|
18
|
+
<div class="flex flex-wrap items-center gap-2 mb-3 text-sm">
|
|
19
|
+
<%= link_to "All entities", admin_knowledge_coverage_path, class: entity ? "" : "font-semibold underline" %>
|
|
20
|
+
<% entities.each do |candidate| %>
|
|
21
|
+
<%= link_to candidate, admin_knowledge_coverage_path(entity: candidate),
|
|
22
|
+
class: candidate == entity ? "font-semibold underline" : "" %>
|
|
23
|
+
<% end %>
|
|
24
|
+
</div>
|
|
25
|
+
<% end %>
|
|
26
|
+
|
|
27
|
+
<% if expectations.any? %>
|
|
28
|
+
<div class="overflow-x-auto mb-6">
|
|
29
|
+
<table id="knowledge-coverage" class="w-full text-sm">
|
|
30
|
+
<thead>
|
|
31
|
+
<tr class="text-left border-b">
|
|
32
|
+
<th class="py-2 pr-3">Expected</th>
|
|
33
|
+
<th class="py-2 pr-3">Folder</th>
|
|
34
|
+
<th class="py-2 pr-3">Cadence</th>
|
|
35
|
+
<th class="py-2 pr-3">Coverage</th>
|
|
36
|
+
</tr>
|
|
37
|
+
</thead>
|
|
38
|
+
<tbody>
|
|
39
|
+
<% expectations.each do |expectation| %>
|
|
40
|
+
<% result = expectation.coverage %>
|
|
41
|
+
<tr id="coverage-exp-<%= expectation.id %>" class="border-b align-top">
|
|
42
|
+
<td class="py-2 pr-3">
|
|
43
|
+
<span class="font-medium"><%= expectation.title %></span>
|
|
44
|
+
<% if expectation.source_note.present? %>
|
|
45
|
+
<div class="text-xs opacity-60"><%= expectation.source_note %></div>
|
|
46
|
+
<% end %>
|
|
47
|
+
</td>
|
|
48
|
+
<td class="py-2 pr-3 text-xs opacity-80"><%= expectation.path.presence || "root" %></td>
|
|
49
|
+
<td class="py-2 pr-3"><%= expectation.cadence %></td>
|
|
50
|
+
<td class="py-2 pr-3">
|
|
51
|
+
<% if expectation.monthly? %>
|
|
52
|
+
<% result[:slots].each do |slot| %>
|
|
53
|
+
<span class="coverage-slot coverage-mark <%= slot[:docs].any? ? "coverage-filled" : "coverage-missing" %>"
|
|
54
|
+
id="coverage-slot-<%= expectation.id %>-<%= slot[:month].strftime("%Y-%m") %>">
|
|
55
|
+
<%= slot[:month].strftime("%Y-%m") %> <%= slot[:docs].any? ? "✓" : "MISSING" %>
|
|
56
|
+
</span>
|
|
57
|
+
<% end %>
|
|
58
|
+
<% elsif result[:filled] %>
|
|
59
|
+
<span class="coverage-mark coverage-filled">✓ filled —
|
|
60
|
+
<% result[:docs].first(2).each do |doc| %>
|
|
61
|
+
<%= link_to doc.title, admin_knowledge_doc_path(doc) %>
|
|
62
|
+
<% end %>
|
|
63
|
+
</span>
|
|
64
|
+
<% else %>
|
|
65
|
+
<span class="coverage-mark coverage-missing">MISSING</span>
|
|
66
|
+
<% end %>
|
|
67
|
+
</td>
|
|
68
|
+
</tr>
|
|
69
|
+
<% end %>
|
|
70
|
+
</tbody>
|
|
71
|
+
</table>
|
|
72
|
+
</div>
|
|
73
|
+
<% else %>
|
|
74
|
+
<p id="coverage-empty" class="text-sm opacity-70 mb-4">Nothing is expected yet — add the first expectation below.</p>
|
|
75
|
+
<% end %>
|
|
76
|
+
|
|
77
|
+
<div id="coverage-new" class="p-4 border rounded">
|
|
78
|
+
<h2 class="font-semibold mb-3">Expect a document</h2>
|
|
79
|
+
<%= form_with url: admin_knowledge_expectations_path, method: :post, local: true do |form| %>
|
|
80
|
+
<div class="grid gap-3 md:grid-cols-3">
|
|
81
|
+
<label class="block text-sm">
|
|
82
|
+
<span class="block mb-1">Title</span>
|
|
83
|
+
<%= form.text_field "knowledge_expectation[title]", required: true, class: "block w-full border rounded px-2 py-1" %>
|
|
84
|
+
</label>
|
|
85
|
+
<label class="block text-sm">
|
|
86
|
+
<span class="block mb-1">Entity</span>
|
|
87
|
+
<%= form.text_field "knowledge_expectation[entity]", value: entity, required: true, class: "block w-full border rounded px-2 py-1" %>
|
|
88
|
+
</label>
|
|
89
|
+
<label class="block text-sm">
|
|
90
|
+
<span class="block mb-1">Folder</span>
|
|
91
|
+
<%= form.text_field "knowledge_expectation[path]", class: "block w-full border rounded px-2 py-1" %>
|
|
92
|
+
</label>
|
|
93
|
+
<label class="block text-sm">
|
|
94
|
+
<span class="block mb-1">Cadence</span>
|
|
95
|
+
<%= form.select "knowledge_expectation[cadence]",
|
|
96
|
+
[["once", "once"], ["monthly", "monthly"]], {}, class: "block w-full border rounded px-2 py-1" %>
|
|
97
|
+
</label>
|
|
98
|
+
<label class="block text-sm">
|
|
99
|
+
<span class="block mb-1">Starts <span class="opacity-60">(monthly only)</span></span>
|
|
100
|
+
<%= form.date_field "knowledge_expectation[start_on]", class: "block w-full border rounded px-2 py-1" %>
|
|
101
|
+
</label>
|
|
102
|
+
<label class="block text-sm">
|
|
103
|
+
<span class="block mb-1">Source <span class="opacity-60">("tracker item 14")</span></span>
|
|
104
|
+
<%= form.text_field "knowledge_expectation[source_note]", class: "block w-full border rounded px-2 py-1" %>
|
|
105
|
+
</label>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="mt-3"><%= form.submit "Add expectation", class: "px-3 py-1.5 border rounded font-medium" %></div>
|
|
108
|
+
<% end %>
|
|
109
|
+
</div>
|
|
@@ -69,6 +69,15 @@
|
|
|
69
69
|
class: "block w-full border rounded px-2 py-1" %>
|
|
70
70
|
</label>
|
|
71
71
|
<% end %>
|
|
72
|
+
<% expectations = @linkable_expectations || [] %>
|
|
73
|
+
<% if expectations.any? %>
|
|
74
|
+
<label class="block text-sm" id="knowledge-expectation-select">
|
|
75
|
+
<span class="block mb-1">Fulfills expectation</span>
|
|
76
|
+
<%= form.select "knowledge_doc[expectation_id]",
|
|
77
|
+
[["— none —", ""]] + expectations.map { |e| [ "#{e.title} (#{e.cadence})", e.id ] },
|
|
78
|
+
{ selected: @doc.expectation_id }, class: "block w-full border rounded px-2 py-1" %>
|
|
79
|
+
</label>
|
|
80
|
+
<% end %>
|
|
72
81
|
<label class="block text-sm md:col-span-2">
|
|
73
82
|
<span class="block mb-1">Summary (safe for "aware" agents)</span>
|
|
74
83
|
<%= form.text_area "knowledge_doc[summary]", value: @doc.summary, rows: 3, class: "block w-full border rounded px-2 py-1" %>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Reference migration for Studio::KnowledgeExpectation — the "what SHOULD
|
|
2
|
+
# exist" half of the knowledge layer's coverage view. Installed per consumer
|
|
3
|
+
# like the other studio_* tables (`bin/rails studio_engine:install:migrations
|
|
4
|
+
# && bin/rails db:migrate`); do not hand-copy.
|
|
5
|
+
#
|
|
6
|
+
# STYLE NOTE: reference migrations are written omakase-rubocop-compatible
|
|
7
|
+
# (spaces inside array brackets) because the installed copy is linted by the
|
|
8
|
+
# CONSUMER's rubocop, not this gem's.
|
|
9
|
+
class CreateStudioKnowledgeExpectations < ActiveRecord::Migration[7.2]
|
|
10
|
+
def change
|
|
11
|
+
create_table :studio_knowledge_expectations do |t|
|
|
12
|
+
# Which business expects the document (same vocabulary as knowledge docs).
|
|
13
|
+
t.string :entity, null: false
|
|
14
|
+
t.string :title, null: false
|
|
15
|
+
# Folder the fulfilling documents live under; display + default, not a matcher.
|
|
16
|
+
t.string :path, null: false, default: ""
|
|
17
|
+
t.string :category
|
|
18
|
+
# once: a single document satisfies it forever.
|
|
19
|
+
# monthly: one document per calendar month from start_on onward.
|
|
20
|
+
t.string :cadence, null: false, default: "once"
|
|
21
|
+
# First month a monthly expectation owes a document (defaults to creation month).
|
|
22
|
+
t.date :start_on
|
|
23
|
+
# Provenance: "diligence tracker item 14", "lender checklist", …
|
|
24
|
+
t.string :source_note
|
|
25
|
+
t.boolean :active, null: false, default: true
|
|
26
|
+
|
|
27
|
+
t.timestamps
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
add_index :studio_knowledge_expectations, [ :entity, :active ]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Links a knowledge doc to the expectation it fulfills — set at triage, an
|
|
2
|
+
# EXPLICIT pointer rather than name-matching (a fuzzy match silently merges
|
|
3
|
+
# lookalikes; an id never does). Installed per consumer; do not hand-copy.
|
|
4
|
+
class AddExpectationToStudioKnowledgeDocs < ActiveRecord::Migration[7.2]
|
|
5
|
+
def change
|
|
6
|
+
add_column :studio_knowledge_docs, :expectation_id, :bigint
|
|
7
|
+
add_index :studio_knowledge_docs, :expectation_id
|
|
8
|
+
end
|
|
9
|
+
end
|
data/lib/studio/version.rb
CHANGED
data/lib/studio.rb
CHANGED
|
@@ -861,6 +861,14 @@ module Studio
|
|
|
861
861
|
if Studio.draw_knowledge_routes
|
|
862
862
|
get "admin/knowledge", to: "studio/knowledge_docs#index", as: :admin_knowledge
|
|
863
863
|
post "admin/knowledge", to: "studio/knowledge_docs#create"
|
|
864
|
+
# Coverage (expected vs uploaded) — MUST precede the :id route below,
|
|
865
|
+
# or "coverage" is swallowed as a document id.
|
|
866
|
+
get "admin/knowledge/coverage", to: "studio/knowledge_docs#coverage",
|
|
867
|
+
as: :admin_knowledge_coverage
|
|
868
|
+
post "admin/knowledge/expectations", to: "studio/knowledge_docs#create_expectation",
|
|
869
|
+
as: :admin_knowledge_expectations
|
|
870
|
+
patch "admin/knowledge/expectations/:id", to: "studio/knowledge_docs#update_expectation",
|
|
871
|
+
as: :admin_knowledge_expectation
|
|
864
872
|
get "admin/knowledge/:id", to: "studio/knowledge_docs#show", as: :admin_knowledge_doc
|
|
865
873
|
patch "admin/knowledge/:id", to: "studio/knowledge_docs#update"
|
|
866
874
|
get "admin/knowledge/:id/download", to: "studio/knowledge_docs#download",
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: studio-engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.69.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex McRitchie
|
|
@@ -363,6 +363,7 @@ files:
|
|
|
363
363
|
- app/models/studio/enumeral.rb
|
|
364
364
|
- app/models/studio/geo_setting.rb
|
|
365
365
|
- app/models/studio/knowledge_doc.rb
|
|
366
|
+
- app/models/studio/knowledge_expectation.rb
|
|
366
367
|
- app/models/studio/link.rb
|
|
367
368
|
- app/models/studio/model_page.rb
|
|
368
369
|
- app/models/theme_setting.rb
|
|
@@ -441,8 +442,10 @@ files:
|
|
|
441
442
|
- app/views/studio/knowledge_docs/_access_chips.html.erb
|
|
442
443
|
- app/views/studio/knowledge_docs/_browser.html.erb
|
|
443
444
|
- app/views/studio/knowledge_docs/_chip_styles.html.erb
|
|
445
|
+
- app/views/studio/knowledge_docs/_coverage_table.html.erb
|
|
444
446
|
- app/views/studio/knowledge_docs/_doc_row.html.erb
|
|
445
447
|
- app/views/studio/knowledge_docs/_upload_form.html.erb
|
|
448
|
+
- app/views/studio/knowledge_docs/coverage.html.erb
|
|
446
449
|
- app/views/studio/knowledge_docs/index.html.erb
|
|
447
450
|
- app/views/studio/knowledge_docs/show.html.erb
|
|
448
451
|
- app/views/studio/links/confirm.html.erb
|
|
@@ -554,6 +557,8 @@ files:
|
|
|
554
557
|
- db/migrate/20260813220000_add_standard_user_profile_columns.rb
|
|
555
558
|
- db/migrate/20260818120000_create_studio_geo_settings.rb
|
|
556
559
|
- db/migrate/20260901000001_create_studio_knowledge_docs.rb
|
|
560
|
+
- db/migrate/20260902000001_create_studio_knowledge_expectations.rb
|
|
561
|
+
- db/migrate/20260902000002_add_expectation_to_studio_knowledge_docs.rb
|
|
557
562
|
- lib/studio-engine.rb
|
|
558
563
|
- lib/studio.rb
|
|
559
564
|
- lib/studio/cable.rb
|