refactor(examples): replace sector-specific example material with generic, fictitious examples
The context sets, the packaged knowledge bases and the example bundles are replaced by one fictitious example set about IT operations in an invented organisation: three context sets (serverrom-2027, driftsavtale-2027 and the two-base drift-og-avtale-2027), two synthetic knowledge bases under src/portfolio_optimiser/data/kunnskapsbaser and two example bundles under src/portfolio_optimiser/data/bundles. Numbers, codes and structural values in tests and fixtures are kept; names, ids and wording change. Dated measurement documents that only recorded runs on the replaced material are deleted. Gate figures measured on the new set are not comparable with earlier ones. The exclusion gate from the previous commit is green: 0 tracked files hit outside the shared/ subtree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
058dd25570
commit
37547fe292
1147 changed files with 24138 additions and 9503 deletions
415
examples/syntetiske-kunnskapsbaser/generate.py
Normal file
415
examples/syntetiske-kunnskapsbaser/generate.py
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
"""Build the two fictional example knowledge bases from ``kilde.json`` and pin them.
|
||||
|
||||
Usage (from the repository root)::
|
||||
|
||||
uv run python examples/syntetiske-kunnskapsbaser/generate.py
|
||||
|
||||
Writes ``src/portfolio_optimiser/data/kunnskapsbaser/<name>-<sha12>/`` for each base, removes any
|
||||
older ``<name>-*`` copy, and rewrites the ``bundles`` entries of
|
||||
``src/portfolio_optimiser/frozen_bundles.json`` with the new digests. Renewing a base is therefore
|
||||
ONE command, and the new copy and the new pin land in the same diff.
|
||||
|
||||
**Deterministic by construction**: no clock, no randomness, sorted output, and document ids are
|
||||
``uuid5`` of a fixed namespace and the document's own number. Running it twice gives byte-identical
|
||||
bases and the same pins — ``--check`` says whether the tracked copies are what the source builds.
|
||||
|
||||
**Why generated rather than built with ``okf build``**: the code consumes per-document identifier
|
||||
frontmatter (``req_number``, ``prosessnr``, ``seksjon``), and ``okf build`` takes frontmatter only
|
||||
as ONE ``--frontmatter KEY=VALUE`` applied to every concept. The OKF layout itself — a root
|
||||
``index.md`` declaring ``okf_version`` and ``bundle_id``, a linked ``index.md`` per level, one
|
||||
concept document per file with a block ``sources`` sequence — is written here directly.
|
||||
|
||||
Everything the bases say is invented: Eksempelvirksomheten, its requirements and its process
|
||||
catalogue describe no real organisation, and no text is taken from a real standard.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
REPO = HERE.parents[1]
|
||||
SOURCE = HERE / "kilde.json"
|
||||
TARGET = REPO / "src" / "portfolio_optimiser" / "data" / "kunnskapsbaser"
|
||||
PIN_FILE = REPO / "src" / "portfolio_optimiser" / "frozen_bundles.json"
|
||||
|
||||
_NAMESPACE = uuid.uuid5(uuid.NAMESPACE_URL, "https://example.invalid/eksempelvirksomheten")
|
||||
_DESCRIPTION_MAX = 160
|
||||
|
||||
|
||||
def _uid(*parts: str) -> str:
|
||||
return str(uuid.uuid5(_NAMESPACE, "/".join(parts)))
|
||||
|
||||
|
||||
def _quoted(value: str) -> str:
|
||||
"""A number-shaped scalar is quoted, as a YAML dumper would, so it stays a string."""
|
||||
return f"'{value}'"
|
||||
|
||||
|
||||
def _float_like(value: str) -> bool:
|
||||
head, dot, tail = value.partition(".")
|
||||
return head.isdigit() and (not dot or (tail.isdigit() and "." not in tail))
|
||||
|
||||
|
||||
def _capitalised(text: str) -> str:
|
||||
return text[:1].upper() + text[1:]
|
||||
|
||||
|
||||
def _short(text: str) -> str:
|
||||
if len(text) <= _DESCRIPTION_MAX:
|
||||
return text
|
||||
return text[: _DESCRIPTION_MAX - 1].rstrip() + "…"
|
||||
|
||||
|
||||
def _frontmatter(pairs: list[tuple[str, str]], source: tuple[str, str]) -> str:
|
||||
lines = ["---", *(f"{key}: {value}" for key, value in pairs)]
|
||||
lines += ["sources:", f" - resource: {source[0]}", f" title: {source[1]}", "---"]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def _write(root: Path, rel: str, text: str) -> None:
|
||||
path = root / rel
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text, encoding="utf-8", newline="\n")
|
||||
|
||||
|
||||
def _sort_key(number: str) -> tuple[int, ...]:
|
||||
"""Natural order of a section number such as ``4.2.5.1``."""
|
||||
return tuple(int(part) for part in number.split("."))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------ the requirements
|
||||
|
||||
|
||||
def _requirements(part: dict[str, Any], spec: dict[str, Any]) -> list[dict[str, str]]:
|
||||
"""Every requirement of one part: explicit ones verbatim, the rest from the aspect templates."""
|
||||
aspects = spec["aspects"]
|
||||
params = spec["parameters"]
|
||||
out: list[dict[str, str]] = []
|
||||
for index, section in enumerate(part["sections"]):
|
||||
if "krav" in section:
|
||||
for suffix, text in section["krav"]:
|
||||
out.append(
|
||||
{
|
||||
"section": section["nr"],
|
||||
"section_title": section["title"],
|
||||
"suffix": suffix,
|
||||
"text": text,
|
||||
"guidance": section["veiledning"],
|
||||
}
|
||||
)
|
||||
continue
|
||||
subject = section["subject"]
|
||||
for n in range(1, int(section["count"]) + 1):
|
||||
aspect = aspects[(index + n - 1) % len(aspects)]
|
||||
values = {
|
||||
"S": _capitalised(subject),
|
||||
"s": subject,
|
||||
**{
|
||||
key: str(options[(index + n) % len(options)]) for key, options in params.items()
|
||||
},
|
||||
}
|
||||
out.append(
|
||||
{
|
||||
"section": section["nr"],
|
||||
"section_title": section["title"],
|
||||
"suffix": str(n),
|
||||
"text": aspect["krav"].format(**values),
|
||||
"guidance": aspect["veiledning"].format(**values),
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def build_requirements(spec: dict[str, Any], stamp: str, honesty: str, root: Path) -> None:
|
||||
source_root = spec["source_root"]
|
||||
level_lines: list[str] = []
|
||||
standard_lines: list[str] = []
|
||||
total = 0
|
||||
for part in spec["parts"]:
|
||||
pid = part["id"]
|
||||
edition = f"{pid}:2027"
|
||||
source = (f"{source_root}/{pid.lower()}", edition)
|
||||
entries: list[tuple[tuple[int, ...], str, str]] = []
|
||||
for req in _requirements(part, spec):
|
||||
number = f"Krav {req['section']}—{req['suffix']}"
|
||||
title = f"{number} {req['section_title']}"
|
||||
doc_id = f"id-{_uid('driftskrav', pid, number)}"
|
||||
section = req["section"]
|
||||
text = req["text"]
|
||||
pairs = [
|
||||
("type", "Krav"),
|
||||
("title", title),
|
||||
("description", _short(text)),
|
||||
("kravtype", "bør" if " bør " in f" {text} " else "skal"),
|
||||
("standard", pid),
|
||||
("utgave", edition),
|
||||
("req_number", number),
|
||||
("status", "stable"),
|
||||
("trust_tier", "unverified"),
|
||||
("seksjon", _quoted(section) if _float_like(section) else section),
|
||||
("seksjonstittel", req["section_title"]),
|
||||
("ingested_at", stamp),
|
||||
("source_element_id", doc_id),
|
||||
]
|
||||
body = f"\n## Krav\n\n{text}\n\n## Veiledning (ikke-normativ)\n\n{req['guidance']}\n"
|
||||
_write(root, f"krav/{pid}/{doc_id}.md", _frontmatter(pairs, source) + body)
|
||||
entries.append(
|
||||
(_sort_key(section), req["suffix"], f"- [{title}]({doc_id}.md) — {_short(text)}")
|
||||
)
|
||||
entries.sort(key=lambda e: (e[0], [int(p) for p in e[1].split("_")]))
|
||||
_write(root, f"krav/{pid}/index.md", "# Krav\n\n" + "\n".join(e[2] for e in entries) + "\n")
|
||||
level_lines.append(
|
||||
f"- [{pid}]({pid}/index.md) — {len(entries)} konsepter under krav/{pid}."
|
||||
)
|
||||
total += len(entries)
|
||||
|
||||
pairs = [
|
||||
("type", "Standard"),
|
||||
("title", edition),
|
||||
("description", f"Driftskrav del {pid[1:]}: {part['title']}"),
|
||||
("standard", pid),
|
||||
("utgave", edition),
|
||||
("status", "stable"),
|
||||
("trust_tier", "unverified"),
|
||||
("krav_i_bundlen", str(len(entries))),
|
||||
("ingested_at", stamp),
|
||||
]
|
||||
body = (
|
||||
f"\n## Om delen\n\n{part['scope']} Delen bærer {len(entries)} krav, hvert som eget "
|
||||
f"konsept under `krav/{pid}/`.\n\n## Opphav\n\n{honesty}\n"
|
||||
)
|
||||
_write(root, f"standard/{pid}.md", _frontmatter(pairs, source) + body)
|
||||
standard_lines.append(
|
||||
f"- [{edition}]({pid}.md) — Driftskrav del {pid[1:]}: {part['title']}"
|
||||
)
|
||||
|
||||
_write(root, "krav/index.md", "# Kataloger\n\n" + "\n".join(level_lines) + "\n")
|
||||
_write(root, "standard/index.md", "# Standard\n\n" + "\n".join(standard_lines) + "\n")
|
||||
_write(
|
||||
root,
|
||||
"index.md",
|
||||
f"---\nokf_version: 0.2\nbundle_id: {spec['bundle_id']}\n---\n\n# Kataloger\n\n"
|
||||
f"- [krav](krav/index.md) — {total} konsepter under krav.\n"
|
||||
f"- [standard](standard/index.md) — {len(spec['parts'])} konsepter under standard.\n",
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------- the process catalogue
|
||||
|
||||
|
||||
def _processes(spec: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""The catalogue as a flat list, parents before children, each with its number and relations."""
|
||||
out: list[dict[str, Any]] = []
|
||||
first = int(spec["default_subs_for_first"])
|
||||
for main in spec["main"]:
|
||||
main_node = {
|
||||
"nr": main["nr"],
|
||||
"title": f"Hovedprosess {main['nr']} {main['title']}",
|
||||
"link": f"Hovedprosess {main['nr']} {main['title']}",
|
||||
"level": 1,
|
||||
"main": main["nr"],
|
||||
"parents": [],
|
||||
"omfang": None,
|
||||
"seksjon": f"Hovedprosess {main['nr']}",
|
||||
"seksjonstittel": main["title"],
|
||||
}
|
||||
out.append(main_node)
|
||||
for group in main["groups"]:
|
||||
group_node = {
|
||||
"nr": group["nr"],
|
||||
"title": group["title"],
|
||||
"link": f"{group['nr']} {group['title']}",
|
||||
"level": 2,
|
||||
"main": main["nr"],
|
||||
"parents": [main_node],
|
||||
"omfang": None,
|
||||
}
|
||||
out.append(group_node)
|
||||
for i, proc in enumerate(group["processes"], start=1):
|
||||
obj = proc.get("obj", proc["title"][:1].lower() + proc["title"][1:])
|
||||
nr = f"{group['nr']}.{i}"
|
||||
node = {
|
||||
"nr": nr,
|
||||
"title": proc["title"],
|
||||
"link": f"{nr} {proc['title']}",
|
||||
"level": 3,
|
||||
"main": main["nr"],
|
||||
"parents": [group_node, main_node],
|
||||
"omfang": proc.get("omfang", spec["default_omfang"].format(obj=obj)),
|
||||
}
|
||||
out.append(node)
|
||||
subs = proc.get("subs")
|
||||
if subs is None:
|
||||
subs = spec["default_subs"] if i <= first else []
|
||||
for j, sub in enumerate(subs, start=1):
|
||||
sub_nr = f"{nr}{j}"
|
||||
title = sub["title"].format(obj=obj)
|
||||
out.append(
|
||||
{
|
||||
"nr": sub_nr,
|
||||
"title": title,
|
||||
"link": f"{sub_nr} {title}",
|
||||
"level": 4,
|
||||
"main": main["nr"],
|
||||
"parents": [node, group_node, main_node],
|
||||
"omfang": sub["omfang"].format(obj=obj),
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def build_catalogue(spec: dict[str, Any], stamp: str, honesty: str, root: Path) -> None:
|
||||
cat = spec["catalogue"]
|
||||
source = (f"{spec['source_root']}/{cat.lower()}", spec["edition"])
|
||||
nodes = _processes(spec)
|
||||
for node in nodes:
|
||||
node["dir"] = node["nr"].replace(".", "-")
|
||||
node["file"] = f"_{node['main']}_id-{_uid('prosesskatalog', node['nr'])}.md"
|
||||
node["children"] = []
|
||||
by_nr = {node["nr"]: node for node in nodes}
|
||||
for node in nodes:
|
||||
if node["parents"]:
|
||||
by_nr[node["parents"][0]["nr"]]["children"].append(node)
|
||||
|
||||
def rel(node: dict[str, Any]) -> str:
|
||||
return f"../{node['dir']}/{node['file']}"
|
||||
|
||||
for node in nodes:
|
||||
description = node["omfang"].split(". ")[0].rstrip(".") + "." if node["omfang"] else ""
|
||||
summary = _short(description or node["title"])
|
||||
pairs = [
|
||||
("type", "Prosess"),
|
||||
("title", node["title"]),
|
||||
("description", summary),
|
||||
("standard", spec["catalogue_title"]),
|
||||
("utgave", spec["edition"]),
|
||||
("prosessnr", _quoted(node["nr"])),
|
||||
("hovedprosess", _quoted(node["main"])),
|
||||
]
|
||||
if node["parents"]:
|
||||
pairs.append(("forelder", _quoted(node["parents"][0]["nr"])))
|
||||
pairs += [
|
||||
("nivaa", str(node["level"])),
|
||||
("status", "stable"),
|
||||
("trust_tier", "unverified"),
|
||||
("seksjon", node.get("seksjon", _quoted(node["nr"]))),
|
||||
("seksjonstittel", node.get("seksjonstittel", node["title"])),
|
||||
("ingested_at", stamp),
|
||||
("source_element_id", node["file"][:-3]),
|
||||
]
|
||||
body = ""
|
||||
if node["omfang"]:
|
||||
body += f"\n## a) Omfang\n\n{node['omfang']}\n"
|
||||
body += "\n## Relasjoner\n"
|
||||
if node["parents"]:
|
||||
body += "\n### Overordnede prosesser\n\n"
|
||||
body += "\n".join(f"- [{p['link']}]({rel(p)})" for p in node["parents"]) + "\n"
|
||||
if node["children"]:
|
||||
body += "\n### Underprosesser\n\n"
|
||||
body += "\n".join(f"- [{c['link']}]({rel(c)})" for c in node["children"]) + "\n"
|
||||
_write(root, f"{cat}/{node['dir']}/{node['file']}", _frontmatter(pairs, source) + body)
|
||||
_write(
|
||||
root,
|
||||
f"{cat}/{node['dir']}/index.md",
|
||||
f"# Prosess\n\n- [{node['title']}]({node['file']}) — {summary}\n",
|
||||
)
|
||||
|
||||
dirs = sorted(node["dir"] for node in nodes)
|
||||
_write(
|
||||
root,
|
||||
f"{cat}/index.md",
|
||||
"# Kataloger\n\n"
|
||||
+ "\n".join(f"- [{d}]({d}/index.md) — 1 konsept under {cat}/{d}." for d in dirs)
|
||||
+ "\n",
|
||||
)
|
||||
mains = [node for node in nodes if node["level"] == 1]
|
||||
pairs = [
|
||||
("type", "Katalog"),
|
||||
("title", spec["edition"]),
|
||||
("description", spec["edition"]),
|
||||
("standard", spec["catalogue_title"]),
|
||||
("utgave", spec["edition"]),
|
||||
("status", "stable"),
|
||||
("trust_tier", "unverified"),
|
||||
("prosesser_i_bundlen", str(len(nodes))),
|
||||
("ingested_at", stamp),
|
||||
]
|
||||
body = (
|
||||
f"\n## Dekning\n\n{spec['scope']} Bundlen bærer {len(nodes)} prosesser, hver som eget "
|
||||
f"konsept under `{cat}/<prosessnummer>/`.\n\n## Opphav\n\n{honesty}\n\n"
|
||||
"## Hovedprosesser\n\n"
|
||||
+ "\n".join(f"- [{m['title']}]({cat}/{m['dir']}/{m['file']})" for m in mains)
|
||||
+ "\n"
|
||||
)
|
||||
_write(root, f"{cat}.md", _frontmatter(pairs, source) + body)
|
||||
_write(
|
||||
root,
|
||||
"index.md",
|
||||
f"---\nokf_version: 0.2\nbundle_id: {spec['bundle_id']}\n---\n\n# Kataloger\n\n"
|
||||
f"- [{cat}]({cat}/index.md) — {len(nodes)} konsepter under {cat}.\n\n# Katalog\n\n"
|
||||
f"- [{spec['edition']}]({cat}.md) — {spec['edition']}\n",
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------------ the pins
|
||||
|
||||
|
||||
def _digest(root: Path) -> tuple[str, int]:
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
from portfolio_optimiser.frozen_bundles import digest_bundle
|
||||
|
||||
return digest_bundle(root)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="build into a temporary directory and compare with the pins; write nothing",
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
source = json.loads(SOURCE.read_text(encoding="utf-8"))
|
||||
stamp, honesty = source["ingested_at"], source["honesty"]
|
||||
builders = (
|
||||
(source["driftskrav"], build_requirements),
|
||||
(source["prosesskatalog"], build_catalogue),
|
||||
)
|
||||
pins = json.loads(PIN_FILE.read_text(encoding="utf-8"))
|
||||
built: dict[str, dict[str, Any]] = {}
|
||||
drift = 0
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
for spec, build in builders:
|
||||
staged = Path(tmp) / spec["name"]
|
||||
build(spec, stamp, honesty, staged)
|
||||
sha, files = _digest(staged)
|
||||
directory = f"{spec['name']}-{sha[:12]}"
|
||||
pinned = pins["bundles"].get(spec["name"], {})
|
||||
same = pinned.get("sha256") == sha and (TARGET / directory).is_dir()
|
||||
print(f"{spec['name']}: {directory} ({files} files){'' if same else ' CHANGED'}")
|
||||
if args.check:
|
||||
drift += 0 if same else 1
|
||||
continue
|
||||
for old in TARGET.glob(f"{spec['name']}-*"):
|
||||
shutil.rmtree(old)
|
||||
TARGET.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copytree(staged, TARGET / directory)
|
||||
built[spec["name"]] = {"directory": directory, "sha256": sha, "files": files}
|
||||
if args.check:
|
||||
return 1 if drift else 0
|
||||
pins["bundles"] = built
|
||||
PIN_FILE.write_text(json.dumps(pins, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
326
examples/syntetiske-kunnskapsbaser/kilde.json
Normal file
326
examples/syntetiske-kunnskapsbaser/kilde.json
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
{
|
||||
"about": "Fictional source for the two example knowledge bases. Eksempelvirksomheten, its requirements and its process catalogue are invented; no real organisation, place, person or publication is described. generate.py turns this file into OKF bundles.",
|
||||
"ingested_at": "2027-01-15T12:00:00Z",
|
||||
"honesty": "Alt innhold i denne kunnskapsbasen er oppdiktet eksempelmateriale om IT-drift i den fiktive Eksempelvirksomheten. Det er generert deterministisk av examples/syntetiske-kunnskapsbaser/generate.py fra kilde.json, og ingen tekst er hentet fra en virkelig standard.",
|
||||
"driftskrav": {
|
||||
"name": "driftskrav-2027",
|
||||
"bundle_id": "eksempel-driftskrav-2027",
|
||||
"source_root": "https://example.invalid/eksempelvirksomheten/driftskrav",
|
||||
"aspects": [
|
||||
{
|
||||
"krav": "{S} skal være dokumentert med eier, plassering og driftsrutiner før tjenesten settes i drift.",
|
||||
"veiledning": "Dokumentasjonen holdes i driftshåndboken og oppdateres ved hver endring."
|
||||
},
|
||||
{
|
||||
"krav": "{S} skal overvåkes kontinuerlig, og avvik skal varsles til driftssenteret innen {m} minutter.",
|
||||
"veiledning": "Varslingstiden regnes fra avviket oppstår til driftssenteret har mottatt alarmen."
|
||||
},
|
||||
{
|
||||
"krav": "Forebyggende vedlikehold av {s} skal utføres minst {k} ganger per år.",
|
||||
"veiledning": "Vedlikeholdet følger produsentens anvisning og protokollføres i vedlikeholdsplanen."
|
||||
},
|
||||
{
|
||||
"krav": "{S} skal funksjonstestes minst én gang per år, og resultatet skal protokollføres.",
|
||||
"veiledning": "Testen gjennomføres i et avtalt vindu og med en plan for tilbakeføring."
|
||||
},
|
||||
{
|
||||
"krav": "Endringer i {s} skal gjennomføres etter godkjent endringsrutine og med plan for tilbakeføring.",
|
||||
"veiledning": "Mindre standardendringer kan forhåndsgodkjennes i endringsrutinen."
|
||||
},
|
||||
{
|
||||
"krav": "{S} skal ha en kapasitetsreserve på minst {p} % ved normal last.",
|
||||
"veiledning": "Reserven vurderes på nytt når driftsklassen for en tjeneste endres."
|
||||
},
|
||||
{
|
||||
"krav": "Avvik som gjelder {s}, skal registreres i avvikssystemet og lukkes med dokumentert årsak.",
|
||||
"veiledning": "Gjentatte avvik av samme art behandles som et problem og følges opp særskilt."
|
||||
},
|
||||
{
|
||||
"krav": "{S} bør gjennomgås årlig mot gjeldende trusselbilde og driftsbehov.",
|
||||
"veiledning": "Gjennomgangen kan samordnes med den årlige risikovurderingen."
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"m": [5, 10, 15, 30],
|
||||
"k": [2, 4, 6],
|
||||
"p": [10, 15, 20, 25, 30]
|
||||
},
|
||||
"parts": [
|
||||
{
|
||||
"id": "D100",
|
||||
"title": "Brukerflate og klientdrift",
|
||||
"scope": "Delen stiller krav til klientplattform, brukerstøtte, tilgang og utstyr på arbeidsplasser og i møterom.",
|
||||
"sections": [
|
||||
{"nr": "1.1", "title": "Standardklient", "subject": "standardklienten", "count": 6},
|
||||
{"nr": "1.2", "title": "Programvaredistribusjon", "subject": "programvaredistribusjonen", "count": 5},
|
||||
{"nr": "1.3", "title": "Oppdateringer", "subject": "oppdateringsrutinen for klienter", "count": 5},
|
||||
{"nr": "2.1", "title": "Tjenestedisk", "subject": "tjenestedisken", "count": 6},
|
||||
{"nr": "2.2", "title": "Responstid", "subject": "responstiden for henvendelser", "count": 5},
|
||||
{"nr": "3.1", "title": "Brukerkontoer", "subject": "brukerkontoene", "count": 6},
|
||||
{"nr": "3.2", "title": "Tofaktorpålogging", "subject": "tofaktorpåloggingen", "count": 5},
|
||||
{"nr": "4.1", "title": "Skjermer", "subject": "skjermene på arbeidsplassene", "count": 4},
|
||||
{
|
||||
"nr": "4.2.5.1",
|
||||
"title": "Skjerm og kamera",
|
||||
"veiledning": "Kravene gjelder møterom med fast utstyr. Mobile løsninger omfattes ikke.",
|
||||
"krav": [
|
||||
["1", "Hvert møterom med fast utstyr skal ha skjerm og kamera som dekker alle sitteplasser."],
|
||||
["2", "Skjermen skal kunne vise innhold fra både bærbar klient og videomøte uten omkobling av kabler."],
|
||||
["3", "Skjerm og kamera skal kunne styres fra ett panel, og oppsettet skal være likt i alle møterom av samme størrelse."]
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "D200",
|
||||
"title": "Serverrom og infrastruktur",
|
||||
"scope": "Delen stiller krav til serverrom, strømforsyning, kjøling, lagring, nettverk og overvåking.",
|
||||
"sections": [
|
||||
{"nr": "1.1", "title": "Omfang", "subject": "driftsmiljøet", "count": 6},
|
||||
{"nr": "1.2", "title": "Driftsklasser", "subject": "driftsklassene", "count": 6},
|
||||
{"nr": "1.3", "title": "Dokumentasjon", "subject": "driftsdokumentasjonen", "count": 6},
|
||||
{"nr": "2.1", "title": "Plassering", "subject": "serverrommet", "count": 7},
|
||||
{"nr": "2.2", "title": "Gulv og bæreevne", "subject": "det tekniske gulvet", "count": 5},
|
||||
{"nr": "2.3", "title": "Adgangskontroll", "subject": "adgangskontrollen til serverrommet", "count": 7},
|
||||
{"nr": "2.4", "title": "Brannsikring", "subject": "brannslokkeanlegget", "count": 7},
|
||||
{"nr": "2.5", "title": "Vannlekkasje", "subject": "lekkasjedeteksjonen", "count": 5},
|
||||
{"nr": "3.1", "title": "Hovedtilførsel", "subject": "hovedtilførselen", "count": 6},
|
||||
{
|
||||
"nr": "3.2.1",
|
||||
"title": "Autonomitid",
|
||||
"veiledning": "Autonomitiden gjelder UPS-anlegget alene, uten bidrag fra nødstrømsaggregatet.",
|
||||
"krav": [
|
||||
["1", "UPS-anlegget skal gi en autonomitid på minst 15 minutter ved full last i driftsklasse 1."],
|
||||
["2", "Autonomitiden skal være lang nok til at nødstrømsaggregatet kan starte og overta lasten, med minst 5 minutters margin."],
|
||||
["3", "Autonomitiden skal verifiseres ved belastningstest minst én gang per år."],
|
||||
["4", "Der nødstrømsaggregat mangler, skal autonomitiden være lang nok til kontrollert nedstenging av alle tjenester i driftsklasse 1 og 2."]
|
||||
]
|
||||
},
|
||||
{"nr": "3.2.2", "title": "Batterier", "subject": "batteribanken", "count": 7},
|
||||
{"nr": "3.2.3", "title": "Bypass", "subject": "bypass-bryteren", "count": 5},
|
||||
{"nr": "3.3", "title": "Nødstrømsaggregat", "subject": "nødstrømsaggregatet", "count": 8},
|
||||
{"nr": "3.4", "title": "Strømfordeling", "subject": "strømfordelingen i rackradene", "count": 7},
|
||||
{"nr": "3.5", "title": "Jording", "subject": "jordingsanlegget", "count": 5},
|
||||
{
|
||||
"nr": "4.1.1",
|
||||
"title": "Dimensjonering",
|
||||
"veiledning": "Målt varmelast er grunnlaget. Installert merkeeffekt overvurderer som regel lasten.",
|
||||
"krav": [
|
||||
["1", "Kjøleanlegget skal dimensjoneres etter målt varmelast pluss dokumentert vekst i avtaleperioden, ikke etter installert merkeeffekt."],
|
||||
["2", "Kjølekapasiteten skal ha en reserve på minst 20 % over dimensjonerende varmelast."],
|
||||
["3", "Dimensjonerende varmelast skal måles over minst fire uker før kapasiteten fastsettes."],
|
||||
["4", "Kjølekapasiteten skal kunne utvides trinnvis uten at serverrommet må tas ut av drift."],
|
||||
["5", "Dimensjoneringsgrunnlaget skal dokumenteres og oppdateres når varmelasten endres med mer enn 10 %."]
|
||||
]
|
||||
},
|
||||
{"nr": "4.1.2", "title": "Redundans", "subject": "redundansen i kjøleanlegget", "count": 6},
|
||||
{"nr": "4.1.3", "title": "Kuldemedium", "subject": "kuldemediet", "count": 5},
|
||||
{"nr": "4.2.1", "title": "Kaldgang", "subject": "kaldgangen", "count": 6},
|
||||
{"nr": "4.2.2", "title": "Varmgang", "subject": "varmgangen", "count": 5},
|
||||
{"nr": "4.2.3", "title": "Blindplater", "subject": "blindplatene i rackskapene", "count": 5},
|
||||
{"nr": "4.3", "title": "Temperatur og fukt", "subject": "temperaturen og fuktigheten i serverrommet", "count": 7},
|
||||
{
|
||||
"nr": "4.4",
|
||||
"title": "Frikjøling",
|
||||
"veiledning": "Frikjøling reduserer driften av kompressorer i de kalde delene av året.",
|
||||
"krav": [
|
||||
["1", "Frikjøling skal benyttes når utetemperaturen tillater det, og styringen skal skifte automatisk mellom frikjøling og mekanisk kjøling."],
|
||||
["2", "Frikjølingsanlegget skal ha filter som hindrer støv og fukt i å nå serverrommet."],
|
||||
["3", "Omkoblingen mellom frikjøling og mekanisk kjøling skal ikke gi temperaturavvik utenfor tillatt område i serverrommet."]
|
||||
]
|
||||
},
|
||||
{"nr": "5.1", "title": "Lagringssystemer", "subject": "lagringssystemene", "count": 8},
|
||||
{"nr": "5.2.1", "title": "Kopifrekvens", "subject": "sikkerhetskopieringen", "count": 7},
|
||||
{
|
||||
"nr": "5.2.2",
|
||||
"title": "Lagringsklasser",
|
||||
"veiledning": "En lagringsklasse beskriver hvor raskt en kopi kan hentes fram igjen, ikke hvilket utstyr den ligger på.",
|
||||
"krav": [
|
||||
["1", "Sikkerhetskopier skal lagres i minst to lagringsklasser, der minst én er atskilt fra produksjonsmiljøet."],
|
||||
["1_1", "Sikkerhetskopier eldre enn 90 dager kan flyttes til en lagringsklasse med lengre gjenopprettingstid, dersom gjenopprettingstiden for tjenestens driftsklasse fortsatt overholdes."],
|
||||
["2", "Lagringsklassen for hver tjeneste skal fremgå av driftsdokumentasjonen."]
|
||||
]
|
||||
},
|
||||
{"nr": "5.2.3", "title": "Gjenoppretting", "subject": "gjenopprettingen fra sikkerhetskopi", "count": 7},
|
||||
{"nr": "5.3", "title": "Arkivering", "subject": "arkivlagringen", "count": 6},
|
||||
{"nr": "6.1", "title": "Kjernenett", "subject": "kjernenettet", "count": 7},
|
||||
{"nr": "6.2", "title": "Kabling", "subject": "kablingen", "count": 7},
|
||||
{"nr": "6.3", "title": "Merking", "subject": "merkingen av kabler og porter", "count": 5},
|
||||
{
|
||||
"nr": "6.4",
|
||||
"title": "Rackskap",
|
||||
"veiledning": "Gjenbrukte rackskap omfattes av de samme kravene som nye.",
|
||||
"krav": [
|
||||
["1", "Rackskap skal ha dokumentert bæreevne og være forankret til gulv eller vegg."],
|
||||
["2", "Rackskap skal ha lås, og nøkler skal forvaltes etter rutinen for adgangskontroll."],
|
||||
["3", "Rackskap som gjenbrukes, skal kontrolleres for skader og bæreevne før montering."],
|
||||
["4", "Hvert rackskap skal ha to uavhengige strømskinner i driftsklasse 1."]
|
||||
]
|
||||
},
|
||||
{"nr": "7.1", "title": "Overvåking", "subject": "overvåkingssystemet", "count": 8},
|
||||
{"nr": "7.2", "title": "Alarmer", "subject": "alarmhåndteringen", "count": 7},
|
||||
{"nr": "7.3", "title": "Vedlikehold", "subject": "vedlikeholdsplanen", "count": 6},
|
||||
{"nr": "7.4", "title": "Endringer", "subject": "endringsrutinen", "count": 6},
|
||||
{"nr": "7.5", "title": "Avvik", "subject": "avvikshåndteringen", "count": 6}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "D500",
|
||||
"title": "Informasjonssikkerhet og kontinuitet",
|
||||
"scope": "Delen stiller krav til sikkerhetsstyring, tilgang, logging, beredskap og oppfølging av leverandører.",
|
||||
"sections": [
|
||||
{"nr": "1.1", "title": "Sikkerhetsstyring", "subject": "styringssystemet for informasjonssikkerhet", "count": 5},
|
||||
{"nr": "1.2", "title": "Risikovurdering", "subject": "risikovurderingen", "count": 5},
|
||||
{"nr": "2.1", "title": "Privilegerte kontoer", "subject": "de privilegerte kontoene", "count": 5},
|
||||
{"nr": "2.2", "title": "Tilgangsgjennomgang", "subject": "tilgangsgjennomgangen", "count": 4},
|
||||
{"nr": "3.1", "title": "Sikkerhetslogg", "subject": "sikkerhetsloggen", "count": 5},
|
||||
{"nr": "4.1", "title": "Beredskapsplan", "subject": "beredskapsplanen", "count": 5},
|
||||
{"nr": "4.2", "title": "Gjenopprettingstid", "subject": "gjenopprettingstiden for kritiske tjenester", "count": 5},
|
||||
{"nr": "5.1", "title": "Leverandøroppfølging", "subject": "leverandøroppfølgingen", "count": 4}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"prosesskatalog": {
|
||||
"name": "prosesskatalog-2027",
|
||||
"bundle_id": "eksempel-prosesskatalog-2027",
|
||||
"catalogue": "P900",
|
||||
"catalogue_title": "P900 Prosesskatalogen",
|
||||
"edition": "P900 Prosesskatalogen:2027",
|
||||
"source_root": "https://example.invalid/eksempelvirksomheten/prosesskatalog",
|
||||
"scope": "Prosesskatalogen beskriver hva hver prosess i en driftsleveranse omfatter, slik at en avtale kan beskrives og gjøres opp prosess for prosess. Katalogen bærer ingen priser.",
|
||||
"default_omfang": "Omfatter {obj} i avtaleperioden, med planlegging, gjennomføring, kontroll og dokumentasjon.",
|
||||
"default_subs": [
|
||||
{"title": "Etablering av {obj}", "omfang": "Omfatter etablering av {obj} fram til driftsklar tilstand, med konfigurasjon, test og overlevering til drift."},
|
||||
{"title": "Drift av {obj}", "omfang": "Omfatter drift av {obj} i avtaleperioden. Arbeid som ikke inngår i egne prosesser, forutsettes inkludert i enhetsprisene."},
|
||||
{"title": "Avvikling av {obj}", "omfang": "Omfatter avvikling av {obj}, med sikker sletting av data og tilbakelevering av utstyr."}
|
||||
],
|
||||
"default_subs_for_first": 2,
|
||||
"main": [
|
||||
{
|
||||
"nr": "1",
|
||||
"title": "Forberedende tiltak og generelle kostnader",
|
||||
"groups": [
|
||||
{"nr": "11", "title": "PROSJEKTLEDELSE OG ADMINISTRASJON", "processes": [
|
||||
{"title": "Prosjektledelse"}, {"title": "Leveransedokumentasjon"}, {"title": "Rapportering"}]},
|
||||
{"nr": "12", "title": "DRIFTSMILJØ OG GENERELLE DRIFTSKOSTNADER", "processes": [
|
||||
{
|
||||
"title": "Midlertidig driftsmiljø",
|
||||
"omfang": "Omfatter etablering, drift og avvikling av midlertidig driftsmiljø i overgangsperioden, med servere, lagring og nettverk som trengs mens tjenestene flyttes. Omfatter også administrasjon av driftsmiljøet i den grad dette ikke inngår i egne prosesser eller er inkludert i enhetsprisene.",
|
||||
"subs": [
|
||||
{"title": "Etablering av midlertidig driftsmiljø", "omfang": "Omfatter oppsett av servere, lagring, nettverk og tilganger for det midlertidige driftsmiljøet fram til driftsklar tilstand."},
|
||||
{"title": "Drift av midlertidig driftsmiljø", "omfang": "Omfatter overvåking, oppdatering, sikkerhetskopi og brukerstøtte for det midlertidige driftsmiljøet så lenge det er i bruk. Prosessen gjøres opp per måned."},
|
||||
{"title": "Avvikling av midlertidig driftsmiljø", "omfang": "Omfatter nedstenging av det midlertidige driftsmiljøet, sikker sletting av data og tilbakelevering av utstyr."}
|
||||
]
|
||||
},
|
||||
{"title": "Testmiljø"}, {"title": "Opplæringsmiljø"}]},
|
||||
{"nr": "13", "title": "KARTLEGGING", "processes": [
|
||||
{"title": "Kartlegging av tjenester"}, {"title": "Kartlegging av avhengigheter"}, {"title": "Kartlegging av lisenser"}]},
|
||||
{"nr": "14", "title": "SIKKERHET I PROSJEKTPERIODEN", "processes": [
|
||||
{"title": "Tilgangsstyring i prosjektet"}, {"title": "Sikkerhetsvurdering"}, {"title": "Beredskap i prosjektperioden"}]},
|
||||
{"nr": "15", "title": "KVALITETSSIKRING", "processes": [
|
||||
{"title": "Kvalitetsplan"}, {"title": "Revisjon"}, {"title": "Sluttkontroll"}]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nr": "2",
|
||||
"title": "Migrering og dataflytting",
|
||||
"groups": [
|
||||
{"nr": "21", "title": "FORBEREDELSE AV MIGRERING", "processes": [
|
||||
{"title": "Migreringsplan"}, {"title": "Prøvemigrering"}, {"title": "Datavask"}]},
|
||||
{"nr": "22", "title": "UTTAK OG FLYTTING", "processes": [
|
||||
{
|
||||
"title": "Uttak av maskinvare fra eksisterende miljø",
|
||||
"omfang": "Omfatter demontering og uttak av servere, disker og nettverksutstyr fra eksisterende miljø, med sikker sletting av data før utstyret flyttes. Utstyr som skal gjenbrukes, skal testes og merkes ved uttak.",
|
||||
"subs": []
|
||||
},
|
||||
{"title": "Flytting av data"}, {"title": "Flytting av tjenester"}]},
|
||||
{"nr": "23", "title": "VERIFISERING", "processes": [
|
||||
{"title": "Datakontroll"}, {"title": "Funksjonstest"}, {"title": "Ytelsestest"}]},
|
||||
{"nr": "24", "title": "PARALLELLDRIFT", "processes": [
|
||||
{"title": "Parallelldrift"}, {"title": "Tilbakefallsplan"}, {"title": "Avslutning av parallelldrift"}]},
|
||||
{"nr": "25", "title": "AVVIKLING AV GAMMELT MILJØ", "processes": [
|
||||
{"title": "Sletting av data"}, {"title": "Tilbakelevering av utstyr"}, {"title": "Gjenvinning av utstyr"}]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nr": "3",
|
||||
"title": "Nettverk og kabling",
|
||||
"groups": [
|
||||
{"nr": "31", "title": "KJERNENETT", "processes": [
|
||||
{"title": "Kjernesvitsjer"}, {"title": "Ruting"}, {"title": "Nettverkssegmentering"}]},
|
||||
{"nr": "32", "title": "KABLING", "processes": [
|
||||
{"title": "Horisontal kabling"}, {"title": "Fiberkabling"}, {"title": "Patching"}]},
|
||||
{"nr": "33", "title": "TRÅDLØST NETT", "processes": [
|
||||
{"title": "Tilgangspunkter"}, {"title": "Dekningsmåling"}, {"title": "Gjestenett"}]},
|
||||
{"nr": "34", "title": "FJERNTILGANG", "processes": [
|
||||
{"title": "Fjerntilgangsløsning"}, {"title": "Tofaktorpålogging"}, {"title": "Tilgangslogging"}]},
|
||||
{"nr": "35", "title": "NETTVERKSOVERVÅKING", "processes": [
|
||||
{"title": "Trafikkovervåking"}, {"title": "Kapasitetsrapportering"}, {"title": "Feilsøking"}]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nr": "4",
|
||||
"title": "Klientdrift og brukerstøtte",
|
||||
"groups": [
|
||||
{"nr": "41", "title": "KLIENTER", "processes": [
|
||||
{"title": "Utrulling av klienter"}, {"title": "Klientoppdateringer"}, {"title": "Innsamling av klienter"}]},
|
||||
{"nr": "42", "title": "PROGRAMVARE", "processes": [
|
||||
{"title": "Programvarepakking"}, {"title": "Lisensforvaltning"}, {"title": "Programvaredistribusjon"}]},
|
||||
{"nr": "43", "title": "BRUKERSTØTTE", "processes": [
|
||||
{"title": "Tjenestedisk"}, {"title": "Feltstøtte"}, {"title": "Opplæring av brukere"}]},
|
||||
{"nr": "44", "title": "MØTEROM", "processes": [
|
||||
{"title": "Møteromsutstyr"}, {"title": "Videokonferanse"}, {"title": "Romstyring"}]},
|
||||
{"nr": "45", "title": "UTSKRIFT", "processes": [
|
||||
{"title": "Skrivere"}, {"title": "Utskriftskø"}, {"title": "Forbruksmateriell"}]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nr": "5",
|
||||
"title": "Plattform og infrastruktur",
|
||||
"groups": [
|
||||
{"nr": "51", "title": "PLATTFORMGRUNNLAG", "processes": [
|
||||
{
|
||||
"title": "Standardisering av plattform",
|
||||
"omfang": "Omfatter samordning av eksisterende servere til én felles plattformversjon, med oppgradering av operativsystem og fastvare, i stedet for utskifting av maskinvaren. Maskinvare som ikke kan standardiseres, skal navngis og behandles i egen prosess.",
|
||||
"subs": []
|
||||
},
|
||||
{"title": "Virtualisering"}, {"title": "Containerplattform"}]},
|
||||
{"nr": "52", "title": "LAGRINGSLAG OG DISKER", "processes": [
|
||||
{
|
||||
"title": "Lagringslag",
|
||||
"omfang": "Omfatter levering, montering og konfigurasjon av lagringslag for virtuelle maskiner og databaser. Lagringslaget skal ha dokumentert ytelse og redundans før det tas i bruk.",
|
||||
"subs": [
|
||||
{"title": "Lagringslag av nye diskhyller", "omfang": "Omfatter levering og montering av nye diskhyller med disker til lagringslaget. Disker skal være iht. produsentens spesifikasjon, avsnitt 1.10.4."},
|
||||
{"title": "Lagringslag av gjenbrukt maskinvare", "omfang": "Omfatter oppsett av lagringslag av disker og diskhyller tatt ut av eksisterende miljø. Gjenbrukt utstyr skal være testet, slettet og merket ved uttak, og ytelsen skal dokumenteres før bruk."},
|
||||
{"title": "Drift av lagringslag", "omfang": "Omfatter drift av lagringslaget i avtaleperioden, med kapasitetsoppfølging og utskifting av feilede disker."}
|
||||
]
|
||||
},
|
||||
{"title": "Diskutskifting"}, {"title": "Kapasitetsutvidelse"}]},
|
||||
{"nr": "53", "title": "SERVERROM", "processes": [
|
||||
{"title": "Rackskap"}, {"title": "Strømfordeling"}, {"title": "Kjøling av serverrom"}]},
|
||||
{"nr": "54", "title": "RESERVEKRAFT", "processes": [
|
||||
{"title": "UPS-anlegg"}, {"title": "Nødstrømsaggregat"}, {"title": "Omkoblingstest"}]},
|
||||
{"nr": "55", "title": "OVERVÅKING", "processes": [
|
||||
{"title": "Overvåkingsplattform"}, {"title": "Alarmmottak"}, {"title": "Hendelseslogg"}]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nr": "6",
|
||||
"title": "Lagringssystemer og arkiv",
|
||||
"groups": [
|
||||
{"nr": "61", "title": "ARKIVTJENESTER", "processes": [
|
||||
{"title": "Arkivlagring"}, {"title": "Arkivuttrekk"}, {"title": "Arkivkontroll"}]},
|
||||
{"nr": "62", "title": "SIKKERHETSKOPI", "processes": [
|
||||
{"title": "Kopiering"}, {"title": "Kopi utenfor hovedlokasjonen"}, {"title": "Kopikontroll"}]},
|
||||
{"nr": "63", "title": "GJENOPPRETTING", "processes": [
|
||||
{"title": "Gjenopprettingstest"}, {"title": "Gjenopprettingsplan"}, {"title": "Fullskala gjenoppretting"}]},
|
||||
{"nr": "64", "title": "DATASLETTING", "processes": [
|
||||
{"title": "Sletterutiner"}, {"title": "Sletting av lagringsmedier"}, {"title": "Slettebevis"}]},
|
||||
{"nr": "65", "title": "LAGRINGSSYSTEMER", "processes": [
|
||||
{"title": "Blokklagring"}, {"title": "Fillagring"}, {"title": "Objektlagring"}]}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue