Giter Club home page Giter Club logo

dados-sigplan-planejamento's Introduction

PPAG Planejamento

Updated Checks

Pré-requisitos

Esse projeto utiliza Docker para gerenciamento das dependências. Para fazer build da imagem execute:

docker build --tag ppag-planejamento .

Uso

Para executar o container

docker run -it --rm --mount type=bind,source=$(PWD),target=/project ppag-planejamento bash

Uma vez dentro do container execute os comandos do make

make all

dados-sigplan-planejamento's People

Contributors

fjuniorr avatar

Watchers

 avatar

dados-sigplan-planejamento's Issues

Altera estratégia de nomear colunas a partir da documentação das fontes de dados

Ao começar a experimentar com um projeto para automatizar as conferências realizadas nas bases do sigplan e do sisor no processo de elaboração da LOA e do PPAG pela DCPPN percebi que a inferência do Schema.describe estava incorretamente atribuindo o tipo integer para colunas que deveriam ser number.

Isso gerou falhas em comparações que deveriam ser iguais por causa dos tipos dos dados

── Failure (test_valor_total_anual.R:16:3): Total do orçamento fiscal e investimento SIGPLAN vs SISOR ──
`sisor` not equal to `sigplan`.
Classes differ: 'numeric' is not 'integer64'
[ FAIL 1 | WARN 0 | SKIP 0 | PASS 0 ]

Isso foi um problema específico do data.table no R, mas pode acontecer em outros casos, como por exemplo no df.equals() do pandas. De acordo com o chatGPT:

It depends on what you're looking for:

  • If you want to check if two dataframes are identical in terms of data, data types, column names, indices, and their order, you should use df.equals(). This is a stricter comparison.

  • If you're only interested in whether they have the same data values, and you're not concerned about data types, column names, indices or their order, you can use df.compare(). However, since it does not provide a simple boolean result, you would have to check if the resulting dataframe is empty, like df1.compare(df2).empty.

To summarize, if you want a strict check, use df.equals(). If you are only interested in data values, use df.compare().empty.

For instance, if you want to ensure two datasets are identical (including indices and column names) before running an analysis, df.equals() would be a better choice. On the other hand, if you only care about the data and plan to manipulate the columns or indices later, df.compare().empty could be a good choice.

Além disso, o novo schema das bases do SISOR para 2024 em que as colunas estão mais próximas de um name do que um title mas não necessariamente são o que a gente quer usar mostrou que precisamos abandonar a inversão de name e title na transformação em favor de uma solução mais genérica.

Limpar histórico de dados do repo para migração para modelo de repos upstream

A ideia é que esse repositório (atualmente ppag2023-dadosmg mas que vai mudar para ppag-planejamento ppag-planejamento-dados) contenha apenas a parte de código que em teoria pode ser reutilizada para múltiplos anos e é pequena em termos de espaço.

Para o reuso do código no repositório de cada cada ano (eg. ppag-planejamento-dados-2023) vamos adicionar o repo ppag-planejamento-dados como upstream, permitindo compartilhamento enquanto for necessário mas ainda sendo independentes.

Para manter o histórico desse repo fiz a remoção das pastas data/, logs/ e reports/ usando git-filter-repo

SIGPLAN gera bases txt com dados poluídos de forma intermitente

Ontem eu e @hslinhares vimos o programa 34 - POLICIA OSTENSIVA na base de dados do PPAG 2024 marcado com Programa Novo? Não, algo incorreto tendo em vista o "zeramento" da base exceto para outros poderes e os programas 705 e 706.

Depois de muita investigação com a DCPPN chegamos a conclusão que de forma intermitente o SIGPLAN estava gerando bases de dados poluídas com informações do PPAG 2023.

Não conseguimos chegar a uma conclusão do que estava causando depois de testarmos combinações variadas de:

  • Navegador (Chrome vs Explorer)
  • Usuário (meu e da Carol)
  • Acionamento (GUI vs CLI)

Alterei o log da extração no commit 26f2ffa para ficar mais fácil ver o problema porque sabemos que o programas_planejamento não deve extrair 1.245 registros, e sim cerca de 270 (esqueci o número exato). No log de hoje deu erro novamente:

2023-07-06T12:00:26+0000 INFO  [scripts.extract] Extracting resource programas_planejamento
╭──────────────────────────────────────────────────────────────────────────────╮
│                                                                              │
│                                                                              │
│                                                                              │
│ 09:00:26 - Geração de Arquivos Texto, aguarde esse processamento pode ser um │
│ pouco demorado!                                                              │
│                                                                              │
│                                                                              │
│                                                                              │
│         ::. % - 0 / 1.245 Registros processados - 09:00:26 - 0 seg.          │
│                                                                              │
│ [Clique aqui para fazer o download do arquivo                                │
│ programas_planejamento.txt](../../BasesTxt/programas_planejamento.txt)       │
│                                                                              │
│ ATENÇÃO: Ao abrir o arquivo no Excel use o caracter **|** como separador de  │
│ campos                                                                       │
│                                                                              │
│ 09:01:00 - Arquivo : programas_planejamento.txt gerado com sucesso!!!        │
│                                                                              │
│ **Tempo de Processamento   _00:00:34_**                                      │
│                                                                              │
│                                                                              │
│                                                                              │
│                                                                              │
╰──────────────────────────────────────────────────────────────────────────────╯

Pesquisar regras de quais colunas podem ter missing values

Existem valores ausentes em várias colunas. Por exemplo:

sed -n '633p' data/raw/localizadores_todos_planejamento.txt 

0003|PRESTA??O DE SERVI?OS PREVIDENCI?RIOS|07AT07|Seguran?a P?blica|False|4004|AUX?LIOS PREVIDENCI?RIOS E ASSISTENCIAIS CONCEDIDOS AOS SEGURADOS E AOS SEUS DEPENDENTES|0|Demais Projetos e Atividades|||9|PREVID?NCIA SOCIAL|272|PREVID?NCIA DO REGIME ESTATUT?RIO|02121|INSTITUTO DE PREVID?NCIA DOS SERVIDORES MILITARES DO ESTADO DE MINAS GERAIS|False|V99497|N?o|5|Regi?o Intermedi?ria de Ipatinga|3170578|851|VARGEM ALEGRE|116523|27|119902|28|123392|29|131967|29

Esses valores estão sendo convertidos para NA pelo script scripts/transform.R e gerando erro durante a validação do frictionless tendo em vista que por default as colunas podem ter valores ausentes mas eles devem estar codificados como "":

Many datasets arrive with missing data values, either because a value was not collected or it never existed. Missing values may be indicated simply by the value being empty in other cases a special value may have been used e.g. -, NaN, 0, -9999 etc.

missingValues dictates which string values should be treated as null values. This conversion to null is done before any other attempted type-specific string conversion.
The default value [ "" ] means that empty strings will be converted to null before any other processing takes place.

-- https://specs.frictionlessdata.io/table-schema/#missing-values

Inserir propriedade `hash` manualmente para arquivos que não devem sofrer alteração

Em um dado momento do tempo pode acontecer que os arquivos primários de determinado conjunto não devem mais ser atualizados.

Isso é inclusive um requisito legal da Lei de Acesso a Informação (LAI) que obriga a garantia da integridade1 das informações disponíveis para acesso.

Nesses casos podemos informar manualmente a propriedade hash em cada recurso e o frictionless validate datapackage.yaml vai identificar se houve alguma modificação acidental:. Por exemplo:

─────────────────────────────────────────────────────────────── Tables ────────────────────────────────────────────────────────────────
                                                          acoes_planejamento                                                           
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Row  ┃ Field ┃ Type       ┃ Message                                                                                                 ┃
┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ None │ None  │ hash-count │ The data source does not match the expected hash count: expected is                                     │
│      │       │            │ "d5aa5075fb2dc53d9c58bc4afa2fdf85d0af900c97be88abdc05a12cc89b8bb2" and actual is                        │
│      │       │            │ "c5aa5075fb2dc53d9c58bc4afa2fdf85d0af900c97be88abdc05a12cc89b8bb2"                                      │
└──────┴───────┴────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Uma questão em aberto é que o cálculo da hash do frictionless-py ainda é diferente entre windows e unix.

A discussão do issue transparencia-mg/remuneracao#34 também é relevante para ilustrar a necessidade de garantir a linhagem dos dados.

Footnotes

  1. definida como qualidade da informação não modificada, inclusive quanto à origem, trânsito e destino

Ordem de execução do make gera erros nas validações das chaves estrangeiras

Na documentação da base eu inseri algumas foreignKeys no schema. No entanto, tendo em vista a ordem de execução atual do make ao validar um recurso, por exemplo indicadores_planejamento

frictionless validate --resource-name indicadores_planejamento datapackage.yaml

Eu recebo um erro de que

──────────────────────────────────── Tables ────────────────────────────────────
                            indicadores_planejamento                            
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Row  ┃ Field ┃ Type         ┃ Message                                        ┃
┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ None │ None  │ scheme-error │ The data source could not be successfully      │
│      │       │              │ loaded: [Errno 2] No such file or directory:   │
│      │       │              │ 'data/programas_planejamento.csv'              │
└──────┴───────┴──────────────┴────────────────────────────────────────────────┘

porque python scripts/transform.py programas_planejamento ainda não executou e gerou data/programas_planejamento.csv. O erro acontece mesmo se eu pular o check de foreign-key no frictionless com

frictionless validate --skip-errors foreign-key  --resource-name indicadores_planejamento datapackage.yaml

Se me lembro bem no age7 a gente fazia uma nova rodada de validação no final da execução.

Conversão dos arquivos primários txt para "csv excel" para publicação

O "csv excel" que utiliza codificação utf-8 with bom, separador ; e decimal , é o formato utilizado por padrão para publicação de CSVs no Portal de Dados Abertos (vide transparencia-mg/age7/issues/118) para evitar a necessidade de "tutoriais"1 sobre como abrir os arquivos no excel sem problemas de mojibake.

De acordo com a DCPPN os arquivos primários extraídos do SIGPLAN estão em formato txt (vale a pena confirmar o formato exato em que eles são extraídos) mas o uso corrente por parte da equipe é em excel.

Com a conversão txt -> csv excel atendemos os dois casos.

Footnotes

  1. Como esse da Câmara dos Deputados: Corrigindo a acentuação nos arquivos CSV

Caixa alta não é permitida na propriedade `name` dos recursos

Isso está sendo gerado por causa do arquivo localizadores_Todos_planejamento.txt. É melhor investigar a origem desse nome do que inserir lógica para modificar o nome do recurso criando divergência entre o nome do arquivo e o nome do recurso.

O trecho relevante da especificação:

A resource MUST contain a name property. The name is a simple name or identifier to be used for this resource.

  • If present, the name MUST be unique amongst all resources in this data package.
  • It MUST consist only of lowercase alphanumeric characters plus “.”, “-” and “_”.
  • It would be usual for the name to correspond to the file name (minus the extension) of the data file the resource describes.

Metadados de runtime durante etapa de build do datapackage.json

Ainda que utilizar o yaml seja bom para os processos internos, como em #4-comment e #12, ele ainda não faz parte da especificação1, e, portanto, o nosso produto final deve ser um datapackage.json.

Essa etapa é simples e pode ser gerada por exemplo com:

datapackage.json: datapackage.yaml
    frictionless describe --type package --json datapackage.yaml > datapackage.json

O que eu tenho mais dúvida é se o datapackage.json deve ser versionado e quais informações extras de run time devem ser inseridos (eg. commit).

Um argumento forte para ele ser versionado é que permite que as ferramentas façam a leitura do data package diretamente do repositório do Github.

Footnotes

  1. Vide Yaml as well as JSON for Data Package descriptor files (?) · Issue #292 · frictionlessdata/specs

Remover dependência do yq

O yq é utilizado para extrair os nomes dos recursos do datapackage.yaml para uso no make. Além de ser uma dependência de sistema adicional que exige instalação local e modificações no Dockerfile:

RUN export DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get install -y software-properties-common
RUN add-apt-repository ppa:rmescandon/yq
RUN apt-get install yq -y

Eu perdi muito tempo porque existem dois programas com o mesmo executavel yq mas com flags diferentes.

E o resumo do chatGPT

The yq command with the e or evaluate flag that you mentioned in your original question is from the tool developed by Mike Farah. This tool is written in Go and its syntax is closely aligned with the jq JSON processor.

On the other hand, the yq tool developed by Andrey Kislyuk is a wrapper around jq that translates YAML files to JSON. This version of yq does not support the e flag, and its syntax and options are slightly different.

If you're trying to use the e flag, it seems like you should be using Mike Farah's yq. You can download or update to the latest version of this yq from the GitHub link you provided or using package managers like brew or snap.

If you've installed both versions of yq on your system, they could be conflicting. Ensure that the yq you're using is the one you intend by checking its path with which yq and its version with yq --version.

Traceback muito longo nos logs

Por causa dos objetos da frictionless o traceback está ficando muito grande e dificil de interpretar. Por exemplo:

(venv) ppag-planejamento (main)*$ make extract
python main.py extract acoes_planejamento && python main.py extract indicadores_planejamento && python main.py extract localizadores_todos_planejamento && python main.py extract programas_planejamento && true
2023-06-30T07:47:58-0300 INFO  [scripts.extract] Geração de arquivo texto para acoes_planejamento. Aguarde esse processamento pode ser um pouco demorado!
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /Users/fjunior/Projects/splor/ppag-planejamento/scripts/extract.py:22 in extract_resource        │
│                                                                                                  │
│   19 │   # Tempo de Processamento 00:01:13                                                       │
│   20 │   logger.info(f"Geração de arquivo texto para {resource_name}. Aguarde esse processame    │
│   21 │   if True:                                                                                │
│ ❱ 22 │   │   raise Exception('Mensagem de erro')                                                 │
│   23 │   res = requests.get(resource.custom['api_url']) # Resource is stripping url property     │
│   24 │   res.raise_for_status()                                                                  │
│   25 │   if 'gerado com sucesso!' not in res.text:                                               │
│                                                                                                  │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │    descriptor = 'datapackage.yaml'                                                           │ │
│ │       package = Package(                                                                     │ │
│ │                 │   source=None,                                                             │ │
│ │                 │   control=None,                                                            │ │
│ │                 │   _basepath='',                                                            │ │
│ │                 │   name='ppag2023-dadosmg',                                                 │ │
│ │                 │   title=None,                                                              │ │
│ │                 │   description=None,                                                        │ │
│ │                 │   homepage=None,                                                           │ │
│ │                 │   profile=None,                                                            │ │
│ │                 │   licenses=[],                                                             │ │
│ │                 │   sources=[],                                                              │ │
│ │                 │   contributors=[],                                                         │ │
│ │                 │   keywords=[],                                                             │ │
│ │                 │   image=None,                                                              │ │
│ │                 │   version=None,                                                            │ │
│ │                 │   created=None,                                                            │ │
│ │                 │   resources=[                                                              │ │
│ │ resource_name = 'acoes_planejamento'                                                         │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
Exception: Mensagem de erro
make: *** [extract] Error 1

Vale a pena experimentar com uma versão com menos contexto porém menor:

make extract
python main.py extract acoes_planejamento && python main.py extract indicadores_planejamento && python main.py extract localizadores_todos_planejamento && python main.py extract programas_planejamento && true
2023-06-30T07:55:10-0300 INFO  [scripts.extract] Geração de arquivo texto para acoes_planejamento. Aguarde esse processamento pode ser um pouco demorado!
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /Users/fjunior/Projects/splor/ppag-planejamento/scripts/extract.py:22 in extract_resource        │
│                                                                                                  │
│   19 │   # Tempo de Processamento 00:01:13                                                       │
│   20 │   logger.info(f"Geração de arquivo texto para {resource_name}. Aguarde esse processame    │
│   21 │   if True:                                                                                │
│ ❱ 22 │   │   raise Exception('Mensagem de erro')                                                 │
│   23 │   res = requests.get(resource.custom['api_url']) # Resource is stripping url property     │
│   24 │   res.raise_for_status()                                                                  │
│   25 │   if 'gerado com sucesso!' not in res.text:                                               │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
Exception: Mensagem de erro
make: *** [extract] Error 1

Ps. Ao tentar postar o log original recebi um erro do github de que "There was an error creating your Issue: body is too long (maximum is 65536 characters)."

Etapa de validação dos arquivos txt primários

Se houver modificações nos arquivos primários a validação dos mesmos deve falhar, a menos que manualmente o usuário faça a substituição dos table schemas em schemas/raw/.

Isso significa que os targets

infer: $(TABLESCHEMA_RAW) ingest ## Infer table schema for files in data/raw/ and store under schemas/raw/

$(TABLESCHEMA_RAW): schemas/raw/%.yaml: data/raw/%.txt
	frictionless describe --dialect '{"delimiter": "|"}'  --format csv --type schema --yaml $< > $@

são problemáticos pois se os novos dados tiverem alterações de schema que não são desejadas, os schemas em schemas/raw/ serão atualizados.

Talvez seja mais simples implementar um script scripts/infer.py. O ponto negativo é que ele sera executado para todos os arquivos.

Armazenar dados primários da pasta data-raw/ nos repos anuais durante make publish

Essa alteração é pra permitir uma maior reprodutibilidade do projeto, especialmente nessa fase inicial.

Podemos chegar nos limites de tamanho máximo para o repositório de forma precoce (antes da finalização do ciclo de atualização de uma base)

We recommend repositories remain small, ideally less than 1 GB, and less than 5 GB is strongly recommended. Smaller repositories are faster to clone and easier to work with and maintain. If your repository excessively impacts our infrastructure, you might receive an email from GitHub Support asking you to take corrective action.

Vale reforçar a observação de #21 que os repositórios anuais são "descartáveis" e não existe promessa de que eles serão mantidos online1.

Footnotes

  1. Mas nada impede, muito antes pelo contrário, que a gente faça backup local com git bundle caso seja necessário acessar uma versão específica dos dados. Não temos mais o commit no datapackage.json (como discutido em #14) mas a propriedade updated_at com o título do commit é suficiente pra essa identificação.

Testar leitura do data package zipado com `frictionless-r`

Em um teste inicial recebi a mensagem:

library(frictionless)

package <- read_package("dist/ppag2023-dadosmg.zip")
Error in parse_con(txt, bigint_as_char) : 
  lexical error: invalid char in json text.
                                       PK���                     (right here) ------^

make publish não funciona dentro do docker

O make publish atual executa

publish: 
	git add -Af data/*.csv
	git commit --author="Automated <[email protected]>" -m "Update data package" || exit 0
	git push

No entanto, o git não está instalado no container e executar docker run --rm --mount type=bind,source=${PWD},target=/project ppag-planejamento make all vai gerar o erro abaixo na hora do make publish:

root@94c1d112186e:/project# make publish
git add -Af data/*.csv
/bin/sh: 1: git: not found
make: *** [Makefile:22: publish] Error 127

Não sei se simplesmente instalar o git vai resolver o problema porque pode ser que as permissões não existam de forma correta dentro do container para comunicação no Github. Necessário avaliar esse caso tanto localmente quanto no github actions.

Generalizar makefile entre projetos

Atualmente a variável EXT = txt precisa ser alterada em cada projeto e precisamos manter a consistência entre resource.name e resource.path definidos no datapackage.yaml e o padrão $(INPUT_DIR)/%.$(EXT) definido manualmente no makefile/

Etapa de extração deve ser paralisada em caso de falha em qualquer recurso

Atualmente como os scripts de extração estão sendo combinados com ;. Isso significa que o segundo recurso vai ser extraído ainda que o primeiro lance um erro. Queremos usar && para combinação dos comandos para evitar atualizações parciais. A diferença é:

In a Unix-like command-line shell such as bash, ; and && are both used to link together multiple commands, but they behave differently.

  • The semicolon ; allows you to execute multiple commands in sequence, regardless of whether each previous command succeeds. For example:

    command1 ; command2 ; command3
    

    Here, command2 will execute after command1 completes, regardless of whether command1 succeeded or failed. Similarly, command3 will execute after command2 completes, regardless of command2's outcome.

  • On the other hand, the && operator allows you to execute the next command only if the previous command succeeded (i.e., returned a zero exit status). For example:

    command1 && command2 && command3
    

    Here, command2 will only execute if command1 completes successfully. If command1 fails (returns a non-zero exit status), then command2 won't be executed. Similarly, command3 will only execute if both command1 and command2 succeed.

So, in summary:

  • Use ; to run multiple commands in sequence, regardless of whether each command succeeds or fails.
  • Use && to run multiple commands in sequence, but stop if any command fails.

-- chatGPT

`save-state` command is deprecated

Eu estava recebendo um warning na execução do github actions de que:

The save-state command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands

Eu vi o issue docker/build-push-action#779 e confundi as actions, alterando no commit 2f389db a docker/setup-buildx-action para v3 no lugar da docker/build-push-action . Como ainda não existe docker/setup-buildx-action@v3 recebi o erro

Unable to resolve action docker/setup-buildx-action@v3, unable to find version v3

Criar na base ppag planejamento coluna com a funcional programática formatada

Nas bases do ppag planejamento não há uma coluna com a funcional programática formatada.
Essa informação, necessária à DCMEFO, é criada por meio de concatenação diretamente no BI.
Para melhorar a performance do painel e atender a outros projetos, sugere-se que esta coluna seja incluída diretamente na base durante a etapa de transformação do datapackage.

image

Chave primária composta aceita valores NULL

Os problemas na versão https://github.com/splor-mg/ppag-planejamento-dados-2024/tree/a950492b20bd810485fcc92e22570386d268e11e da base

Os programas que tiveram as áreas temáticas alteradas, estão sem diretriz estratégica. Eles precisam ficar com as diretrizes da época do projeto de lei, vou mandar na planilha em anexa quais são as de cada programa.

Programas: 44, 148, 172

As diretrizes estão no "sistema" na parte que vc visualiza os dados do sistema, mas não aprece quando clica no botão de área, objetivo e diretrizes assim como nas bases.

Além disso, o programa 44 também parece que está com linha duplicada na base programas. Você ajusta isso também?

Deveriam ter sido apontados pela validação mas não foram por causa de um bug nas dependências do projeto frictionlessdata/frictionless-py#1623

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.