Change from format tp contentType

draft_specifications
meerkat 2021-11-28 23:27:57 +11:00
parent 2428dcbf58
commit 28168c5fc1
69 changed files with 1939 additions and 250 deletions

1
.gitignore vendored
View File

@ -27,3 +27,4 @@ source/golang/client/src/test
source/golang/client/src/martilq_client*
source/golang/client/src/*.exe
source/golang/server/main

View File

@ -105,7 +105,7 @@ sample can be generated using the GOLANG client program with parameters:
```json
{
"content-type": "application/vnd.martilq.json",
"contentType": "application/vnd.martilq.json",
"title": "GEN001",
"uid": "9a0a7edb-dd81-4fc5-a6cb-c5716eda7b51",
"description": "Simple example",
@ -140,7 +140,7 @@ sample can be generated using the GOLANG client program with parameters:
"description": "",
"url": "http://localhost/martilq/martilq.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",

View File

@ -1,5 +1,5 @@
{
"content-type": "application/vnd.martilq.json",
"contentType": "application/vnd.martilq.json",
"title": "SampleFetchBSB",
"uid": "08e22a4d-5a11-4b19-9172-feb36601009e",
"description": "",
@ -33,7 +33,7 @@
"description": "",
"url": "ftp://bsb.hostedftp.com/~auspaynetftp/BSB/BSBDirectoryAug21-305.csv",
"version": "",
"content-type": "text/csv",
"contentType": "text/csv",
"compression": null,
"encryption": null,
"attributes": [
@ -98,7 +98,7 @@
"description": "",
"url": "ftp://bsb.hostedftp.com/~auspaynetftp/BSB/BSBDirectoryAug21-305.txt",
"version": "",
"content-type": "text/plain",
"contentType": "text/plain",
"compression": null,
"encryption": null,
"attributes": [
@ -163,7 +163,7 @@
"description": "",
"url": "ftp://bsb.hostedftp.com/~auspaynetftp/BSB/BSBDirectoryJul21-304.csv",
"version": "",
"content-type": "text/csv",
"contentType": "text/csv",
"compression": null,
"encryption": null,
"attributes": [
@ -228,7 +228,7 @@
"description": "",
"url": "ftp://bsb.hostedftp.com/~auspaynetftp/BSB/BSBDirectoryJul21-304.txt",
"version": "",
"content-type": "text/plain",
"contentType": "text/plain",
"compression": null,
"encryption": null,
"attributes": [
@ -293,7 +293,7 @@
"description": "",
"url": "ftp://bsb.hostedftp.com/~auspaynetftp/BSB/BSBDirectorySep21-306.csv",
"version": "",
"content-type": "text/csv",
"contentType": "text/csv",
"compression": null,
"encryption": null,
"attributes": [
@ -358,7 +358,7 @@
"description": "",
"url": "ftp://bsb.hostedftp.com/~auspaynetftp/BSB/BSBDirectorySep21-306.txt",
"version": "",
"content-type": "text/plain",
"contentType": "text/plain",
"compression": null,
"encryption": null,
"attributes": [

View File

@ -1,5 +1,5 @@
{
"content-type": "application/vnd.martilq.json",
"contentType": "application/vnd.martilq.json",
"title": "SampleFetchBSB",
"uid": "08e22a4d-5a11-4b19-9172-feb36601009e",
"description": "",
@ -33,7 +33,7 @@
"description": "",
"url": "http://apnedata.merebox.com.s3.ap-southeast-2.amazonaws.com/au/bsb/BSBDirectoryAug21-305.csv",
"version": "",
"content-type": "text/csv",
"contentType": "text/csv",
"compression": null,
"encryption": null,
"attributes": [
@ -98,7 +98,7 @@
"description": "",
"url": "http://apnedata.merebox.com.s3.ap-southeast-2.amazonaws.com/au/bsb/BSBDirectoryJul21-304.csv",
"version": "",
"content-type": "text/csv",
"contentType": "text/csv",
"compression": null,
"encryption": null,
"attributes": [
@ -163,7 +163,7 @@
"description": "",
"url": "http://apnedata.merebox.com.s3.ap-southeast-2.amazonaws.com/au/bsb/BSBDirectorySep21-306.csv",
"version": "",
"content-type": "text/csv",
"contentType": "text/csv",
"compression": null,
"encryption": null,
"attributes": [

View File

@ -14,7 +14,7 @@ The **martiLQ** document
```json
{
"content-type": "application/vnd.martilq.json",
"contentType": "application/vnd.martilq.json",
"title": "GEN001",
"uid": "cff090b2-5845-490c-bd81-88b3a9267b5a",
"description": "Simple example with no config",
@ -49,7 +49,7 @@ The **martiLQ** document
"description": "",
"url": "file://martiLQ.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",

View File

@ -41,7 +41,7 @@ The **martiLQ** document
```json
{
"content-type": "application/vnd.martilq.json",
"contentType": "application/vnd.martilq.json",
"title": "GEN002",
"uid": "42532ae8-4d42-4875-b7c8-bcb7a5c38eaa",
"description": "Simple example",
@ -79,7 +79,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/martiLQ.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",

View File

@ -16,7 +16,7 @@ The **martiLQ** document
```json
{
"content-type": "application/vnd.martilq.json",
"contentType": "application/vnd.martilq.json",
"title": "GEN002",
"uid": "a4cb9d10-4d87-455b-87ec-64003147b0cd",
"description": "Directory example",
@ -54,7 +54,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/README.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -87,7 +87,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/attributes.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -120,7 +120,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/ckan.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -153,7 +153,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/comparison.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -186,7 +186,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/custom.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -219,7 +219,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/magda.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -252,7 +252,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/martiLQ.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -285,7 +285,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/quality.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -318,7 +318,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/references.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -351,7 +351,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/resources.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -384,7 +384,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/what.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -417,7 +417,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/when.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -450,7 +450,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/who.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -483,7 +483,7 @@ The **martiLQ** document
"description": "",
"url": "https://github.com/meerkat-manor/marti/blob/draft_specifications/docs/source/why.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",

View File

@ -19,7 +19,7 @@ The **martiLQ** document
```json
{
"content-type": "application/vnd.martilq.json",
"contentType": "application/vnd.martilq.json",
"title": "GEN004",
"uid": "207096a1-aac1-4b66-9748-707f07b63691",
"description": "Directory example with filter",
@ -54,7 +54,7 @@ The **martiLQ** document
"description": "",
"url": "file://README.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -87,7 +87,7 @@ The **martiLQ** document
"description": "",
"url": "file://references.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -120,7 +120,7 @@ The **martiLQ** document
"description": "",
"url": "file://resources.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -153,7 +153,7 @@ The **martiLQ** document
"description": "",
"url": "file://samples/README.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
@ -186,7 +186,7 @@ The **martiLQ** document
"description": "",
"url": "file://samples/json/README.md",
"version": "",
"content-type": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",

View File

@ -76,18 +76,18 @@ for file_name in files:
oResource = mlq.NewMartiLQResource(os.path.join(test_dir, file_name), "", False, True)
oMarti["resources"].append(oResource)
mlq.CloseLog()
mlq.Close()
print("Save martiLQ definition")
jsonFile = open(os.path.join(test_dir, "BSBDirectoryPlain.json"), "w")
jsonFile = open(os.path.join(test_dir, "martilq_bsb.json"), "w")
jsonFile.write(json.dumps(oMarti, indent=5))
jsonFile.close()
print("Base sample JSON written: BSBDirectoryPlain.json")
print("Base sample JSON written: martilq_bsb.json")
print("Creating martiLQ ZIP file")
zipFileName = "BSBDirectory.zip"
zipFileName = "martilq_bsb.zip"
fileZipCount = 0
mlq = martiLQ()
@ -108,24 +108,26 @@ with zipfile.ZipFile(os.path.join(test_dir, zipFileName), "w", compression=zipfi
oResource = mlq.NewMartiLQResource(os.path.join(test_dir, zipFileName), "", False, True)
oResource["url"] = os.path.join(test_dir, zipFileName)
mlq.SetAttributeValueString(Attributes=oResource["attributes"], Key="compression", Category="format", Function="algo", Value="WINZIP")
mlq.SetAttributeValueNumber(Attributes=oResource["attributes"], Key="files", Category="dataset", Function="count", Value=fileZipCount)
oResource["url"] = test_dir + "/" + zipFileName
mResource.SetAttributeValueString(oResource, Key="compression", Category="format", Function="algo", Value="WINZIP")
mResource.SetAttributeValueNumber(oResource, Key="files", Category="dataset", Function="count", Value=fileZipCount)
oMarti["resources"].append(oResource)
mlq.CloseLog()
mlq.Close()
print("Save martiLQ ZIP definition")
jsonFile = open(os.path.join(test_dir, "MartiLQ_BSBZip.json"), "w")
jsonFile = open(os.path.join(test_dir, "martilq_bsb_zip.json"), "w")
jsonFile.write(json.dumps(oMarti, indent=5))
jsonFile.close()
print("ZIP sample JSON written: MartiLQ_BSBZip.json")
print("ZIP sample JSON written: martilq_bsb_zip.json")
print("Sample completed: SampleGenerateBsb.py")
print("Sample completed: SampleGenerateFtpBsb.py")
lqresults, testError = mlq.TestMartiDefinition(os.path.join(test_dir, "BSBDirectoryPlain.json"))
lqresults, testError = mlq.TestMartiDefinition(os.path.join(test_dir, "martilq_bsb.json"))
testfile = open(os.path.join(test_dir, "LoadQualityTest01.csv"), "w+", newline ="")
with testfile:

View File

@ -26,11 +26,13 @@ A web UI exists as a functionnal demonstration to view a collection of
## Docker
Please remember to copy documents into "docs" folder.
```
go env -w GOOS=linux
go env -w GOARCH=386
go build ./main.go
go build ./src/main.go
docker build -t martilq-go-server:latest .
```

View File

@ -22,7 +22,7 @@ import (
type Marti struct {
Content_type string `json:"content-type"`
Content_type string `json:"contentType"`
Title string `json:"title"`
Uid string `json:"uid"`

View File

@ -29,8 +29,9 @@ type Resource struct {
Description string `json:"description"`
Url string `json:"url"`
Structure string `json:"structure"`
Version string `json:"version"`
Content_Type string `json:"content-type"`
ContentType string `json:"contentType"`
Encoding string `json:"encoding"`
Compression string `json:"compression"`
Encryption string `json:"encryption"`
@ -112,7 +113,7 @@ func NewMartiLQResource(config configuration, sourcePath string, urlPath string,
parts := strings.Split(sourcePath,".")
extension := parts[len(parts)-1]
r.Content_Type = mime.TypeByExtension("."+extension)
r.ContentType = mime.TypeByExtension("."+extension)
records := 0
columns := -1

View File

@ -1,14 +1,15 @@
FROM busybox
LABEL version="202111A"
LABEL description="matiLQ Go web UI by Meerkat Manor "
LABEL version="202111B"
LABEL description="martiLQ Go web UI by Meerkat Manor "
LABEL authors="meerkat@merebox.com"
COPY ./static /home/static
COPY ./main /home/main
COPY ./docs /home/docs
COPY ./data /data
COPY ./src/static /martilq/static
COPY ./src/main /martilq/main
COPY ./src/docs /martilq/docs
COPY ./src/template /martilq/template
COPY ./src/data /martilq/data
EXPOSE 8080
ENTRYPOINT exec /home/main -p 8080 -s /home/static -data /data/ -docs /home/docs
ENTRYPOINT exec /martilq/main -p 8080 -static /martilq/static -data /martilq/data/ -docs /martilq/docs -template /martilq/template

View File

@ -5,11 +5,11 @@ services:
web:
hostname: martilq-web
image: martilq-go-server:latest
container_name: matilq-web
container_name: martilq-web
ports:
- 8080:8080
volumes:
- ./data:/home/data
- ../../../docs:/home/docs
- ./src/data:/home/data
- ./src/docs:/home/docs

View File

@ -1,96 +0,0 @@
package main
import (
"flag"
"log"
"net/http"
"strings"
"path/filepath"
"os"
)
func main() {
port := flag.String("p", "8080", "Http listen port")
staticDirectory := flag.String("s", "static", "Static directory content")
docsDirectory := flag.String("docs", "", "Docs directory content")
dataDirectory := flag.String("data", "", "Data directory content")
trace := flag.Bool("trace", false, "Produce trace logs")
flag.Parse()
if *trace == true {
log.Printf("static folder: %s\n", *staticDirectory)
log.Printf("data folder: %s\n", *dataDirectory)
log.Printf("docs folder: %s\n", *docsDirectory)
}
http.HandleFunc("/data/", func( res http.ResponseWriter, req *http.Request ) {
safePath := ValidatePath(filepath.FromSlash(req.URL.Path[1:]))
if (*dataDirectory != "") {
safePath = filepath.FromSlash(filepath.Join(*dataDirectory, strings.Replace(safePath, "data/", "", 1)))
}
http.ServeFile(res, req, safePath)
})
http.HandleFunc("/docs/", func( res http.ResponseWriter, req *http.Request ) {
localPath := ""
if (*docsDirectory == "") {
temp := "../../.."
docsDirectory = &temp
localPath = ValidatePath(filepath.FromSlash(*docsDirectory+req.URL.Path))
} else {
localPath = ValidatePath(filepath.FromSlash(*docsDirectory+strings.Replace(req.URL.Path, "docs/", "", 1)))
}
if *trace == true {
log.Printf("fetch docs: \"%s\"", localPath)
}
f, err := os.Open(localPath)
if err != nil {
log.Printf("fetch docs error: \"%s\" with %s", localPath, err)
http.ServeFile(res, req, filepath.FromSlash(*staticDirectory + "/404.html"));
} else {
s, err := f.Stat()
if err != nil || s.IsDir() {
log.Printf("fetch docs stat error: \"%s\"", localPath)
http.ServeFile(res, req, filepath.FromSlash(*staticDirectory + "/404.html"))
} else {
http.ServeFile(res, req, localPath)
}
}
})
fileServer := http.FileServer(FileSystem{http.Dir(*staticDirectory)})
http.Handle("/", fileServer)
log.Printf("Serving on HTTP port: %s\n", *port)
log.Fatal(http.ListenAndServe(":"+*port, nil))
}
type FileSystem struct {
fs http.FileSystem
}
func (fs FileSystem) Open(path string) (http.File, error) {
f, err := fs.fs.Open(path)
if err != nil {
return nil, err
}
s, err := f.Stat()
if s.IsDir() {
index := strings.TrimSuffix(path, "/") + "/index.html"
if _, err := fs.fs.Open(index); err != nil {
return nil, err
}
}
return f, nil
}
func ValidatePath(path string) string {
safePath := path
return safePath
}

View File

@ -0,0 +1,171 @@
{
"id": null,
"describe": "BSB file metadata details. The file is a CSV (RFC4180) formatted file.",
"encoding": "UTF-8",
"format": "RFC4180",
"multiRecord": false,
"lineEndings": "CRLF",
"header": false,
"footer": false,
"delimiter": ",",
"quote": "\"",
"escape": "\"",
"source": null,
"recordFormat": [
{
"formatName" : "Data",
"columns": [
{
"columnName": "BSB",
"description": "Bank State Branch (BSB) code that is unique",
"type": "char",
"length": 7,
"nullable": false,
"default": null,
"dataset": "",
"formatting": "###-###",
"labels": {
"leftLabel": "BSB",
"rightLabel": "",
"heading": "BSB"
}
},
{
"columnName": "Bank",
"description": "Mnemonic for the bank",
"type": "varchar",
"length": 3,
"nullable": false,
"default": null,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "Bank",
"rightLabel": "",
"heading": "Bank"
}
},
{
"columnName": "Branch",
"description": "Branch name and in some cases describes the type of business it conducts",
"type": "varchar",
"length": 35,
"nullable": false,
"default": null,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "Branch",
"rightLabel": "",
"heading": ""
}
},
{
"columnName": "StreetAddress",
"description": "Street address for the bank branch. This includes the level, street number and street type",
"type": "varchar",
"length": 35,
"nullable": false,
"default": null,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "Address",
"rightLabel": "",
"heading": "Street Address"
}
},
{
"columnName": "Town",
"description": "Town where the branch is located",
"type": "varchar",
"length": 20,
"nullable": false,
"default": null,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "Town/City",
"rightLabel": "",
"heading": "Town/City"
}
},
{
"columnName": "State",
"description": "Australian state or terrritory where the branch is located",
"type": "varchar",
"length": 3,
"nullable": false,
"default": null,
"dataset": "NSW, VIC, QLD, SA, NT, WA, TAS, ACT",
"formatting": "",
"labels": {
"leftLabel": "State",
"rightLabel": "",
"heading": "State"
}
},
{
"columnName": "Postcode",
"description": "Australian postcode for the bank branch",
"type": "int",
"length": 4,
"nullable": false,
"default": null,
"dataset": "",
"formatting": "####",
"labels": {
"leftLabel": "Postcode",
"rightLabel": "",
"heading": "PostCode"
}
},
{
"columnName": "Payments",
"description": "Payments flag. The value is a concatenation of P=Paper Payments, E=Electronic Payments, H=High Value Payments",
"type": "varchar",
"length": 3,
"nullable": false,
"default": null,
"dataset": "P, E, H, PE, PH, EH, PEH",
"formatting": "",
"labels": {
"leftLabel": "Payment Types",
"rightLabel": "",
"heading": ""
}
}
]
}
],
"indexes": [
{
"indexName": "BSB",
"primary": true,
"unique": true,
"columns": [
{"name": "BSB"}
]
}
],
"sample": [
{"record": "\"012-028\",\"ANZ\",\"Banking Products NSW Phone\",\"Level 7 75 Dorcas Street\",\"South Melbourne\",\"VIC\",\"3205\",\"PE\""},
{"record": "\"033-062\",\"WBC\",\"Moonee Ponds\",\"71 Puckle Street\",\"Moonee Ponds\",\"VIC\",\"3039\",\"PEH\""},
{"record": "\"062-731\",\"CBA\",\"Narromine\",\"65 Dandaloo St Cnr Nymagee Street\",\"Narromine\",\"NSW\",\"2821\",\"PEH\""}
],
"custom": [
{
"extension": "software",
"softwareName": "MartiReference",
"author": "Meerkat@merebox.com",
"version": "0.0.1"
},
{
"extension": "template",
"renderer": "MARTILQREFERENCE:Mustache",
"url": "template/details/martilq_cols_csv.must"
}
]
}

View File

@ -0,0 +1,311 @@
{
"id": null,
"describe": "BSB file metadata details. The file is a legacy fixed column position file. The record length is not fixed as the last column can be stripped of blanks. The length is 112 to 113 bytes. Position are 1 index based",
"encoding": "UTF-8",
"format": "Fixed Column Position (FCP)",
"multiRecord": true,
"lineEndings": "CRLF",
"header": true,
"footer": true,
"source": null,
"recordFormat": [
{
"formatName" : "Header",
"columns": [
{
"columnName": "TXT1",
"description": "Fixed identifier of \"HEADER RECORD Effective Date: \"",
"type": "char",
"starting": 1,
"ending": 31,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "TXT1",
"rightLabel": "",
"heading": "TXT1"
}
},
{
"columnName": "EffectiveDate",
"description": "Formatted effective date",
"type": "char",
"starting": 32,
"ending": 42,
"dataset": "",
"formatting": "DD mmm YYYY",
"labels": {
"leftLabel": "EffectiveDate",
"rightLabel": "",
"heading": "EffectiveDate"
}
},
{
"columnName": "TXT2",
"description": "Fixed identifier of \" File Created At: \"",
"type": "char",
"starting": 43,
"ending": 64,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "TXT2",
"rightLabel": "",
"heading": "TXT2"
}
},
{
"columnName": "RunDate",
"description": "Formatted run date",
"type": "char",
"starting": 65,
"ending": 84,
"dataset": "",
"formatting": "DD mmm YYYY HH:mm:ss",
"labels": {
"leftLabel": "EffectiveDate",
"rightLabel": "",
"heading": "EffectiveDate"
}
},
{
"columnName": "TXT3",
"description": "Fixed identifier of \" Report Number: \"",
"type": "char",
"starting": 85,
"ending": 103,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "TXT3",
"rightLabel": "",
"heading": "TXT3"
}
},
{
"columnName": "ReportNumber",
"description": "Report sequential number",
"type": "int",
"starting": 104,
"ending": 105,
"dataset": "",
"formatting": "###",
"labels": {
"leftLabel": "Report#",
"rightLabel": "",
"heading": "Report#"
}
},
{
"columnName": "TXT4",
"description": "Trailing blanks for padding",
"type": "char",
"starting": 106,
"ending": 111,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "TXT4",
"rightLabel": "",
"heading": "TXT4"
}
}
]
},
{
"formatName" : "Data",
"columns": [
{
"columnName": "BSB",
"description": "Bank State Branch (BSB) code that is unique",
"type": "char",
"starting": 1,
"ending": 7,
"dataset": "",
"formatting": "###-###",
"labels": {
"leftLabel": "BSB",
"rightLabel": "",
"heading": "BSB"
}
},
{
"columnName": "Bank",
"description": "Mnemonic for the bank",
"type": "varchar",
"starting": 8,
"ending": 10,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "Bank",
"rightLabel": "",
"heading": "Bank"
}
},
{
"columnName": "Branch",
"description": "Branch name and in some cases describes the type of business it conducts",
"type": "varchar",
"starting": 11,
"ending": 45,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "Branch",
"rightLabel": "",
"heading": ""
}
},
{
"columnName": "StreetAddress",
"description": "Street address for the bank branch. This includes the level, street number and street type",
"type": "varchar",
"starting": 46,
"ending": 80,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "Address",
"rightLabel": "",
"heading": "Street Address"
}
},
{
"columnName": "Town",
"description": "Town where the branch is located",
"type": "varchar",
"starting": 81,
"ending": 100,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "Town/City",
"rightLabel": "",
"heading": "Town/City"
}
},
{
"columnName": "State",
"description": "Australian state or terrritory where the branch is located",
"type": "varchar",
"starting": 101,
"ending": 103,
"dataset": "NSW, VIC, QLD, SA, NT, WA, TAS, ACT",
"formatting": "",
"labels": {
"leftLabel": "State",
"rightLabel": "",
"heading": "State"
}
},
{
"columnName": "Postcode",
"description": "Australian postcode for the bank branch",
"type": "int",
"starting": 104,
"ending": 107,
"dataset": "",
"formatting": "####",
"labels": {
"leftLabel": "Postcode",
"rightLabel": "",
"heading": "PostCode"
}
},
{
"columnName": "Payments",
"description": "Payments flag. The value is a concatenation of P=Paper Payments, E=Electronic Payments, H=High Value Payments",
"type": "varchar",
"starting": 108,
"ending": 111,
"dataset": "P, E, H, PE, PH, EH, PEH",
"formatting": "",
"labels": {
"leftLabel": "Payment Types",
"rightLabel": "",
"heading": ""
}
}
]
},
{
"formatName" : "Footer",
"columns": [
{
"columnName": "TXT5",
"description": "Fixed string \"TRAILER RECORD Total number of records (except header and trailer): \"",
"type": "char",
"starting": 1,
"ending": 72,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "TXT5",
"rightLabel": "",
"heading": "TXT5"
}
},
{
"columnName": "RecordCount",
"description": "Number of data records in the file",
"type": "int",
"starting": 73,
"ending": 78,
"dataset": "",
"formatting": "##,###",
"labels": {
"leftLabel": "Report#",
"rightLabel": "",
"heading": "Report#"
}
},
{
"columnName": "TXT6",
"description": "Trailing blanks for padding",
"type": "char",
"starting": 79,
"ending": 112,
"dataset": "",
"formatting": "",
"labels": {
"leftLabel": "TXT6",
"rightLabel": "",
"heading": "TXT6"
}
}
]
}
],
"indexes": [
{
"indexName": "BSB",
"primary": true,
"unique": true,
"columns": [
{"name": "BSB"}
]
}
],
"sample": [
{"record": "HEADER RECORD Effective Date: 01 Sep 2021 File Created At: 01 Sep 2021 08:58:50 Report Number: 305 "},
{"record": "012-298ANZFrenchs Forest Shop 5, Forestway Shopping Centre Frenchs Forest NSW2086PEH "},
{"record": "033-652WBCSwan Hill 169-171 Campbell Street Swan Hill VIC3585PEH "},
{"record": "932-000RABRegional Australia Bank Suite 4 Technology Park MadgwickDr Armidale NSW2350PEH "},
{"record": "TRAILER RECORD Total number of records (except header and trailer): 15,055 "}
],
"custom": [
{
"extension": "software",
"softwareName": "MartiReference",
"author": "Meerkat@merebox.com",
"version": "0.0.1"
},
{
"extension": "template",
"renderer": "MARTILQREFERENCE:Mustache",
"url": "template/details/martilq_cols_fcp.must"
}
]
}

View File

@ -1,5 +1,5 @@
{
"content-type": "application/vnd.martilq.json",
"contentType": "application/vnd.martilq.json",
"title": "Conversion from CKAN",
"uid": "ab7eddce-84df-4098-bc8f-500d0d9776d1",
"description": "This data has been converted from DATA GOV AU CKAN data source with URL 'https://data.gov.au/api/3/action/package_show?id=ab7eddce-84df-4098-bc8f-500d0d9776d1'",
@ -139,8 +139,8 @@
},
{
"extension": "template",
"renderer": "MartiReference:Mustache",
"url": "data/martilq_ckan.must"
"renderer": "MARTILQREFERENCE:Mustache",
"url": "template/martilq_ckan.must"
}
]
}

View File

@ -1,5 +1,5 @@
{
"content-type": "application/vnd.martilq.json",
"contentType": "application/vnd.martilq.json",
"title": "Conversion from CKAN",
"uid": "f2b7c2c1-f4ef-4ae9-aba5-45c19e4d3038",
"description": "###Update November 2019 - additional fields ###\r\n\r\nFrom 21 November 2019, the dataset will be updated to include 7 new fields (see help file for details)\r\n\r\nThese fields are included in conjunction with the professional standards reforms for financial advisers. More information can be found on the ASIC website https://asic.gov.au/regulatory-resources/financial-services/professional-standards-for-financial-advisers-reforms/.\r\n\r\n__Note:__ For most advisers the new fields will be unpopulated on 21 November 2019. As advisers provide this data to ASIC it will appear in the dataset.\r\n\r\n***\r\n\r\n###Dataset summary###\r\n\r\nASIC is Australias corporate, markets and financial services regulator. ASIC contributes to Australias economic reputation and wellbeing by ensuring that Australias financial markets are fair and transparent, supported by confident and informed investors and consumers. \r\n \r\nAustralian Financial Services Licensees are required to keep the details of their financial advisers up to date on ASIC's Financial Advisers Register. Information contained in the register is made available to the public to search via ASIC's Moneysmart website. \r\n\r\nSelect data from the Financial Advisers Register will be uploaded each week to www.data.gov.au. The data made available will be a snapshot of the register at a point in time. Legislation prescribes the type of information ASIC is allowed to disclose to the public. \r\n\r\nThe information included in the downloadable dataset is: \r\n\r\n* Adviser name\r\n* Adviser number\r\n* Adviser role\r\n* Adviser sub type\r\n* Adviser role status\r\n* Adviser ABN\r\n* Year first provided advice \r\n* Licence name\r\n* Licence number\r\n* Licence ABN\r\n* Licence controlled by\r\n* Adviser start date\r\n* Adviser end date\r\n* Adviser CPD failure year\r\n* Adviser principal business address suburb\r\n* Adviser principal business address State/Territory\r\n* Adviser principal business address postcode\r\n* Adviser principal business address Country\r\n* Appointing representative name\r\n* Appointing representative number\r\n* Appointing representative ABN\r\n* Disciplinary action start date\r\n* Disciplinary action end date\r\n* Disciplinary action type\r\n* Product authorisations (for a full list see the Financial Adviser Register Help File)\r\n* Qualifications and Training\r\n* FASEA approved qualifications\r\n* Memberships\r\n* Further restrictions\r\n\r\nAdditional information about financial advisers can be found via [ASIC's website] (http://www.asic.gov.au/ \"ASIC's website\"). Accessing some information may attract a fee. \r\n\r\nMore information about searching [ASIC's registers] (http://www.asic.gov.au/online-services/search-asics-registers/ \"ASIC's registers\"). ",
@ -94,8 +94,8 @@
},
{
"extension": "template",
"renderer": "MartiReference:Mustache",
"url": "data/martilq_ckan.must"
"renderer": "MARTILQREFERENCE:Mustache",
"url": "template/martilq_ckan.must"
}
]
}

View File

@ -1,14 +1,15 @@
{
"content-type": "application/vnd.martilq.json",
"contentType": "application/vnd.martilq.json",
"title": "Australian BSB source",
"uid": "ae5d4d4b-5a7f-494d-8629-a0ced5bc7ba0",
"description": "The original files are published on the APN FTP site and have been copied to a web server",
"issued": "2021-11-24",
"modified": "2021-11-05",
"publisher": "Australian Payment Network",
"contactPoint": "",
"contactPoint": "operations@auspaynet.com.au",
"accessLevel": "Confidential",
"rights": "Restricted",
"tags": [],
"tags": ["payment", "au", "bank"],
"license": "",
"state": "active",
"batch": 1.0,
@ -22,7 +23,7 @@
"documentName": "BSBDirectoryOct21-307.csv",
"issuedDate": "2021-11-05T20:58:03",
"modified": "2021-11-01T09:00:45",
"expires": "",
"expires": "2021-12-01T09:00:45",
"state": "active",
"author": "Meerkat@merebox.com",
"length": 1352205,
@ -32,9 +33,10 @@
"signed": false
},
"description": "",
"url": "",
"version": "",
"content-type": "text/csv",
"url": "http://apnedata.merebox.com.s3.ap-southeast-2.amazonaws.com/au/bsb/BSBDirectoryOct21-307.csv",
"structure": "/view.html?martilq=details/bsb_details_csv.json",
"version": "2012April",
"contentType": "text/csv",
"encoding": null,
"compression": null,
"encryption": null,
@ -83,14 +85,68 @@
}
]
},
{
"title": "BSBDirectoryOct21-307",
"uid": "0c2ee577-3fbb-46b9-bcc6-40eae069569c",
"documentName": "BSBDirectoryOct21-307.txt",
"issuedDate": "2021-11-05T20:58:03",
"modified": "2021-11-01T09:00:45",
"expires": "2021-12-01T09:00:45",
"state": "active",
"author": "Meerkat@merebox.com",
"length": 1352205,
"hash": {
"algo": "SHA256",
"value": "dd01585bed7a0b6da5d4deb5dff70a4b8ccfee873cf0454f2a46910c3275f03e",
"signed": false
},
"description": "",
"url": "http://apnedata.merebox.com.s3.ap-southeast-2.amazonaws.com/au/bsb/BSBDirectoryOct21-307.txt",
"structure": "/view.html?martilq=details/bsb_details_txt.json",
"version": "2012April",
"contentType": "text/txt",
"encoding": null,
"compression": null,
"encryption": null,
"attributes": [
{
"category": "dataset",
"name": "header",
"function": "count",
"comparison": "EQ",
"value": 1
},
{
"category": "dataset",
"name": "footer",
"function": "count",
"comparison": "EQ",
"value": 1
},
{
"category": "dataset",
"name": "records",
"function": "count",
"comparison": "EQ",
"value": 15054
},
{
"category": "dataset",
"name": "columns",
"function": "count",
"comparison": "EQ",
"value": 8
}
]
},
{
"title": "BSBDirectorySep21-306",
"uid": "900e815a-e052-4198-9d92-e0f1aac07616",
"documentName": "BSBDirectorySep21-306.csv",
"issuedDate": "2021-11-05T20:58:03",
"modified": "2021-10-01T09:00:48",
"expires": "",
"state": "active",
"expires": "2021-11-01T09:00:48",
"state": "expired",
"author": "Meerkat@merebox.com",
"length": 1351281,
"hash": {
@ -99,9 +155,10 @@
"signed": false
},
"description": "",
"url": "",
"version": "",
"content-type": "text/csv",
"url": "http://apnedata.merebox.com.s3.ap-southeast-2.amazonaws.com/au/bsb/BSBDirectorySep21-306.csv",
"structure": "/view.html?martilq=details/bsb_details_csv.json",
"version": "2012April",
"contentType": "text/csv",
"encoding": null,
"compression": null,
"encryption": null,
@ -156,8 +213,8 @@
"documentName": "BSBDirectoryAug21-305.csv",
"issuedDate": "2021-11-05T20:58:03",
"modified": "2021-09-01T09:00:43",
"expires": "",
"state": "active",
"expires": "2021-10-01T09:00:43",
"state": "expired",
"author": "Meerkat@merebox.com",
"length": 1352016,
"hash": {
@ -166,9 +223,10 @@
"signed": false
},
"description": "",
"url": "",
"version": "",
"content-type": "text/csv",
"url": "http://apnedata.merebox.com.s3.ap-southeast-2.amazonaws.com/au/bsb/BSBDirectoryAug21-305.csv",
"structure": "/view.html?martilq=details/bsb_details_csv.json",
"version": "2012April",
"contentType": "text/csv",
"encoding": null,
"compression": null,
"encryption": null,
@ -223,8 +281,8 @@
"documentName": "BSBDirectoryJul21-304.csv",
"issuedDate": "2021-11-05T20:58:03",
"modified": "2021-08-05T09:00:46",
"expires": "",
"state": "active",
"expires": "2021-09-05T09:00:46",
"state": "expired",
"author": "Meerkat@merebox.com",
"length": 1351065,
"hash": {
@ -233,9 +291,10 @@
"signed": false
},
"description": "",
"url": "",
"version": "",
"content-type": "text/csv",
"url": "http://apnedata.merebox.com.s3.ap-southeast-2.amazonaws.com/au/bsb/BSBDirectoryJul21-304.csv",
"structure": "/view.html?martilq=details/bsb_details_csv.json",
"version": "2012April",
"contentType": "text/csv",
"encoding": null,
"compression": null,
"encryption": null,
@ -290,8 +349,8 @@
"documentName": "BSBDirectoryJun21-303.csv",
"issuedDate": "2021-11-05T20:58:03",
"modified": "2021-07-01T09:00:48",
"expires": "",
"state": "active",
"expires": "2021-08-01T09:00:48",
"state": "expired",
"author": "Meerkat@merebox.com",
"length": 1350547,
"hash": {
@ -300,9 +359,10 @@
"signed": false
},
"description": "",
"url": "",
"version": "",
"content-type": "text/csv",
"url": "http://apnedata.merebox.com.s3.ap-southeast-2.amazonaws.com/au/bsb/BSBDirectoryJun21-303.csv",
"structure": "/view.html?martilq=details/bsb_details_csv.json",
"version": "2012April",
"contentType": "text/csv",
"encoding": null,
"compression": null,
"encryption": null,
@ -357,8 +417,8 @@
"documentName": "BSBDirectoryMay21-302.csv",
"issuedDate": "2021-11-05T20:58:03",
"modified": "2021-06-01T09:00:52",
"expires": "",
"state": "active",
"expires": "2021-07-01T09:00:52",
"state": "expired",
"author": "Meerkat@merebox.com",
"length": 1350255,
"hash": {
@ -367,9 +427,10 @@
"signed": false
},
"description": "",
"url": "",
"version": "",
"content-type": "text/csv",
"url": "http://apnedata.merebox.com.s3.ap-southeast-2.amazonaws.com/au/bsb/BSBDirectoryMay21-302.csv",
"structure": "/view.html?martilq=details/bsb_details_csv.json",
"version": "2012April",
"contentType": "text/csv",
"encoding": null,
"compression": null,
"encryption": null,
@ -424,8 +485,8 @@
"documentName": "BSBDirectoryApr21-301.csv",
"issuedDate": "2021-11-05T20:58:03",
"modified": "2021-05-03T09:00:51",
"expires": "",
"state": "active",
"expires": "2021-06-03T09:00:51",
"state": "expired",
"author": "Meerkat@merebox.com",
"length": 1352037,
"hash": {
@ -434,9 +495,10 @@
"signed": false
},
"description": "",
"url": "",
"version": "",
"content-type": "text/csv",
"url": "http://apnedata.merebox.com.s3.ap-southeast-2.amazonaws.com/au/bsb/BSBDirectoryApr21-301.csv",
"structure": "/view.html?martilq=details/bsb_details_csv.json",
"version": "2012April",
"contentType": "text/csv",
"encoding": null,
"compression": null,
"encryption": null,
@ -491,8 +553,8 @@
"documentName": "BSBDirectoryMar21-300.csv",
"issuedDate": "2021-11-05T20:58:03",
"modified": "2021-04-01T09:00:55",
"expires": "",
"state": "active",
"expires": "2021-05-01T09:00:55",
"state": "expired",
"author": "Meerkat@merebox.com",
"length": 1353104,
"hash": {
@ -501,9 +563,10 @@
"signed": false
},
"description": "",
"url": "",
"version": "",
"content-type": "text/csv",
"url": "http://apnedata.merebox.com.s3.ap-southeast-2.amazonaws.com/au/bsb/BSBDirectoryMar21-300.csv",
"structure": "/view.html?martilq=details/bsb_details_csv.json",
"version": "2012April",
"contentType": "text/csv",
"encoding": null,
"compression": null,
"encryption": null,
@ -559,6 +622,11 @@
"softwareName": "MARTILQREFERENCE",
"author": "Meerkat@merebox.com",
"version": "0.0.1"
},
{
"extension": "template",
"renderer": "MARTILQREFERENCE:Mustache",
"url": "template/martilq_default.must"
}
]
}

View File

@ -1,6 +1,6 @@
{
"content-type": "application/vnd.martilq.json",
"title": "Sample martiLQ generatd framework file",
"contentType": "application/vnd.martilq.json",
"title": "Sample martiLQ generated framework file",
"uid": "c525f5c5-ce87-4688-aa36-d0aa59d1e939",
"description": "Sample execution of martiLQ execution",
"issued": "2021-11-21T16:57:37",
@ -5564,6 +5564,11 @@
"softwareName": "MartiReference",
"author": "Meerkat@merebox.com",
"version": "0.0.1"
},
{
"extension": "template",
"renderer": "MARTILQREFERENCE:Mustache",
"url": "template/martilq_no_struct.must"
}
]
}

View File

@ -0,0 +1,43 @@
# Documentation
**martiLQ** stands for metadata reconcilation for transfer information, load quality
Before starting with **martiLQ** it is advisable to understand if it is right for
your organisation's needs. Information is available in a number of short
documents.
There is no quickstart document to get you started as each use case and
organisation is different. There are sample implementations which you
can adjust if they resonate with your circumstances,
see [sample implementations](samples/)
There are sample implementations which you
can adjust if they resonate with your circumstances.
The source, documentation and samples are available at `<https://github.com/meerkat-manor/marti>`_
## MartiLQ objective
The objective of **martiLQ** is to define a simple standard for
capturing the data files being transferred. It is not for
real time web service transactions.
**martiLQ** is about file and document transfer and reconciling
that the all files have arrived and have not changed, and if so
required are also encrypted.
The proposition is to have a common, machine readable format
for file exchange that:
* ensures data load quality and reconciles
* can be used on Linux or Windows or Mac
* can be used with Python, Java, PowerShell, Golang, etc
* can be used by web services
* uses a text based format (JSON)
* can form part of the data processing pipeline
And finally is easy to understand.
To get a better understanding have a look at the definition
in [martiLQ](martiLQ.md)

View File

@ -0,0 +1,36 @@
# Attribute definition
A Resource can list attributes related to the document / file.
An attribute is a generic definition and conventions are
observed in the definitions that are captured here. The attribute
section is where load quality metrics are defined.
## Attribute definition
The Attribute is described by the table below. Recommended
values are listed but custom values can also be defined, just be
certain the recipient is able to understand them.
Name|Description|Values or Default
---|---|---
category|A type of attribute|dataset, format
name|A name for the attribute|records,columns,header,footer,separator,quote, escape
function|A function to perform|count,sum,unique
comparison|A comparison value or NA|NA, EQ, NE, GT, GE, LT,LE
value|The value for the attribute based on the above complex key, excluding comparison|numeric
A sample JSON is shown below which describes the
number of records in the file for the given format.
```json
"attributes": [
{
"category": "dataset",
"name": "records",
"function": "count",
"comparison": "EQ",
"value": "9"
}
]
```

View File

@ -0,0 +1,15 @@
# CKAN definition
The **martiLQ** has used similar terms and structures that are found in the
CKAN API describing the resources. This similarity allows for simple mapping of values
from the CKAN format to **martiLQ** format.
What **martiLQ** extends above the CKAN definition is attributes that can be
used to reconcile with the received data, plus the ability to define
compressed and encrypted resources.
For more information on CKAN see https://ckan.org/
A sample JSON to compare against the **martiLQ** document
is https://data.gov.au/data/dataset/f2b7c2c1-f4ef-4ae9-aba5-45c19e4d3038

View File

@ -0,0 +1,46 @@
Comparison of martiLQ document definition
=========================================
The use of metadata definitions is not unique and examples
exist in many different situations. Some are standard and open
while others are closed.
Some open standards are EXIF data for pictures, SQL DDL defintions
for databases, the XMP definition and web header responses before the
web content.
The **martiLQ** document definition is intended to cover the situation
where data files are being transferred and reconciliation is required.
The **martiLQ** document definition is modelled on
the [CKAN API metadata](https://docs.ckan.org/en/2.9/api/index.html)
which has been adapted to included additional elements relevant to when
you are exchanging data files. This includes the reconciliation elements
such as number of records and file hash.
As the definition is based on the CKAN API, there are tools to import
a CKAN source into a **martiLQ** document definition and then process the data
through the pipeline as you would for any other data file that had a
**martiLQ** document definition.
Benefit of CKAN and martiLQ
---------------------------
The CKAN is excellent at defining the data source details but it lacks information
for load quality. If you have CKAN deployed in your organisation and wish
exhange or process the data referenced in CKAN, then there are synergies between
CKAN and marti.
Samples exist on CKAN integration.
Magda and martiLQ
-----------------
Another source of data is [Magda](https://magda.io/) which has API metadata
definitions. Magda is more about data federation and as such provides
functionality on finding data sources and describing the contents.
The Magda software is able to generate APIs and data content. This does not
address the needs of data processing pipeline when reconciliation is required.
If you have Magda data sources then synergies exist between Magda and martiLQ.

View File

@ -0,0 +1,10 @@
Custom Definition
=================
The custome definition section allows the inclusion of extensions
to the standard. To demonstrate the inclusion, there are three
sample extensions. These are:
* Software - describing the **martiLQ** software version
* Spatial - Defining the geographical boundary of the documents
* Temporal - Defining date and time aspects of the documents

View File

@ -0,0 +1,8 @@
Magda definitions
=================
https://magda.io/
https://search.data.gov.au/api/v0/apidocs/index.html

View File

@ -0,0 +1,172 @@
# MartiLQ document
The metadata reconciliation transfer information is referred
to as the **martiLQ** document throughout this documentation.
The **martiLQ** document can be part of a message or a file
in its own right. The definition is currently a JSON file.
## Structure
The JSON is composed of:
* A root definition that contains information applicable to all reosurces
* A [resource](resources.md) list that contains information related
to each document or file
* [Attribute](attributes.md) list as a child to each resource
* A [custom](custom.md) list
## Mandatory root information
The mandatory information in the root of the **martiLQ** document is:
Name|Description|Default or values
---|---|---
title|A title or batch identifier. Use a name that is easy to understand or relate to|None
uid|Unique identifier for the document. If the same document is reproduced this may not change but the minor number of the batch must change|Auto generated
resources|Resource list. See Resource section summary below or detailed document [Resource](resources.md)|At least one required
### Optional information
The optional information is described in the table below. A number of elements can be configured for their
default value(s) in a configuration file.
Name|Description|Default or values
---|---|--
description|Long description of the purpose, background or files included|None
modified|Modified date and time of the **martiLQ** document|Now
tags|List of tags or keywords|
publisher|Publisher name|
contactPoint|Contact point of a person or team|
accessLevel|Acces level|
rights|Rights|
* Batch
* License
* Described By - A link to the metadata describing the document.
More detailed information could be supplied at the link
* Landing page
* Theme
* Custom list - List of custom entries, one being the **martiLQ** software details
see [custom](custom.md)
### Information extension
The information supplied can be extended by party agreement and there
are place holders in the defintion.
## Resource
The resource section is a list of documents or files that are to be grouped
together are listed under the same **martiLQ** definition.
At least one document or file must be included. If the same resource is repeated
it will commonly be for definiting multiple formats, with each file having a
different extension. Commonly the definition includes at least the following
items:
Name|Description|Default or values
---|---|--
title|Title for the resource|Document name
uid|A unique identifier, commonly a GUID|Auto generated
documentName|A name of the document such as the file name
issueDate|Issued date - When the document was made available. The date can include time
modified|Modified - When the document was created or modified. This is the data and time
size|Size of document - The document size in bytes
url|URL - This can be ``file://``, ``https://``, ``ftp://``, etc resource location
### Resource optional
The following are some of the optional items in the resource section. See [Resource](resources.md)
for more details
Name|Description|Default or values
---|---|--
hash|Hash of document - The hash of the document, which can be blank especially for large documents
algo|Hash algorithm - Algoroithm used to generate the hash value or sign it
description|Description - A more detailed description
version|Version - A document version
encoding|Encoding
contentType|Content Type
compression|Compression|None
encryption|Encryption|None
author|Author
attributes|List of attributes for the resource|Record count
## Simple sample
A sample of a single resource **martiLQ** document is shown below. The
sample can be generated using the GOLANG client program with parameters:
```
-t GEN -m Sample.json -s ./docs/source/martilq.md --title "GEN001" --description "Simple example"
```
```json
{
"contentType": "application/vnd.martilq.json",
"title": "GEN001",
"uid": "9a0a7edb-dd81-4fc5-a6cb-c5716eda7b51",
"description": "Simple example",
"modified": "2021-11-02T22:44:29.6887001+11:00",
"publisher": "",
"contactPoint": "",
"accessLevel": "Confidential",
"rights": "Restricted",
"tags": null,
"license": "",
"state": "active",
"batch": 1.001,
"describedBy": "",
"landingPage": "",
"theme": "",
"resources": [
{
"title": "martilq.md",
"uid": "a88b4e5f-66b7-4003-ac24-831c95d0da07",
"documentName": "martilq.md",
"issueDate": "2021-11-02T22:44:29.6881663+11:00",
"modified": "2021-11-02T07:47:13.9410018+11:00",
"expires": "2023-11-02T00:00:00+11:00",
"state": "active",
"author": "",
"length": 3654,
"hash": {
"algo": "SHA256",
"value": "213a6254ddc02423b6c3bb3d977892678258539d37f06410ef18d27c14ffa821",
"signed": false
},
"description": "",
"url": "http://localhost/martilq/martilq.md",
"version": "",
"contentType": "",
"encoding": "UTF-8",
"compression": "",
"encryption": "",
"describedBy": "",
"attributes": [
{
"category": "dataset",
"name": "records",
"function": "count",
"comparison": "EQ",
"value": "95"
}
]
}
],
"custom": [
{
"extension": "software",
"softwareName": "MARTILQREFERENCE",
"author": "Meerkat@merebox.com",
"version": "0.0.1"
}
]
}
```
You can view a more complete sample [samples/json/sample_02.md](samples/json/sample_02.md)
which has been generated using a configuration file to supply default values.

View File

@ -0,0 +1,51 @@
# Load Quality
The **martiLQ** document allows for the inclusion of load quality
metrics. The load quality metrics is intended to be
able to be applied universally with common tools. Not
all needs are covered with the base definition but can be extended.
The load quality metrics are in the majority defined in the [attributes](attributes.md)
list attached to each resource. Therefore each resource can have different
load quality metrics.
## Defined load quality metrics
* Sequential batch number - This is a decimal number defined at the **martiLQ** document
header and applies to all resources. The integer portion is for new batches and the fraction
part can be used for issues with the same data extract. such as requiring resend because
a resource was missing.
* Number of records in the document - This is the number of data primary records not the
count of end of lines and is agreed between parties. XML record counts could be based
on the number of primary segments under root. JSON records can be counted in a similar way.
The headers or trailling records are not counted
## Addresses deficiencies
The **martiLQ** objective is to address deficiencies with alternative
data load quality approaches such as:
* magic formats in file names
* identifying the number of files
* knowing when all files are ready
* separate documentation that is unlinked
* securing the data
* adding footers to the data, requiring custom file handlers
## Extending load quality metrics
**martLQ** document is open to extension so that extra
load metrics appropriate to the situation can be included.
### Extension ideas
The following extensions for load quality can easily be included:
* Mandatory data in column
* Uniqueness of data values
* Data values are within defined tolerances
* Check for data exclusions
And all this information is included in the **martiLQ** document
allowing for self describing load quality.

View File

@ -0,0 +1,24 @@
# References
The following are references to documents that inspired the creation of **martiLQ**
document and associatd framework.
https://dex.dss.gov.au/sites/default/files/documents/2021-06/data-exchange-protocols-june-2021.pdf
https://www.imf.org/en/Data
https://www.imf.org/-/media/Files/Publications/WEO/WEO-Database/2021/WEOApr2021all.ashx
SDMX
LIXI
https://www.imf.org/-/media/Files/Publications/WEO/WEO-Database/2021/WEOApr2021all.ashx
https://standards.theodi.org/introduction/types-of-open-standards-for-data/
https://developers.google.com/transit/gtfs/
http://www.popoloproject.com/
https://datatracker.ietf.org/doc/html/rfc6350#section-6.2.7
https://www.w3.org/TR/vocab-dcat/

View File

@ -0,0 +1,98 @@
# Resources definition
The resources section defines the files that are grouped
together by association. This association is not defined but can
include different formats of the same data or a common batch extract
such as end of day.
Some files may expand to multiple files if they are
compressed with a utility such as WinZIP or 7ZIP. In the situation
where a ZIP file expands to multiple documents, then the expectation is
that the ZIP file contains a **martiLQ** document describing its contents.
The elements in the resource section are:
* Title
* Document name - Commonly being absolute or relative file name.
This value could also be an URL address or network path
* Issued date - When the document was made available. The date can include time
* Modified - When the document was created or modified. This is the data and time
* Size of file - The file size in bytes
* Hash of file - The hash of the file, which can be blank especially for large files
* Hash algorithm
* Attributes - List of attributes associated with the document
The following are optional in the resource section.
* Identifier
* Description
* Download URL
* Version - File version. The same file could be updated or this might denote the next version
of a regular report. For example a daily extract will have the version number incremented
every day and provide a new URL. The previous file can be retained.
* Content type - if not specified then the consumer will in all likelihood use the file extension / mime type
* Expiry Date - The date and time that this file expires and can be removed from the download URL
location. This is not the file retention period as might be required for archiving.
* Described By - A link to the metadata describing this file data and format
* Compression - Type of compression used if any
* Encryption - Type of encryption used if any
## Compression
Files can be compressed using a utility. A single compressed file can contain
multiple files. The **martiLQ** definition document applies to the compressed file
and not to the contents, which could be multiple files.
In the case of a compressed files, there should be a **martiLQ** definition document in the
compressed file.
Compression of files always occur before encryption.
### martiLQ definition for Compressed File
For a compressed file that is not encrypted, the distribution definition will be:
* Title - The compressed file title which could be a group name
* Document name - Commonly being absolute or relative file name.
This value could also be an URL address or network path
* Issued date - When the compressed file was made available.
* Modified - When the compressed file was created or modified. This is the date and time
and is not the modified date of the file in the compressed file.
* Size of file - The compressed file size in bytes
* Hash of file - The hash of the compressed file, which can be
blank especially for large files
* Hash algorithm
The reason for this approach is it allows a generic tool to be deployed to
check the validity of the contents without unpacking the received /fetched
file. That is you can perform load quality pipeline processing.
## Encryption
The encryption of content is always applied after compression not before, if
you are not using the compression tool native encryption. WinZIP and 7ZIP
provide encryption within the tool execution.
If the compression is TAR or GZIP then you may consider applying a GPG
or other encryption algorithm to the compressed file.
* Title - The encrypted file title
* Document name - Commonly being absolute or relative file name.
This value could also be an URL address or network path
* Issued date - When the **encrypted** file was made available.
* Modified - When the **encrypted** file was created or modified.
This is the data and time and is not the modified date of the encrypted file.
* Size of file - The **decrypted** file size in bytes
* Hash of file - The hash of the **decrypted** file, which can be
blank especially for large files
* Hash algorithm
The rational for using the decrypted file attributes is that an ecrypted
file is unlikely to be able to be modified without knowing encryption keys.
Checking the decrypted fille attributes is a better check.
The reason for this approach is it allows a generic tool to be deployed to
decrypt and check the validity of the received / fetched file without
needing to understand the contents. That is you can perform load quality
pipeline processing.

View File

@ -0,0 +1,18 @@
# What is marti
The foundation pillar for the **martiLQ** framework is the [martiLQ document](martiLQ.md)
that defines the reconciliation and other metadata of the document / file being transferred.
A definition, while fundamental, benefits from having tools that can create, read and
interpret the definition. The **martiLQ** framework is about providing those tools
and an ecosystem that can be added to.
The majority of the effort in creating **martiLQ** is in the tools. There are tools
for various programming languages and situations. As many programming languages
generate portable programs that can execute on multiple operating systems, the
likelihood is that a tools exists for you.
The source for tools is provided in the Github repository and some have precompiled
images.
See the project source directory for more details.

View File

@ -0,0 +1,22 @@
# When would you use martiLQ
You are likely to start using the **martiLQ** framework when:
1. you have no existing standard or framework or;
2. your existing standards no longer meets you needs or;
3. you are starting document exchange with another business or division that uses the framework or;
4. there is a benefit to further automating your document processing in regard to audit and reconciliation
If you already have a standard and it works for you, and you have no upcoming (large)
initiative that would benefit from the framework, then stick with what you have. The benefits
of the framework are unlikely to weigh in your framework.
## Read the material
Please read the material before jumping in and make sure the **martiLQ** framework will
provide you with the expected benefits. Implementing the framework and then changing
your direction will more than likely add to your support costs.
**Note**: As the framework is based on open structure and can be processed independently
without the tools, you can export the definitions to other tools / future methods.

View File

@ -0,0 +1,41 @@
Who is likely to use martiLQ
============================
You are likely to find the **martiLQ** framework relevant if you:
1. Have many document exchanges, such as End of Day batches
2. Need to verify or reconcile the documents
Data exchanges
--------------
If you are creating or receiving many documents or files on a regular basis
then you probably have some framework defined. The framework may be as simple as:
1. The files are placed in given folders that have significance, such as the source or topic
2. File names have a naming standard, such as subject domain and date of extract
Simple framework such as the above have limitations, such as:
* File names becoming long and need special parsing, with associated testing
* Risk of overwriting
* New folders need to be created for new sources
* Require constant polling, if passive
* Lower automation prospects and alignment to DataSecOps
* Poor fit to web applications (they tend to be designed for FTP and LAN)
Framework Sidecar files
-----------------------
The **martiLQ** framework addresses the issues and limitations by using sidecar
or shadow files. The [concept of sidecar files](https://en.wikipedia.org/wiki/Sidecar_file) is
not new and are commonly found associated to media file processing.
Sidecar files can also be implemented as ``forks`` and built into the operating system, such as
in Mac OS X HFS. The Microsoft NTFS supports Alternate Data Streams to achieve a similar outcome.
Unfortunately this information is not transferrable to other systems.
The proposition is to define a format for the sidecare file and provide common library tools that
can be be used on multiple platforms when exchanging documents / files. Multiple documents can be
defined in a singel **martiLQ** definition which adds to efficiency and productivity if used
for End of Day or similar batches - or even single file transfers.

View File

@ -0,0 +1,37 @@
Why use marti
=============
**martiLQ** is a framework for providing a degree of auditability and reconciliation of
documents transferred between systems in an organisation and externally. It does not intend
define the format or content of the document. It defines controls that can be used to:
1. verify source
2. identify unauthorized alterations (tampering)
3. reconcile agreed metrics, such as the number of records
4. ensure sequential processing
5. describe metadata including format, extract time
6. link to further information
You would use **martiLQ** if any of the controls are a requirement for you.
Documents
---------
Documents in this context are digital storage objects such as operating system files,
cloud storage objects or blobs. The document content can have structure and contains multiple
records or it can be unstructered such as PDFs. If you are including PDFs then they are
likely to be be supporting documents for your actual data files.
The **martiLQ** framework is not intended to be used for single record transfers such as
in single web transactions. It is for providing controls when moving large amounts of
data as one event. This data are commonly referred to as batch extracts and performed
at scheduled times such as end of day.
Security
--------
The framework does not replace your security, inflight encryption or encryption at rest.
You are encouraged to use TLS or SSH to connect devices and transfer documents. Storage
encryption and access controls for your documents is also relevant as part of the bigger
picture.

View File

@ -0,0 +1,196 @@
package main
import (
"flag"
"log"
"net/http"
"strings"
"path/filepath"
"io/ioutil"
"os"
"encoding/json"
)
func main() {
port := flag.String("p", "8080", "Http listen port")
staticDirectory := flag.String("static", "static", "Static directory content")
docsDirectory := flag.String("docs", "", "Document directory content")
dataDirectory := flag.String("data", "", "Data directory content")
templateDirectory := flag.String("template", "", "Template directory content")
trace := flag.Bool("trace", false, "Produce trace logs")
flag.Parse()
if *trace == true {
log.Printf("static folder: %s\n", *staticDirectory)
log.Printf("data folder: %s\n", *dataDirectory)
log.Printf("docs folder: %s\n", *docsDirectory)
log.Printf("template folder: %s\n", *templateDirectory)
}
http.HandleFunc("/data/", func( res http.ResponseWriter, req *http.Request ) {
safePath := ValidatePath(req.URL.Path[1:])
if (*dataDirectory != "") {
safePath = filepath.FromSlash(filepath.Join(*dataDirectory, strings.Replace(safePath, "data/", "", 1)))
}
if *trace == true {
log.Printf("resolved data folder: %s\n", safePath)
}
http.ServeFile(res, req, safePath)
})
http.HandleFunc("/template/", func( res http.ResponseWriter, req *http.Request ) {
safePath := ValidatePath(req.URL.Path[1:])
if (*templateDirectory != "") {
safePath = filepath.FromSlash(filepath.Join(*templateDirectory, strings.Replace(safePath, "template/", "", 1)))
}
if *trace == true {
log.Printf("resolved template folder: %s\n", safePath)
}
http.ServeFile(res, req, safePath)
})
http.HandleFunc("/docs/", func( res http.ResponseWriter, req *http.Request ) {
localPath := ""
if (*docsDirectory == "") {
temp := "../../.."
docsDirectory = &temp
localPath = ValidatePath(filepath.FromSlash(*docsDirectory+req.URL.Path))
} else {
localPath = ValidatePath(filepath.FromSlash(*docsDirectory+strings.Replace(req.URL.Path, "docs/", "", 1)))
}
if *trace == true {
log.Printf("resolved docs folder: \"%s\"", localPath)
}
f, err := os.Open(localPath)
if err != nil {
log.Printf("fetch docs error: \"%s\" with %s", localPath, err)
http.NotFound(res, req)
} else {
s, err := f.Stat()
if err != nil || s.IsDir() {
log.Printf("fetch docs stat error: \"%s\"", localPath)
http.NotFound(res, req)
//http.ServeFile(res, req, filepath.FromSlash(*staticDirectory + "/404.html"))
} else {
http.ServeFile(res, req, localPath)
}
}
})
http.HandleFunc("/api/", apiHandler)
http.HandleFunc("/api/view", apiHandlerView)
fileServer := http.FileServer(FileSystem{http.Dir(*staticDirectory)})
http.Handle("/", fileServer)
log.Printf("Serving on HTTP port: %s\n", *port)
log.Fatal(http.ListenAndServe(":"+*port, nil))
}
type FileSystem struct {
fs http.FileSystem
}
func (fs FileSystem) Open(path string) (http.File, error) {
f, err := fs.fs.Open(path)
if err != nil {
return nil, err
}
s, err := f.Stat()
if s.IsDir() {
index := strings.TrimSuffix(path, "/") + "/index.html"
if _, err := fs.fs.Open(index); err != nil {
return nil, err
}
}
return f, nil
}
func ValidatePath(path string) string {
safePath := path
return safePath
}
func apiHandler(res http.ResponseWriter, req *http.Request) {
apiPath := req.URL.Path
log.Printf("fetch api: \"%s\"", apiPath)
res.Write([]byte("<h1>Welcome to my web server!</h1>"))
}
type oTemplate struct {
Extension string `json:"extension"`
Renderer string `json:"renderer"`
Url string `json:"url"`
}
type Definition struct {
FileName string `json:"fileName"`
Describe string `json:"describe"`
}
type DirectoryList struct {
Custom []oTemplate `json:"custom"`
Files []Definition `json:"files"`
}
func apiHandlerView(res http.ResponseWriter, req *http.Request) {
dataFolder := "data/"
if req.Method == "GET" {
res.Header().Set("contentType", "application/json")
files, err := ioutil.ReadDir(dataFolder)
if err != nil {
log.Fatal(err)
}
template := oTemplate{Extension: "template", Renderer: "MARTILQREFERENCE:Mustache", Url: "template/martilq_view.must"}
fileList := []Definition{}
list := DirectoryList{}
list.Custom = append(list.Custom, template)
for _, file := range files {
if !file.IsDir() {
if filepath.Ext(file.Name()) == ".json" {
describe := ""
// Fetch the description
data, err := ioutil.ReadFile(filepath.Join(dataFolder,file.Name()))
if err != nil {
log.Fatal("error with file read ")
} else {
var unknown map[string]interface{}
err = json.Unmarshal(data, &unknown)
if err != nil {
log.Fatal("error with json read ")
} else {
describe = unknown["title"].(string)
}
}
def := Definition{FileName: file.Name(), Describe: describe}
fileList = append(fileList, def)
}
}
}
list.Files = fileList
content, _ := json.Marshal(list)
res.Write([]byte(content))
} else {
http.Error(res, "Only GET requests are allowed!", http.StatusMethodNotAllowed)
}
}

View File

Before

Width:  |  Height:  |  Size: 1007 B

After

Width:  |  Height:  |  Size: 1007 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -83,6 +83,8 @@
<script src="assets/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js" integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.min.js"></script>
<script src="js/martilq_app.js"></script>
</body>
</html>

View File

@ -2,10 +2,12 @@
"use strict";
const output = document.querySelector(".output");
var localJsonFile = "data/marti_test_asic.json";
var localJsonFile = "";
var btn = document.getElementById("loadBtn");
btn.onclick = dataLoadFunction;
if (btn) {
btn.onclick = dataLoadFunction;
}
function dataLoadFunction() {
var loadDef = document.getElementById("loaddefinition")
@ -31,7 +33,9 @@ window.addEventListener("DOMContentLoaded", () => {
fetchData("data/"+ loadDef);
}
var ld = document.getElementById("loaddefinition")
ld.value = loadDef
if (ld) {
ld.value = loadDef
}
} else {
output.innerHTML = "Please supply a MartiLQ definition to load, such as \"<a href=\"?martilq=martilq_asic.json\">martilq_asic.json</a>\"";
}
@ -44,13 +48,15 @@ function fetchData(dataFile) {
.then((response) => response.json())
.then((data) => {
var template = "data/martilq_def.must";
var template = "template/martilq_default.must";
jdata["item"] = data;
jdata["describe"] = data.description.replace(/\r\n/g, "<br>");
if (data.description) {
jdata["describe"] = data.description.replace(/\r\n/g, "<br>");
}
if (data["custom"]) {
data.custom.forEach((el) => {
if (el.extension == "template" && el.renderer == "MartiReference:Mustache") {
if (el.extension == "template" && el.renderer == "MARTILQREFERENCE:Mustache") {
template = el.url;
}
});

View File

@ -0,0 +1,58 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.88.1">
<title>MartiLQ Definition</title>
<!-- Bootstrap core CSS -->
<link href="assets/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
</style>
<link href="css/dashboard.css" rel="stylesheet">
</head>
<body>
<header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="https://github.com/meerkat-manor/marti">MartiLQ</a>
</header>
<div class="container-fluid">
<div class="row">
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<div class="table-responsive">
<div class="output"></div>
</div>
</main>
</div>
</div>
<footer class="bg-dark flex-md-nowrap p-0 shadow">
</footer>
<script src="assets/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js" integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.min.js"></script>
<script src="js/martilq_app.js"></script>
</body>
</html>

View File

@ -0,0 +1,79 @@
<h2>Metadata definition</h2>
{{#item}}
<br>
<h3>File details</h3>
<table class="table table-striped table-sm">
<tr><th>Description</th><td>{{describe}}</td></tr>
<tr><th>Encoding</th><td>{{encoding}}</td></tr>
<tr><th>Format</th><td>{{format}}</td></tr>
<tr><th>Multi record</th><td>{{multiRecord}}</td></tr>
<tr><th>Line Endings</th><td>{{lineEndings}}</td></tr>
<tr><th>Header</th><td>{{header}}</td></tr>
<tr><th>Footer</th><td>{{footer}}</td></tr>
<tr><th>Delimiter</th><td>{{delimiter}}</td></tr>
<tr><th>Quote</th><td>{{quote}}</td></tr>
<tr><th>Escape</th><td>{{escape}}</td></tr>
</table>
{{#recordFormat}}
<br>
<h3>{{formatName}} Columns</h3>
<table class="table table-striped table-sm">
<thead>
<th>Name</th><th>Description</th><th>Type</th><th>Length</th><th>Nullable</th><th>Default</th><th>Dataset</th>
<thead>
<tbody>
{{#columns}}
<tr>
<td>{{columnName}}</td>
<td>{{description}}</td>
<td>{{type}}</td>
<td>{{length}}</td>
<td>{{nullable}}</td>
<td>{{default}}</td>
<td>{{dataset}}</td>
</tr>
<tr>
<td></td>
<td>Format: {{formatting}}</td>
<td colspan="2">L Label: {{labels.leftLabel}}</td>
<td colspan="2">R Label: {{labels.rightLabel}}</td>
<td colspan="2">Heading: {{labels.heading}}</td>
</tr>
{{/columns}}
</tbody>
</table>
{{/recordFormat}}
<br>
<h3>Index</h3>
<table class="table table-striped table-sm">
<thead>
<th>Name</th><th>Primary</th><th>Unique</th><th>Columns</th>
<thead>
<tbody>
{{#indexes}}
<tr>
<td>{{indexName}}</td>
<td>{{primary}}</td>
<td>{{unique}}</td>
<td>
{{#columns}}
{{name}},
{{/columns}}
</td>
</tr>
{{/indexes}}
</tbody>
</table>
<br><hr>
<h3>Sample</h3>
<pre>
{{#sample}}
{{record}}
{{/sample}}
</pre>
{{/item}}
<p font="small"><a href="/template/details/martilq_cols_csv.must">Default columns CSV template</a>. Version: 202111B</p>

View File

@ -0,0 +1,75 @@
<h2>Metadata definition</h2>
{{#item}}
<br>
<h3>File details</h3>
<table class="table table-striped table-sm">
<tr><th>Description</th><td>{{describe}}</td></tr>
<tr><th>Encoding</th><td>{{encoding}}</td></tr>
<tr><th>Format</th><td>{{format}}</td></tr>
<tr><th>Multi record</th><td>{{multiRecord}}</td></tr>
<tr><th>Line Endings</th><td>{{lineEndings}}</td></tr>
<tr><th>Header</th><td>{{header}}</td></tr>
<tr><th>Footer</th><td>{{footer}}</td></tr>
</table>
{{#recordFormat}}
<br>
<h3>{{formatName}} Columns</h3>
<table class="table table-striped table-sm">
<thead>
<th>Name</th><th>Description</th><th>Type</th><th>Starting</th><th>Ending</th><th>Dataset</th>
<thead>
<tbody>
{{#columns}}
<tr>
<td>{{columnName}}</td>
<td>{{description}}</td>
<td>{{type}}</td>
<td>{{starting}}</td>
<td>{{ending}}</td>
<td>{{dataset}}</td>
</tr>
<tr>
<td></td>
<td>Format: {{formatting}}</td>
<td colspan="2">L Label: {{labels.leftLabel}}</td>
<td colspan="2">R Label: {{labels.rightLabel}}</td>
<td colspan="2">Heading: {{labels.heading}}</td>
</tr>
{{/columns}}
</tbody>
</table>
{{/recordFormat}}
<br>
<h3>Index</h3>
<table class="table table-striped table-sm">
<thead>
<th>Name</th><th>Primary</th><th>Unique</th><th>Columns</th>
<thead>
<tbody>
{{#indexes}}
<tr>
<td>{{indexName}}</td>
<td>{{primary}}</td>
<td>{{unique}}</td>
<td>
{{#columns}}
{{name}},
{{/columns}}
</td>
</tr>
{{/indexes}}
</tbody>
</table>
<br><hr>
<h3>Sample</h3>
<pre>
{{#sample}}
{{record}}
{{/sample}}
</pre>
{{/item}}
<p font="small"><a href="/template/details/martilq_cols_fco.must">Default columns FCP template</a>. Version: 202111B</p>

View File

@ -21,7 +21,7 @@
<br>
<table class="table table-striped table-sm">
<thead>
<th>Title</th><th>Document</th><th>Size</th><th>Issued</th><th>Modified</th><th>Format</th><th>State</th><th>Version</th>
<th>Title</th><th>Document</th><th>Size</th><th>Issued</th><th>Modified</th><th>Format</th><th>State</th><th>Version</th><th>Structure</th>
<thead>
<tbody>
{{#resources}}
@ -34,8 +34,10 @@
<td>{{format}}</td>
<td>{{state}}</td>
<td>{{version}}</td>
<td><a href="{{structure}}">Structure</a></td>
</tr>
{{/resources}}
</tbody>
</table>
{{/item}}
<p font="small"><a href="/template/martilq_ckan.must">CKAN template</a>. Version: 202111B</p>

View File

@ -16,7 +16,7 @@
<br>
<table class="table table-striped table-sm">
<thead>
<th>Title</th><th>Document</th><th>Size</th><th>Issued</th><th>Modified</th><th>Expires</th><th>State</th><th>Version</th>
<th>Title</th><th>Document</th><th>Size</th><th>Issued</th><th>Modified</th><th>Expires</th><th>State</th><th>Version</th><th>Structure</th>
<thead>
<tbody>
{{#resources}}
@ -29,8 +29,10 @@
<td>{{expires}}</td>
<td>{{state}}</td>
<td>{{version}}</td>
<td><a href="{{structure}}">Structure</a></td>
</tr>
{{/resources}}
</tbody>
</table>
{{/item}}
<p font="small"><a href="/template/martilq_custom.must">Custom template</a>. Version: 202111B</p>

View File

@ -0,0 +1,41 @@
<br>
<table class="table table-striped table-sm">
<tr><th>Title</th><td>{{item.title}}</td></tr>
<tr><th>UID</th><td>{{item.uid}}</td></tr>
<tr><th>Description</th><td>{{{describe}}}</td></tr>
<tr><th>State</th><td>{{{item.state}}}</td></tr>
<tr><th>Issued</th><td>{{item.issued}}</td></tr>
<tr><th>Modified</th><td>{{item.modified}}</td></tr>
<tr><th>Expires</th><td>{{item.expires}}</td></tr>
<tr><th>Tags</th><td>{{item.tags}}</td></tr>
<tr><th>Access Level</th><td>{{item.accessLevel}}</td></tr>
<tr><th>Rights</th><td>{{item.rights}}</td></tr>
<tr><th>License</th><td>{{item.license}}</td></tr>
<tr><th>Contact</th><td>{{item.contactPoint}}</td></tr>
</table>
{{#item}}
<br>
<table class="table table-striped table-sm">
<thead>
<th>Title</th><th>Document</th><th>Size</th><th>Issued</th><th>Modified</th><th>Format</th><th>State</th><th>Version</th><th>Structure</th>
<thead>
<tbody>
{{#resources}}
<tr>
<td>{{title}}</td>
<td><a href="{{url}}">{{documentName}}</a></td>
<td>{{length}}</td>
<td>{{issuedDate}}</td>
<td>{{modified}}</td>
<td>{{contentType}}</td>
<td>{{state}}</td>
<td>{{version}}</td>
<td><a href="{{structure}}">Structure</a></td>
</tr>
{{/resources}}
</tbody>
</table>
{{/item}}
<p font="small"><a href="/template/martilq_default.must">Default template</a>. Version: 202111B</p>

View File

@ -12,6 +12,7 @@
<tr><th>Access Level</th><td>{{item.accessLevel}}</td></tr>
<tr><th>Rights</th><td>{{item.rights}}</td></tr>
<tr><th>License</th><td>{{item.license}}</td></tr>
<tr><th>Contact</th><td>{{item.contactPoint}}</td></tr>
</table>
{{#item}}
@ -28,7 +29,7 @@
<td>{{length}}</td>
<td>{{issuedDate}}</td>
<td>{{modified}}</td>
<td>{{format}}</td>
<td>{{contentType}}</td>
<td>{{state}}</td>
<td>{{version}}</td>
</tr>
@ -36,3 +37,4 @@
</tbody>
</table>
{{/item}}
<p font="small"><a href="/template/martilq_no_struct.must">No structure template</a>. Version: 202111B</p>

View File

@ -0,0 +1,20 @@
<h2>MartiLQ definition</h2>
<p>The following <string>martiLQ</string> definitions are provided as samples</p>
<p>The <a href="https://github.com/meerkat-manor/marti">server code</a> for this is available and the code can be changed to reflect your actual or sample definitions</p>
{{#item}}
<br>
<table class="table table-striped table-sm">
<thead>
<th>Definition</th><th>Document</th>
<thead>
<tbody>
{{#files}}
<tr>
<td><a href="browse.html?martilq={{fileName}}">{{fileName}}</a></td>
<td>{{describe}}</td>
</tr>
{{/files}}
</tbody>
</table>
{{/item}}
<p font="small"><a href="/template/martilq_view.must">View template</a>. Version: 202111B</p>

View File

@ -21,8 +21,9 @@ public class Resource {
public String description;
public String url;
public String structure;
public String version;
public String format;
public String content_type;
public String compression;
public String encryption;

View File

@ -1,15 +1,10 @@
#. ..\..\..\source\powershell\MartiLQUtilities.ps1
. .\source\powershell\MartiLQUtilities.ps1
. .\source\powershell\MartiLQConfiguration.ps1
. .\source\powershell\MartiLQResource.ps1
. .\source\powershell\MartiLQAttribute.ps1
function New-MartiDefinition
{
@ -20,22 +15,31 @@ function New-MartiDefinition
version = "$script:SoftwareVersion"
}
$oTemplate = [PSCustomObject]@{
extension = "template"
renderer = "MARTILQREFERENCE:Mustache"
url = ""
}
$publisher = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
[System.Collections.ArrayList]$lcustom = @()
$lcustom += $oSoftware
$lcustom += $oTemplate
[System.Collections.ArrayList]$lresource = @()
$expires = (Get-Date).AddYears(7)
$oMarti = [PSCustomObject]@{
"content-type" = "application/vnd.martilq.json"
contentType = "application/vnd.martilq.json"
title = ""
uid = (New-Guid).ToString()
description = ""
issued = Get-Date -f "yyyy-MM-ddTHH:mm:ss"
modified = Get-Date -f "yyyy-MM-ddTHH:mm:ss"
expires = ""
expires = $expires -f "yyyy-MM-ddTHH:mm:ss"
tags = @( "document", "marti")
publisher = $publisher
contactPoint = ""
@ -218,13 +222,15 @@ Param(
}
$hash = New-MartiHash -Algorithm "SHA256" -FilePath "" -Value $_.hash
$expires = (Get-Date).AddYears(7)
$oResource = [PSCustomObject]@{
title = $_.name
uid = $_.id
documentName = $name
issuedDate = $_.created
modified = $_.last_modified
issuedDate = $_.created.ToString("yyyy-MM-ddTHH:mm:ss")
modified = $_.last_modified.ToString("yyyy-MM-ddTHH:mm:ss")
expires = $expires.Tostring("yyyy-MM-ddTHH:mm:ss")
state = $_.state
author = $oCkan.result.author
length = $_.size
@ -232,10 +238,11 @@ Param(
description = $_.description
url = $_.url
structure = $null
version = $version
format = $_.format
compression = ""
encryption = ""
contentType = Get-MimeType(("."+$_.format))
compression = $null
encryption = $null
}
$lresource += $oResource

View File

@ -1,4 +1,44 @@
function Get-MimeType {
Param(
[Parameter(Mandatory)][String] $Extension
)
$mimeType = "application/unknown";
if ( $null -ne $Extension )
{
Switch ($Extension)
{
".json" { $mimetype = "application/json" ; break }
".md" { $mimetype = "text/markdown" ; break }
".yml" { $mimetype = "text/yaml" ; break }
".rst" { $mimetype = "text/x-rst" ; break }
".7z" { $mimetype = "application/x-7z-compressed" ; break }
".mti" { $mimetype = "application/vnd.martilq.json" ; break }
".ttf" { $mimetype = $null ; break }
".eot" { $mimetype = $null ; break }
".woff" { $mimetype = $null ; break }
".woff2" { $mimetype = $null ; break }
".csv" { $mimetype = "text/csv" ; break }
".tsv" { $mimetype = "text/csv" ; break }
Default {
$drive = Get-PSDrive HKCR -ErrorAction SilentlyContinue;
if ( $null -eq $drive )
{
$drive = New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT
}
$ext = Get-ItemProperty HKCR:$Extension -ErrorAction SilentlyContinue;
if ( $null -ne $ext) {
$mimeType = $ext."Content Type";
}
}
}
}
return $mimeType
}
function New-MartiResource {
Param(
@ -45,14 +85,15 @@ Param(
modified = $item.LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss")
expires = $expires.ToString("yyyy-MM-ddTHH:mm:ss")
state = "active"
author = ""
author = $null
length = $item.Length
hash = $hash
description = ""
url = ""
description = $null
url = $null
structure = $null
version = $version
format = $item.Extension.Substring(1)
contentType = Get-MimeType($item.Extension)
compression = $null
encryption = $null
@ -531,7 +572,7 @@ function Compare-MartiResource {
$formatProcessed = $false
[System.Collections.ArrayList]$lerror = @()
if ($Resource.format -eq "CSV") {
if ($Resource.contentType -eq "text/csv") {
$formatProcessed = $true
$data = $inputData | ConvertFrom-Csv -Delim ','
@ -573,7 +614,7 @@ function Compare-MartiResource {
}
if ($Resource.format -eq "JSON") {
if ($Resource.contentType -eq "application/json") {
$formatProcessed = $true
$data = $inputData | ConvertFrom-Json

View File

@ -30,6 +30,12 @@ class martiLQ:
"version": "0.0.1"
}
_oTemplate = {
"extension": "template",
"renderer": "MARTILQREFERENCE:Mustache",
"url": ""
}
_MartiErrorId = ""
_oMartiDefinition = None
@ -99,16 +105,21 @@ class martiLQ:
publisher = getpass.getuser()
lcustom = []
self._oSoftware["softwareName"] = self.GetSoftwareName()
lcustom.append(self._oSoftware)
self._oTemplate["renderer"] = self.GetSoftwareName() + ":Mustache"
self._oTemplate["url"] = "template/martilq_ckan.must"
lcustom.append(self._oTemplate)
lresource = []
self._oMartiDefinition = {
"content-type": "application/vnd.martilq.json",
"contentType": "application/vnd.martilq.json",
"title": "",
"uid": str(uuid.uuid4()),
"description": "",
"issued": dateToday,
"modified": dateToday,
"publisher": publisher,
"contactPoint": self._oConfiguration.GetConfig("contactPoint"),

View File

@ -109,8 +109,9 @@ class mResource:
"description": "",
"url": self._oConfiguration.GetConfig("urlPrefix"),
"structure": "",
"version": self._oConfiguration.GetConfig("version"),
"content-type": self.GetContentType(SourcePath),
"contentType": self.GetContentType(SourcePath),
"encoding": self._oConfiguration.GetConfig("encoding"),
"compression": self._oConfiguration.GetConfig("compression"),
"encryption": self._oConfiguration.GetConfig("encryption"),
@ -136,6 +137,36 @@ class mResource:
return oResource
def SetAttributeValueString(oResource, Category, Key, Function, Value, Comparison="EQ"):
for item in oResource["attributes"]:
if item["category"] == Category and item["name"] == Key and item["function"] == Function:
if item["comparison"] == "NA" or item["comparison"] == Comparison:
item["comparison"] = Comparison
item["value"] = Value
return
# Add the attribute
oAttribute = {
"category": Category,
"name": Key,
"function": Function,
"comparison": Comparison,
"value": Value
}
oResource["attributes"].append(oAttribute)
return
def SetAttributeValueNumber(oResource, Category, Key, Function, Value, Comparison="EQ"):
mResource.SetAttributeValueString(oResource, Category, Key, Function, Value, Comparison)
return
def NewMartiLQHash(self, Algorithm, FilePath, Value="", Sign=""):

View File

@ -0,0 +1,27 @@
To execute the PowerShell scripts, please invoke from the root Marti directory and not from
with the current directory set to ``.\test\powershel``
``powershell
# To seed the test data
.\test\powershell\test_retrievedata.ps1
# For initial tests
.\test\powershell\test_MartiLQ.ps1
#
.\test\powershell\test_MartiLQCkan.ps1
#
.\test\powershell\test_MartiLQData1.ps1
#
.\test\powershell\test_MartiLQData2.ps1
#
.\test\powershell\test_MartiLQData3.ps1
``

View File

@ -2,8 +2,8 @@
# .\test\powershell\test_MartiLQCkan.ps1 from project root
. .\source\powershell\MartiLQ.ps1
. .\source\powershell\MartiLQItem.ps1
. .\source\powershell\ConvertFrom-Ckan.ps1
. .\source\powershell\MartiLQResource.ps1
. .\source\powershell\MartiLQUtilities.ps1
$outFile = ".\test\powershell\results\marti_test_asic.json"
$ckan = Get-Content -Path ".\docs\source\samples\json\asic_ckan_api.json" -Raw

View File

@ -1,8 +1,8 @@
. .\source\powershell\MartiLQ.ps1
. .\source\powershell\MartiLQItem.ps1
. .\source\powershell\ConvertFrom-Ckan.ps1
. .\source\powershell\Compare-MartiLQResource.ps1
. .\source\powershell\MartiLQResource.ps1
. .\source\powershell\MartiLQAttribute.ps1
. .\source\powershell\MartiLQUtilities.ps1
try {

View File

@ -1,8 +1,9 @@
. .\source\powershell\MartiLQ.ps1
. .\source\powershell\MartiLQItem.ps1
. .\source\powershell\ConvertFrom-Ckan.ps1
. .\source\powershell\Compare-MartiLQResource.ps1
. .\source\powershell\MartiLQResource.ps1
. .\source\powershell\MartiLQAttribute.ps1
. .\source\powershell\MartiLQUtilities.ps1
try {

View File

@ -1,8 +1,8 @@
. .\source\powershell\New-Marti.ps1
. .\source\powershell\ConvertFrom-Ckan.ps1
. .\source\powershell\MartiLQ.ps1
. .\source\powershell\MartiLQUtilities.ps1
if (!(Test-Path -Path ".\test\powershell\results\data")) {